diff --git a/src/browserlib/extract-cddl.mjs b/src/browserlib/extract-cddl.mjs index 769349ce..7f93c55d 100644 --- a/src/browserlib/extract-cddl.mjs +++ b/src/browserlib/extract-cddl.mjs @@ -32,10 +32,11 @@ export default function () { // `
` convention, or they don't flag CDDL blocks in any
     // way, making it impossible to extract them.
     const cddlSelectors = ['pre.cddl:not(.exclude):not(.extract)'];
-    const excludeSelectors = ['#cddl-index'];
 
-    // Retrieve all elements that contains CDDL content
-    const cddlEls = getCodeElements(cddlSelectors, { excludeSelectors });
+    // Bikeshed generates a CDDL index that repeats all CDDL blocks, as
+    // siblings that follow the index heading rather than descendants of it.
+    const isInCddlIndex = createSectionTest(document.getElementById('cddl-index'));
+    const cddlEls = getCodeElements(cddlSelectors, { excludeFilter: isInCddlIndex });
 
     // Start by assembling the list of modules
     const modules = {};
@@ -97,6 +98,37 @@ export default function () {
 }
 
 
+/**
+ * Return a function that tells whether an element belongs to the section
+ * introduced by the given element. When that element is a heading, the
+ * section runs from the heading to the next heading of the same or a higher
+ * level. Otherwise, the section is the element's subtree.
+ */
+function createSectionTest(sectionEl) {
+    if (!sectionEl) {
+        return () => false;
+    }
+    const level = getHeadingLevel(sectionEl);
+    if (!level) {
+        return el => sectionEl.contains(el);
+    }
+    const sectionEnd = [...document.querySelectorAll('h1, h2, h3, h4, h5, h6')]
+        .find(heading => getHeadingLevel(heading) <= level && follows(sectionEl, heading));
+    return el => follows(sectionEl, el) && (!sectionEnd || follows(el, sectionEnd));
+}
+
+
+function getHeadingLevel(el) {
+    const match = el.tagName.match(/^H([1-6])$/i);
+    return match ? parseInt(match[1], 10) : 0;
+}
+
+
+function follows(el, other) {
+    return !!(el.compareDocumentPosition(other) & Node.DOCUMENT_POSITION_FOLLOWING);
+}
+
+
 /**
  * Retrieve the list of CDDL module shortnames that the element references.
  *
diff --git a/src/browserlib/get-code-elements.mjs b/src/browserlib/get-code-elements.mjs
index 1b3d4632..4daf0300 100644
--- a/src/browserlib/get-code-elements.mjs
+++ b/src/browserlib/get-code-elements.mjs
@@ -5,15 +5,18 @@ import cloneAndClean from './clone-and-clean.mjs';
  * Helper function that returns a set of code elements in document order based
  * on a given set of selectors, excluding elements that are within an index.
  *
- * The function excludes elements defined in informative sections.
+ * The function excludes elements defined in informative sections, elements
+ * that have an ancestor matching one of the exclude selectors, and elements
+ * for which the exclude filter, called on the live element, returns true.
  *
  * The code elements are cloned and cleaned before they are returned to strip
  * annotations and other asides.
  */
-export default function getCodeElements(codeSelectors, { excludeSelectors = [] }) {
+export default function getCodeElements(codeSelectors, { excludeSelectors = [], excludeFilter = () => false }) {
     return [...document.querySelectorAll(codeSelectors.join(', '))]
         // Skip excluded and elements and those in informative content
-        .filter(el => !el.closest(excludeSelectors.join(', ')))
+        .filter(el => excludeSelectors.length === 0 || !el.closest(excludeSelectors.join(', ')))
+        .filter(el => !excludeFilter(el))
         .filter(el => !el.closest(informativeSelector))
 
         // Clone and clean the elements
diff --git a/test/extract-cddl.js b/test/extract-cddl.js
index 49f09c55..a4e9d840 100644
--- a/test/extract-cddl.js
+++ b/test/extract-cddl.js
@@ -53,6 +53,60 @@ typedef = tstr
   groupdef = tstr`
   },
 
+  {
+    title: 'excludes CDDL generated in the Bikeshed CDDL index section',
+    html: `
real = tstr
+ +

CDDL Index

+

Remote end definition

+
real = tstr
+

Local end definition

+
real = tstr
+ +

Next section

+
other = tstr
`, + res: `real = tstr + +other = tstr` + }, + + { + title: 'excludes CDDL in a container with the CDDL index ID', + html: `
real = tstr
+
+

CDDL Index

+
real = tstr
+
+
other = tstr
`, + res: `real = tstr + +other = tstr` + }, + + { + title: 'excludes CDDL in index subsections wrapped in section elements', + html: `
real = tstr
+
+

CDDL Index

+

Remote end definition

real = tstr
+
+

Next

other = tstr
`, + res: `real = tstr + +other = tstr` + }, + + { + title: 'does not exclude CDDL modules from an index without modules', + html: `
cddl = tstr
+

CDDL Index

+
cddl = tstr
`, + res: [ + { name: 'all', cddl: 'cddl = tstr' }, + { name: 'mod', cddl: 'cddl = tstr' } + ] + }, + { title: 'extracts CDDL module names from data-cddl-module', html: `
cddl = tstr
`,