reffy 6.2.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +158 -158
- package/index.js +11 -11
- package/package.json +53 -53
- package/reffy.js +248 -248
- package/src/browserlib/canonicalize-url.mjs +50 -50
- package/src/browserlib/create-outline.mjs +352 -352
- package/src/browserlib/extract-cssdfn.mjs +319 -319
- package/src/browserlib/extract-dfns.mjs +686 -686
- package/src/browserlib/extract-elements.mjs +205 -205
- package/src/browserlib/extract-headings.mjs +48 -48
- package/src/browserlib/extract-ids.mjs +28 -28
- package/src/browserlib/extract-links.mjs +28 -28
- package/src/browserlib/extract-references.mjs +203 -203
- package/src/browserlib/extract-webidl.mjs +134 -134
- package/src/browserlib/get-absolute-url.mjs +21 -21
- package/src/browserlib/get-generator.mjs +26 -26
- package/src/browserlib/get-lastmodified-date.mjs +13 -13
- package/src/browserlib/get-title.mjs +11 -11
- package/src/browserlib/informative-selector.mjs +16 -16
- package/src/browserlib/map-ids-to-headings.mjs +136 -136
- package/src/browserlib/reffy.json +53 -53
- package/src/cli/check-missing-dfns.js +609 -609
- package/src/cli/generate-idlnames.js +430 -430
- package/src/cli/generate-idlparsed.js +139 -139
- package/src/cli/merge-crawl-results.js +128 -128
- package/src/cli/parse-webidl.js +430 -430
- package/src/lib/css-grammar-parse-tree.schema.json +109 -109
- package/src/lib/css-grammar-parser.js +440 -440
- package/src/lib/fetch.js +56 -56
- package/src/lib/nock-server.js +127 -120
- package/src/lib/specs-crawler.js +622 -603
- package/src/lib/util.js +943 -898
- package/src/specs/missing-css-rules.json +197 -197
- package/src/specs/spec-equivalents.json +149 -149
- package/src/browserlib/extract-editors.mjs~ +0 -14
- package/src/browserlib/generate-es-dfn-report.sh~ +0 -4
- package/src/cli/csstree-grammar-check.js +0 -28
- package/src/cli/csstree-grammar-check.js~ +0 -10
- package/src/cli/csstree-grammar-parser.js +0 -11
- package/src/cli/csstree-grammar-parser.js~ +0 -1
- package/src/cli/extract-editors.js~ +0 -38
- package/src/cli/process-specs.js~ +0 -28
|
@@ -1,352 +1,352 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates an outline for the DOM subtree rooted at the given sectioning content
|
|
3
|
-
* or sectioning root element.
|
|
4
|
-
*
|
|
5
|
-
* This function implements the "creating an outline" algorithm in HTML:
|
|
6
|
-
* https://html.spec.whatwg.org/multipage/sections.html#outlines
|
|
7
|
-
*
|
|
8
|
-
* As a by-product of generating the outline, the function also generates a
|
|
9
|
-
* mapping between elements and the (conceptual) section that contains them in
|
|
10
|
-
* the outline. To save memory, this mapping is only done for elements that have
|
|
11
|
-
* an ID.
|
|
12
|
-
*
|
|
13
|
-
* Both the outline and the mapping are returned.
|
|
14
|
-
*/
|
|
15
|
-
export default function (root) {
|
|
16
|
-
const headingContent = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HGROUP'];
|
|
17
|
-
const sectioningContent = ['ARTICLE', 'ASIDE', 'NAV', 'SECTION'];
|
|
18
|
-
const sectioningRoot = ['BLOCKQUOTE', 'BODY', 'DETAILS', 'DIALOG', 'FIELDSET', 'FIGURE', 'TD'];
|
|
19
|
-
|
|
20
|
-
// A conceptual section has:
|
|
21
|
-
// - a heading element, which may be the string "__implied" when there is no
|
|
22
|
-
// real heading element
|
|
23
|
-
// - an explicit sectioning content element that gave birth to the section,
|
|
24
|
-
// unless the section was implicitly created through a heading element
|
|
25
|
-
// - a list of nested sections
|
|
26
|
-
// - a list of nested outlines, generated by sectioning root elements that
|
|
27
|
-
// this section may contain
|
|
28
|
-
function createSection() {
|
|
29
|
-
return {
|
|
30
|
-
heading: null,
|
|
31
|
-
root: null,
|
|
32
|
-
subSections: [],
|
|
33
|
-
subRoots: []
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function flattenSections(outline) {
|
|
38
|
-
return outline.concat(outline.flatMap(section =>
|
|
39
|
-
flattenSections(section.subSections)));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// 1. Let current outline target be null. (It holds the element whose outline
|
|
43
|
-
// is being created.)
|
|
44
|
-
let currentOutlineTarget = null;
|
|
45
|
-
|
|
46
|
-
// 2. Let current section be null. (It holds a pointer to a section, so that
|
|
47
|
-
// elements in the DOM can all be associated with a section.)
|
|
48
|
-
let currentSection = null;
|
|
49
|
-
|
|
50
|
-
// 3. Create a stack to hold elements, which is used to handle nesting.
|
|
51
|
-
// Initialize this stack to empty.
|
|
52
|
-
let stack = [];
|
|
53
|
-
|
|
54
|
-
let nodeToOutline = new Map();
|
|
55
|
-
let nodeToParentSection = new Map();
|
|
56
|
-
let nodeToSection = new Map();
|
|
57
|
-
|
|
58
|
-
// Compute the rank of the given node
|
|
59
|
-
function rank(node) {
|
|
60
|
-
switch (node.nodeName) {
|
|
61
|
-
case 'H1': return -1;
|
|
62
|
-
case 'H2': return -2;
|
|
63
|
-
case 'H3': return -3;
|
|
64
|
-
case 'H4': return -4;
|
|
65
|
-
case 'H5': return -5;
|
|
66
|
-
case 'H6': return -6;
|
|
67
|
-
case 'HGROUP': return Math.max(...[...node.childNodes].map(rank));
|
|
68
|
-
default: return -100;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Process node when walk enters it
|
|
73
|
-
function enter(node) {
|
|
74
|
-
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// If the top of the stack is a heading content element or an element with a
|
|
79
|
-
// hidden attribute, do nothing.
|
|
80
|
-
const topOfStack = (stack.length > 0) ? stack[stack.length - 1] : null;
|
|
81
|
-
if (topOfStack &&
|
|
82
|
-
(headingContent.includes(topOfStack.nodeName) ||
|
|
83
|
-
topOfStack.hasAttribute('hidden'))) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// When entering an element with a hidden attribute, push the element being
|
|
88
|
-
// entered onto the stack (This causes the algorithm to skip that element
|
|
89
|
-
// and any descendants of the element).
|
|
90
|
-
if (node.hasAttribute('hidden')) {
|
|
91
|
-
stack.push(node);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// When entering a sectioning content element
|
|
96
|
-
if (sectioningContent.includes(node.nodeName)) {
|
|
97
|
-
// 1. If current outline target is not null, then:
|
|
98
|
-
if (currentOutlineTarget) {
|
|
99
|
-
// 1.1 If the current section has no heading, create an implied heading and
|
|
100
|
-
// let that be the heading for the current section.
|
|
101
|
-
if (!currentSection.heading) {
|
|
102
|
-
currentSection.heading = '__implied';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 1.2 Push current outline target onto the stack.
|
|
106
|
-
stack.push(currentOutlineTarget);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 2. Let current outline target be the element that is being entered.
|
|
110
|
-
currentOutlineTarget = node;
|
|
111
|
-
|
|
112
|
-
// 3. Let current section be a newly created section for the current
|
|
113
|
-
// outline target element.
|
|
114
|
-
currentSection = createSection();
|
|
115
|
-
currentSection.root = currentOutlineTarget;
|
|
116
|
-
|
|
117
|
-
// 4. Associate current outline target with current section.
|
|
118
|
-
nodeToSection.set(currentOutlineTarget, currentSection);
|
|
119
|
-
|
|
120
|
-
// 5. Let there be a new outline for the new current outline target,
|
|
121
|
-
// initialized with just the new current section as the only section in
|
|
122
|
-
// the outline.
|
|
123
|
-
nodeToOutline.set(currentOutlineTarget, [currentSection]);
|
|
124
|
-
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// When entering a sectioning root element
|
|
129
|
-
if (sectioningRoot.includes(node.nodeName)) {
|
|
130
|
-
// 1. If current outline target is not null, push current outline target
|
|
131
|
-
// onto the stack.
|
|
132
|
-
if (currentOutlineTarget) {
|
|
133
|
-
stack.push(currentOutlineTarget);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// 2. Let current outline target be the element that is being entered.
|
|
137
|
-
currentOutlineTarget = node;
|
|
138
|
-
|
|
139
|
-
// 3. Let current outline target's parent section be current section.
|
|
140
|
-
nodeToParentSection.set(currentOutlineTarget, currentSection);
|
|
141
|
-
|
|
142
|
-
// 4. Let current section be a newly created section for the current
|
|
143
|
-
// outline target element.
|
|
144
|
-
currentSection = createSection();
|
|
145
|
-
currentSection.root = currentOutlineTarget;
|
|
146
|
-
|
|
147
|
-
// 5. Let there be a new outline for the new current outline target,
|
|
148
|
-
// initialized with just the new current section as the only section in
|
|
149
|
-
// the outline.
|
|
150
|
-
nodeToOutline.set(currentOutlineTarget, [currentSection]);
|
|
151
|
-
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// When entering a heading content element
|
|
156
|
-
if (headingContent.includes(node.nodeName)) {
|
|
157
|
-
const outline = nodeToOutline.get(currentOutlineTarget);
|
|
158
|
-
const lastSection = outline[outline.length - 1];
|
|
159
|
-
|
|
160
|
-
// If the current section has no heading, let the element being entered be
|
|
161
|
-
// the heading for the current section.
|
|
162
|
-
if (!currentSection.heading) {
|
|
163
|
-
currentSection.heading = node;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Otherwise, if the element being entered has a rank equal to or higher
|
|
167
|
-
// than the heading of the last section of the outline of the current
|
|
168
|
-
// outline target, or if the heading of the last section of the outline of
|
|
169
|
-
// the current outline target is an implied heading, then create a new
|
|
170
|
-
// section and append it to the outline of the current outline target
|
|
171
|
-
// element, so that this new section is the new last section of that
|
|
172
|
-
// outline. Let current section be that new section. Let the element being
|
|
173
|
-
// entered be the new heading for the current section.
|
|
174
|
-
else if ((lastSection.heading === '__implied') ||
|
|
175
|
-
(rank(node) >= rank(lastSection.heading))) {
|
|
176
|
-
currentSection = createSection();
|
|
177
|
-
currentSection.heading = node;
|
|
178
|
-
outline.push(currentSection);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Otherwise, run these substeps:
|
|
182
|
-
else {
|
|
183
|
-
// 1. Let candidate section be current section.
|
|
184
|
-
let candidateSection = currentSection;
|
|
185
|
-
while (candidateSection) {
|
|
186
|
-
// 2. Heading loop: If the element being entered has a rank lower than
|
|
187
|
-
// the rank of the heading of the candidate section, then create a new
|
|
188
|
-
// section, and append it to candidate section. (This does not change
|
|
189
|
-
// which section is the last section in the outline.) Let current
|
|
190
|
-
// section be this new section. Let the element being entered be the
|
|
191
|
-
// new heading for the current section. Abort these substeps.
|
|
192
|
-
if (rank(node) < rank(candidateSection.heading)) {
|
|
193
|
-
currentSection = createSection();
|
|
194
|
-
currentSection.heading = node;
|
|
195
|
-
candidateSection.subSections.push(currentSection);
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 3. Let new candidate section be the section that contains candidate
|
|
200
|
-
// section in the outline of current outline target.
|
|
201
|
-
const sections = flattenSections(nodeToOutline.get(currentOutlineTarget));
|
|
202
|
-
let newCandidateSection = sections.find(section =>
|
|
203
|
-
section.subSections.includes(candidateSection));
|
|
204
|
-
|
|
205
|
-
// 4. Let candidate section be new candidate section.
|
|
206
|
-
candidateSection = newCandidateSection;
|
|
207
|
-
|
|
208
|
-
// 5. Return to the step labeled heading loop.
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Push the element being entered onto the stack. (This causes the
|
|
212
|
-
// algorithm to skip any descendants of the element.)
|
|
213
|
-
stack.push(node);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Process node when walk exits it
|
|
220
|
-
function exit(node) {
|
|
221
|
-
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function innerExit() {
|
|
226
|
-
const topOfStack = (stack.length > 0) ? stack[stack.length - 1] : null;
|
|
227
|
-
|
|
228
|
-
// When exiting an element, if that element is the element at the top of
|
|
229
|
-
// the stack, pop that element from the stack.
|
|
230
|
-
if (topOfStack === node) {
|
|
231
|
-
stack.pop();
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// If the top of the stack is a heading content element or an element with
|
|
236
|
-
// a hidden attribute, do nothing.
|
|
237
|
-
if (topOfStack &&
|
|
238
|
-
(headingContent.includes(topOfStack.nodeName) ||
|
|
239
|
-
topOfStack.hasAttribute('hidden'))) {
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// When exiting a sectioning content element, if the stack is not empty
|
|
244
|
-
if (sectioningContent.includes(node.nodeName) && (stack.length > 0)) {
|
|
245
|
-
// 1. If the current section has no heading, create an implied heading
|
|
246
|
-
// and let that be the heading for the current section.
|
|
247
|
-
if (!currentSection.heading) {
|
|
248
|
-
currentSection.heading = '__implied';
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// 2. Pop the top element from the stack, and let the current outline
|
|
252
|
-
// target be that element.
|
|
253
|
-
currentOutlineTarget = stack.pop();
|
|
254
|
-
|
|
255
|
-
// 3. Let current section be the last section in the outline of the
|
|
256
|
-
// current outline target element.
|
|
257
|
-
let outline = nodeToOutline.get(currentOutlineTarget);
|
|
258
|
-
currentSection = outline[outline.length - 1];
|
|
259
|
-
|
|
260
|
-
// 4. Append the outline of the sectioning content element being exited
|
|
261
|
-
// to the current section. (This does not change which section is the
|
|
262
|
-
// last section in the outline)
|
|
263
|
-
currentSection.subSections.push(...nodeToOutline.get(node));
|
|
264
|
-
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// When exiting a sectioning root element, if the stack is not empty
|
|
269
|
-
if (sectioningRoot.includes(node.nodeName) && (stack.length > 0)) {
|
|
270
|
-
// 1. If the current section has no heading, create an implied heading
|
|
271
|
-
// and let that be the heading for the current section.
|
|
272
|
-
if (!currentSection.heading) {
|
|
273
|
-
currentSection.heading = '__implied';
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// 2. Let current section be current outline target's parent section.
|
|
277
|
-
currentSection = nodeToParentSection.get(currentOutlineTarget);
|
|
278
|
-
|
|
279
|
-
// A sectioning root generates a separate outline, let's attach it
|
|
280
|
-
// to the main outline
|
|
281
|
-
currentSection.subRoots.push(...nodeToOutline.get(node));
|
|
282
|
-
|
|
283
|
-
// 3. Pop the top element from the stack, and let the current outline
|
|
284
|
-
// target be that element.
|
|
285
|
-
currentOutlineTarget = stack.pop();
|
|
286
|
-
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// When exiting a sectioning content element or a sectioning root element
|
|
291
|
-
// (when the stack is empty)
|
|
292
|
-
if (sectioningContent.includes(node.nodeName) ||
|
|
293
|
-
sectioningRoot.includes(node.nodeName)) {
|
|
294
|
-
// If the current section has no heading, create an implied heading and
|
|
295
|
-
// let that be the heading for the current section.
|
|
296
|
-
if (!currentSection.heading) {
|
|
297
|
-
currentSection.heading = '__implied';
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Skip to the next step in the overall set of steps. (The walk is over)
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
innerExit();
|
|
306
|
-
|
|
307
|
-
// In addition, whenever the walk exits a node, after doing the steps above,
|
|
308
|
-
// if the node is not associated with a section yet, associate the node with
|
|
309
|
-
// the section current section.
|
|
310
|
-
// (we will only do that for elements that have an ID)
|
|
311
|
-
if (node.getAttribute('id') && !nodeToSection.has(node)) {
|
|
312
|
-
nodeToSection.set(node, currentSection);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Walk the DOM subtree in depth-first order, entering and exiting nodes.
|
|
317
|
-
function walk(root, enter, exit) {
|
|
318
|
-
let node = root;
|
|
319
|
-
start: while (node) {
|
|
320
|
-
enter(node);
|
|
321
|
-
// Note: HGROUP is composed of multiple sub-headings but represents a
|
|
322
|
-
// single heading, skip its children as that would create ghost
|
|
323
|
-
// subsections
|
|
324
|
-
if ((node.nodeName !== 'HGROUP') && node.firstChild) {
|
|
325
|
-
node = node.firstChild;
|
|
326
|
-
continue start;
|
|
327
|
-
}
|
|
328
|
-
while (node) {
|
|
329
|
-
exit(node);
|
|
330
|
-
if (node == root) {
|
|
331
|
-
node = null;
|
|
332
|
-
} else if (node.nextSibling) {
|
|
333
|
-
node = node.nextSibling;
|
|
334
|
-
continue start;
|
|
335
|
-
} else {
|
|
336
|
-
node = node.parentNode;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// 4. Walk over the DOM in tree order, starting with the sectioning content
|
|
343
|
-
// element or sectioning root element at the root of the subtree for which an
|
|
344
|
-
// outline is to be created, and trigger the first relevant step below for
|
|
345
|
-
// each element as the walk enters and exits it.
|
|
346
|
-
walk(root, enter, exit);
|
|
347
|
-
|
|
348
|
-
return {
|
|
349
|
-
outline: nodeToOutline.get(root),
|
|
350
|
-
nodeToSection
|
|
351
|
-
};
|
|
352
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Creates an outline for the DOM subtree rooted at the given sectioning content
|
|
3
|
+
* or sectioning root element.
|
|
4
|
+
*
|
|
5
|
+
* This function implements the "creating an outline" algorithm in HTML:
|
|
6
|
+
* https://html.spec.whatwg.org/multipage/sections.html#outlines
|
|
7
|
+
*
|
|
8
|
+
* As a by-product of generating the outline, the function also generates a
|
|
9
|
+
* mapping between elements and the (conceptual) section that contains them in
|
|
10
|
+
* the outline. To save memory, this mapping is only done for elements that have
|
|
11
|
+
* an ID.
|
|
12
|
+
*
|
|
13
|
+
* Both the outline and the mapping are returned.
|
|
14
|
+
*/
|
|
15
|
+
export default function (root) {
|
|
16
|
+
const headingContent = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HGROUP'];
|
|
17
|
+
const sectioningContent = ['ARTICLE', 'ASIDE', 'NAV', 'SECTION'];
|
|
18
|
+
const sectioningRoot = ['BLOCKQUOTE', 'BODY', 'DETAILS', 'DIALOG', 'FIELDSET', 'FIGURE', 'TD'];
|
|
19
|
+
|
|
20
|
+
// A conceptual section has:
|
|
21
|
+
// - a heading element, which may be the string "__implied" when there is no
|
|
22
|
+
// real heading element
|
|
23
|
+
// - an explicit sectioning content element that gave birth to the section,
|
|
24
|
+
// unless the section was implicitly created through a heading element
|
|
25
|
+
// - a list of nested sections
|
|
26
|
+
// - a list of nested outlines, generated by sectioning root elements that
|
|
27
|
+
// this section may contain
|
|
28
|
+
function createSection() {
|
|
29
|
+
return {
|
|
30
|
+
heading: null,
|
|
31
|
+
root: null,
|
|
32
|
+
subSections: [],
|
|
33
|
+
subRoots: []
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function flattenSections(outline) {
|
|
38
|
+
return outline.concat(outline.flatMap(section =>
|
|
39
|
+
flattenSections(section.subSections)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 1. Let current outline target be null. (It holds the element whose outline
|
|
43
|
+
// is being created.)
|
|
44
|
+
let currentOutlineTarget = null;
|
|
45
|
+
|
|
46
|
+
// 2. Let current section be null. (It holds a pointer to a section, so that
|
|
47
|
+
// elements in the DOM can all be associated with a section.)
|
|
48
|
+
let currentSection = null;
|
|
49
|
+
|
|
50
|
+
// 3. Create a stack to hold elements, which is used to handle nesting.
|
|
51
|
+
// Initialize this stack to empty.
|
|
52
|
+
let stack = [];
|
|
53
|
+
|
|
54
|
+
let nodeToOutline = new Map();
|
|
55
|
+
let nodeToParentSection = new Map();
|
|
56
|
+
let nodeToSection = new Map();
|
|
57
|
+
|
|
58
|
+
// Compute the rank of the given node
|
|
59
|
+
function rank(node) {
|
|
60
|
+
switch (node.nodeName) {
|
|
61
|
+
case 'H1': return -1;
|
|
62
|
+
case 'H2': return -2;
|
|
63
|
+
case 'H3': return -3;
|
|
64
|
+
case 'H4': return -4;
|
|
65
|
+
case 'H5': return -5;
|
|
66
|
+
case 'H6': return -6;
|
|
67
|
+
case 'HGROUP': return Math.max(...[...node.childNodes].map(rank));
|
|
68
|
+
default: return -100;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Process node when walk enters it
|
|
73
|
+
function enter(node) {
|
|
74
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// If the top of the stack is a heading content element or an element with a
|
|
79
|
+
// hidden attribute, do nothing.
|
|
80
|
+
const topOfStack = (stack.length > 0) ? stack[stack.length - 1] : null;
|
|
81
|
+
if (topOfStack &&
|
|
82
|
+
(headingContent.includes(topOfStack.nodeName) ||
|
|
83
|
+
topOfStack.hasAttribute('hidden'))) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// When entering an element with a hidden attribute, push the element being
|
|
88
|
+
// entered onto the stack (This causes the algorithm to skip that element
|
|
89
|
+
// and any descendants of the element).
|
|
90
|
+
if (node.hasAttribute('hidden')) {
|
|
91
|
+
stack.push(node);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// When entering a sectioning content element
|
|
96
|
+
if (sectioningContent.includes(node.nodeName)) {
|
|
97
|
+
// 1. If current outline target is not null, then:
|
|
98
|
+
if (currentOutlineTarget) {
|
|
99
|
+
// 1.1 If the current section has no heading, create an implied heading and
|
|
100
|
+
// let that be the heading for the current section.
|
|
101
|
+
if (!currentSection.heading) {
|
|
102
|
+
currentSection.heading = '__implied';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 1.2 Push current outline target onto the stack.
|
|
106
|
+
stack.push(currentOutlineTarget);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 2. Let current outline target be the element that is being entered.
|
|
110
|
+
currentOutlineTarget = node;
|
|
111
|
+
|
|
112
|
+
// 3. Let current section be a newly created section for the current
|
|
113
|
+
// outline target element.
|
|
114
|
+
currentSection = createSection();
|
|
115
|
+
currentSection.root = currentOutlineTarget;
|
|
116
|
+
|
|
117
|
+
// 4. Associate current outline target with current section.
|
|
118
|
+
nodeToSection.set(currentOutlineTarget, currentSection);
|
|
119
|
+
|
|
120
|
+
// 5. Let there be a new outline for the new current outline target,
|
|
121
|
+
// initialized with just the new current section as the only section in
|
|
122
|
+
// the outline.
|
|
123
|
+
nodeToOutline.set(currentOutlineTarget, [currentSection]);
|
|
124
|
+
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// When entering a sectioning root element
|
|
129
|
+
if (sectioningRoot.includes(node.nodeName)) {
|
|
130
|
+
// 1. If current outline target is not null, push current outline target
|
|
131
|
+
// onto the stack.
|
|
132
|
+
if (currentOutlineTarget) {
|
|
133
|
+
stack.push(currentOutlineTarget);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 2. Let current outline target be the element that is being entered.
|
|
137
|
+
currentOutlineTarget = node;
|
|
138
|
+
|
|
139
|
+
// 3. Let current outline target's parent section be current section.
|
|
140
|
+
nodeToParentSection.set(currentOutlineTarget, currentSection);
|
|
141
|
+
|
|
142
|
+
// 4. Let current section be a newly created section for the current
|
|
143
|
+
// outline target element.
|
|
144
|
+
currentSection = createSection();
|
|
145
|
+
currentSection.root = currentOutlineTarget;
|
|
146
|
+
|
|
147
|
+
// 5. Let there be a new outline for the new current outline target,
|
|
148
|
+
// initialized with just the new current section as the only section in
|
|
149
|
+
// the outline.
|
|
150
|
+
nodeToOutline.set(currentOutlineTarget, [currentSection]);
|
|
151
|
+
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// When entering a heading content element
|
|
156
|
+
if (headingContent.includes(node.nodeName)) {
|
|
157
|
+
const outline = nodeToOutline.get(currentOutlineTarget);
|
|
158
|
+
const lastSection = outline[outline.length - 1];
|
|
159
|
+
|
|
160
|
+
// If the current section has no heading, let the element being entered be
|
|
161
|
+
// the heading for the current section.
|
|
162
|
+
if (!currentSection.heading) {
|
|
163
|
+
currentSection.heading = node;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Otherwise, if the element being entered has a rank equal to or higher
|
|
167
|
+
// than the heading of the last section of the outline of the current
|
|
168
|
+
// outline target, or if the heading of the last section of the outline of
|
|
169
|
+
// the current outline target is an implied heading, then create a new
|
|
170
|
+
// section and append it to the outline of the current outline target
|
|
171
|
+
// element, so that this new section is the new last section of that
|
|
172
|
+
// outline. Let current section be that new section. Let the element being
|
|
173
|
+
// entered be the new heading for the current section.
|
|
174
|
+
else if ((lastSection.heading === '__implied') ||
|
|
175
|
+
(rank(node) >= rank(lastSection.heading))) {
|
|
176
|
+
currentSection = createSection();
|
|
177
|
+
currentSection.heading = node;
|
|
178
|
+
outline.push(currentSection);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Otherwise, run these substeps:
|
|
182
|
+
else {
|
|
183
|
+
// 1. Let candidate section be current section.
|
|
184
|
+
let candidateSection = currentSection;
|
|
185
|
+
while (candidateSection) {
|
|
186
|
+
// 2. Heading loop: If the element being entered has a rank lower than
|
|
187
|
+
// the rank of the heading of the candidate section, then create a new
|
|
188
|
+
// section, and append it to candidate section. (This does not change
|
|
189
|
+
// which section is the last section in the outline.) Let current
|
|
190
|
+
// section be this new section. Let the element being entered be the
|
|
191
|
+
// new heading for the current section. Abort these substeps.
|
|
192
|
+
if (rank(node) < rank(candidateSection.heading)) {
|
|
193
|
+
currentSection = createSection();
|
|
194
|
+
currentSection.heading = node;
|
|
195
|
+
candidateSection.subSections.push(currentSection);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 3. Let new candidate section be the section that contains candidate
|
|
200
|
+
// section in the outline of current outline target.
|
|
201
|
+
const sections = flattenSections(nodeToOutline.get(currentOutlineTarget));
|
|
202
|
+
let newCandidateSection = sections.find(section =>
|
|
203
|
+
section.subSections.includes(candidateSection));
|
|
204
|
+
|
|
205
|
+
// 4. Let candidate section be new candidate section.
|
|
206
|
+
candidateSection = newCandidateSection;
|
|
207
|
+
|
|
208
|
+
// 5. Return to the step labeled heading loop.
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Push the element being entered onto the stack. (This causes the
|
|
212
|
+
// algorithm to skip any descendants of the element.)
|
|
213
|
+
stack.push(node);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Process node when walk exits it
|
|
220
|
+
function exit(node) {
|
|
221
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function innerExit() {
|
|
226
|
+
const topOfStack = (stack.length > 0) ? stack[stack.length - 1] : null;
|
|
227
|
+
|
|
228
|
+
// When exiting an element, if that element is the element at the top of
|
|
229
|
+
// the stack, pop that element from the stack.
|
|
230
|
+
if (topOfStack === node) {
|
|
231
|
+
stack.pop();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// If the top of the stack is a heading content element or an element with
|
|
236
|
+
// a hidden attribute, do nothing.
|
|
237
|
+
if (topOfStack &&
|
|
238
|
+
(headingContent.includes(topOfStack.nodeName) ||
|
|
239
|
+
topOfStack.hasAttribute('hidden'))) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// When exiting a sectioning content element, if the stack is not empty
|
|
244
|
+
if (sectioningContent.includes(node.nodeName) && (stack.length > 0)) {
|
|
245
|
+
// 1. If the current section has no heading, create an implied heading
|
|
246
|
+
// and let that be the heading for the current section.
|
|
247
|
+
if (!currentSection.heading) {
|
|
248
|
+
currentSection.heading = '__implied';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 2. Pop the top element from the stack, and let the current outline
|
|
252
|
+
// target be that element.
|
|
253
|
+
currentOutlineTarget = stack.pop();
|
|
254
|
+
|
|
255
|
+
// 3. Let current section be the last section in the outline of the
|
|
256
|
+
// current outline target element.
|
|
257
|
+
let outline = nodeToOutline.get(currentOutlineTarget);
|
|
258
|
+
currentSection = outline[outline.length - 1];
|
|
259
|
+
|
|
260
|
+
// 4. Append the outline of the sectioning content element being exited
|
|
261
|
+
// to the current section. (This does not change which section is the
|
|
262
|
+
// last section in the outline)
|
|
263
|
+
currentSection.subSections.push(...nodeToOutline.get(node));
|
|
264
|
+
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// When exiting a sectioning root element, if the stack is not empty
|
|
269
|
+
if (sectioningRoot.includes(node.nodeName) && (stack.length > 0)) {
|
|
270
|
+
// 1. If the current section has no heading, create an implied heading
|
|
271
|
+
// and let that be the heading for the current section.
|
|
272
|
+
if (!currentSection.heading) {
|
|
273
|
+
currentSection.heading = '__implied';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 2. Let current section be current outline target's parent section.
|
|
277
|
+
currentSection = nodeToParentSection.get(currentOutlineTarget);
|
|
278
|
+
|
|
279
|
+
// A sectioning root generates a separate outline, let's attach it
|
|
280
|
+
// to the main outline
|
|
281
|
+
currentSection.subRoots.push(...nodeToOutline.get(node));
|
|
282
|
+
|
|
283
|
+
// 3. Pop the top element from the stack, and let the current outline
|
|
284
|
+
// target be that element.
|
|
285
|
+
currentOutlineTarget = stack.pop();
|
|
286
|
+
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// When exiting a sectioning content element or a sectioning root element
|
|
291
|
+
// (when the stack is empty)
|
|
292
|
+
if (sectioningContent.includes(node.nodeName) ||
|
|
293
|
+
sectioningRoot.includes(node.nodeName)) {
|
|
294
|
+
// If the current section has no heading, create an implied heading and
|
|
295
|
+
// let that be the heading for the current section.
|
|
296
|
+
if (!currentSection.heading) {
|
|
297
|
+
currentSection.heading = '__implied';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Skip to the next step in the overall set of steps. (The walk is over)
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
innerExit();
|
|
306
|
+
|
|
307
|
+
// In addition, whenever the walk exits a node, after doing the steps above,
|
|
308
|
+
// if the node is not associated with a section yet, associate the node with
|
|
309
|
+
// the section current section.
|
|
310
|
+
// (we will only do that for elements that have an ID)
|
|
311
|
+
if (node.getAttribute('id') && !nodeToSection.has(node)) {
|
|
312
|
+
nodeToSection.set(node, currentSection);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Walk the DOM subtree in depth-first order, entering and exiting nodes.
|
|
317
|
+
function walk(root, enter, exit) {
|
|
318
|
+
let node = root;
|
|
319
|
+
start: while (node) {
|
|
320
|
+
enter(node);
|
|
321
|
+
// Note: HGROUP is composed of multiple sub-headings but represents a
|
|
322
|
+
// single heading, skip its children as that would create ghost
|
|
323
|
+
// subsections
|
|
324
|
+
if ((node.nodeName !== 'HGROUP') && node.firstChild) {
|
|
325
|
+
node = node.firstChild;
|
|
326
|
+
continue start;
|
|
327
|
+
}
|
|
328
|
+
while (node) {
|
|
329
|
+
exit(node);
|
|
330
|
+
if (node == root) {
|
|
331
|
+
node = null;
|
|
332
|
+
} else if (node.nextSibling) {
|
|
333
|
+
node = node.nextSibling;
|
|
334
|
+
continue start;
|
|
335
|
+
} else {
|
|
336
|
+
node = node.parentNode;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 4. Walk over the DOM in tree order, starting with the sectioning content
|
|
343
|
+
// element or sectioning root element at the root of the subtree for which an
|
|
344
|
+
// outline is to be created, and trigger the first relevant step below for
|
|
345
|
+
// each element as the walk enters and exits it.
|
|
346
|
+
walk(root, enter, exit);
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
outline: nodeToOutline.get(root),
|
|
350
|
+
nodeToSection
|
|
351
|
+
};
|
|
352
|
+
}
|