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.
Files changed (43) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +158 -158
  3. package/index.js +11 -11
  4. package/package.json +53 -53
  5. package/reffy.js +248 -248
  6. package/src/browserlib/canonicalize-url.mjs +50 -50
  7. package/src/browserlib/create-outline.mjs +352 -352
  8. package/src/browserlib/extract-cssdfn.mjs +319 -319
  9. package/src/browserlib/extract-dfns.mjs +686 -686
  10. package/src/browserlib/extract-elements.mjs +205 -205
  11. package/src/browserlib/extract-headings.mjs +48 -48
  12. package/src/browserlib/extract-ids.mjs +28 -28
  13. package/src/browserlib/extract-links.mjs +28 -28
  14. package/src/browserlib/extract-references.mjs +203 -203
  15. package/src/browserlib/extract-webidl.mjs +134 -134
  16. package/src/browserlib/get-absolute-url.mjs +21 -21
  17. package/src/browserlib/get-generator.mjs +26 -26
  18. package/src/browserlib/get-lastmodified-date.mjs +13 -13
  19. package/src/browserlib/get-title.mjs +11 -11
  20. package/src/browserlib/informative-selector.mjs +16 -16
  21. package/src/browserlib/map-ids-to-headings.mjs +136 -136
  22. package/src/browserlib/reffy.json +53 -53
  23. package/src/cli/check-missing-dfns.js +609 -609
  24. package/src/cli/generate-idlnames.js +430 -430
  25. package/src/cli/generate-idlparsed.js +139 -139
  26. package/src/cli/merge-crawl-results.js +128 -128
  27. package/src/cli/parse-webidl.js +430 -430
  28. package/src/lib/css-grammar-parse-tree.schema.json +109 -109
  29. package/src/lib/css-grammar-parser.js +440 -440
  30. package/src/lib/fetch.js +56 -56
  31. package/src/lib/nock-server.js +127 -120
  32. package/src/lib/specs-crawler.js +622 -603
  33. package/src/lib/util.js +943 -898
  34. package/src/specs/missing-css-rules.json +197 -197
  35. package/src/specs/spec-equivalents.json +149 -149
  36. package/src/browserlib/extract-editors.mjs~ +0 -14
  37. package/src/browserlib/generate-es-dfn-report.sh~ +0 -4
  38. package/src/cli/csstree-grammar-check.js +0 -28
  39. package/src/cli/csstree-grammar-check.js~ +0 -10
  40. package/src/cli/csstree-grammar-parser.js +0 -11
  41. package/src/cli/csstree-grammar-parser.js~ +0 -1
  42. package/src/cli/extract-editors.js~ +0 -38
  43. package/src/cli/process-specs.js~ +0 -28
@@ -1,319 +1,319 @@
1
- import informativeSelector from './informative-selector.mjs';
2
-
3
- /**
4
- * Extract the list of CSS definitions in the current spec
5
- *
6
- * @function
7
- * @public
8
- * @return {Promise} The promise to get an extract of the CSS definitions, or
9
- * an empty CSS description object if the spec does not contain any CSS
10
- * definition. The return object will have properties named "properties",
11
- * "descriptors", and "valuespaces".
12
- */
13
- export default function () {
14
- let res = {
15
- properties: extractTableDfns(document, 'propdef', { unique: true }),
16
- descriptors: extractTableDfns(document, 'descdef', { unique: false }),
17
- valuespaces: extractValueSpaces(document)
18
- };
19
-
20
- // Try old recipes if we couldn't extract anything
21
- if ((Object.keys(res.properties).length === 0) &&
22
- (Object.keys(res.descriptors).length === 0)) {
23
- res.properties = extractDlDfns(document, 'propdef', { unique: true });
24
- res.descriptors = extractDlDfns(document, 'descdef', { unique: false });
25
- }
26
-
27
- return res;
28
- }
29
-
30
-
31
- /**
32
- * Converts a definition label as it appears in a CSS spec to a lower camel
33
- * case property name.
34
- *
35
- * @param {String} label Definition label
36
- * @return {String} lower camel case property name for the label
37
- */
38
- const dfnLabel2Property = label => label.trim()
39
- .replace(/:/, '')
40
- .split(' ')
41
- .map((str, idx) => (idx === 0) ?
42
- str.toLowerCase() :
43
- str.charAt(0).toUpperCase() + str.slice(1))
44
- .join('');
45
-
46
-
47
- /**
48
- * Extract a CSS definition from a table
49
- *
50
- * All recent CSS specs should follow that pattern
51
- */
52
- const extractTableDfn = table => {
53
- let res = {};
54
- const lines = [...table.querySelectorAll('tr')]
55
- .map(line => {
56
- const cleanedLine = line.cloneNode(true);
57
- const annotations = cleanedLine.querySelectorAll("aside, .mdn-anno");
58
- annotations.forEach(n => n.remove());
59
- return {
60
- name: dfnLabel2Property(cleanedLine.querySelector(':first-child').textContent),
61
- value: cleanedLine.querySelector('td:last-child').textContent.trim().replace(/\s+/g, ' ')
62
- };
63
- });
64
- for (let prop of lines) {
65
- res[prop.name] = prop.value;
66
- }
67
- return res;
68
- };
69
-
70
-
71
- /**
72
- * Extract a CSS definition from a dl list
73
- *
74
- * Used in "old" CSS specs
75
- */
76
- const extractDlDfn = dl => {
77
- let res = {};
78
- res.name = dl.querySelector('dt').textContent.replace(/'/g, '').trim();
79
- const lines = [...dl.querySelectorAll('dd table tr')]
80
- .map(line => Object.assign({
81
- name: dfnLabel2Property(line.querySelector(':first-child').textContent),
82
- value: line.querySelector('td:last-child').textContent.trim().replace(/\s+/g, ' ')
83
- }));
84
- for (let prop of lines) {
85
- res[prop.name] = prop.value;
86
- }
87
- return res;
88
- };
89
-
90
-
91
- /**
92
- * Merges CSS definitions for the same property into one
93
- *
94
- * The function runs a few simple sanity checks on the definitions. More checks
95
- * would be needed to fully validate that merging is fine.
96
- *
97
- * The function returns null if CSS definitions cannot be merged.
98
- */
99
- const mergeDfns = (dfn1, dfn2) => {
100
- // Definitions should be about the same property
101
- if (dfn1.name !== dfn2.name) {
102
- return null;
103
- }
104
-
105
- // There should never be two base definitions for the same CSS property
106
- if (dfn1.value && dfn2.value) {
107
- return null;
108
- }
109
-
110
- const [baseDfn, partialDfn] = dfn2.value ? [dfn2, dfn1] : [dfn1, dfn2];
111
- if ((!baseDfn.value && !baseDfn.newValues) ||
112
- !partialDfn.newValues ||
113
- (partialDfn.initial && partialDfn.initial !== baseDfn.initial)) {
114
- return null;
115
- }
116
-
117
- const merged = Object.assign(baseDfn);
118
- if (merged.value) {
119
- merged.value += ` | ${partialDfn.newValues}`;
120
- }
121
- else {
122
- merged.newValues += ` | ${partialDfn.newValues}`;
123
- }
124
-
125
- return merged;
126
- };
127
-
128
-
129
- /**
130
- * Extract CSS definitions in a spec using the given CSS selector and extractor
131
- */
132
- const extractDfns = (doc, selector, extractor, { unique } = { unique: true }) => {
133
- let res = {};
134
- [...doc.querySelectorAll(selector)]
135
- .filter(el => !el.closest(informativeSelector))
136
- .filter(el => !el.querySelector('ins, del'))
137
- .map(extractor)
138
- .filter(dfn => !!dfn.name)
139
- .map(dfn => dfn.name.split(',').map(name => Object.assign({},
140
- dfn, { name: name.trim() })))
141
- .reduce((acc, val) => acc.concat(val), [])
142
- .forEach(dfn => {
143
- if (res[dfn.name]) {
144
- if (unique) {
145
- const merged = mergeDfns(res[dfn.name], dfn);
146
- if (!merged) {
147
- throw new Error(`More than one dfn found for CSS property "${dfn.name}" and dfns cannot be merged`);
148
- }
149
- res[dfn.name] = merged;
150
- }
151
- else {
152
- res[dfn.name].push(dfn);
153
- }
154
- }
155
- else {
156
- res[dfn.name] = unique ? dfn : [dfn];
157
- }
158
- });
159
- return res;
160
- };
161
-
162
-
163
- /**
164
- * Extract CSS definitions in tables for the given class name
165
- * (typically one of `propdef` or `descdef`)
166
- */
167
- const extractTableDfns = (doc, className, options) =>
168
- extractDfns(doc, 'table.' + className + ':not(.attrdef)', extractTableDfn, options);
169
-
170
-
171
- /**
172
- * Extract CSS definitions in a dl list for the given class name
173
- * (typically one of `propdef` or `descdef`)
174
- */
175
- const extractDlDfns = (doc, className, options) =>
176
- extractDfns(doc, 'div.' + className + ' dl', extractDlDfn, options);
177
-
178
-
179
- /**
180
- * Extract value spaces (non-terminal values) defined in the specification
181
- */
182
- const extractValueSpaces = doc => {
183
- let res = {};
184
-
185
- const parseProductionRules = rules =>
186
- rules
187
- .map(val => val.split(/\n(?=[^\n]*\s?=\s)/m))
188
- .reduce((acc, val) => acc.concat(val), [])
189
- .map(line => line.split(/\s?=\s/).map(s => s.trim().replace(/\s+/g, ' ')))
190
- .filter(val => val[0].match(/^<.*>$|^.*\(\)$/))
191
- .filter(val => !!val[1])
192
- .forEach(val => {
193
- const name = val[0].replace(/^(.*\(\))$/, '<$1>')
194
- if (!(name in res)) {
195
- res[name] = { value: val[1] };
196
- }
197
- });
198
-
199
- // Extract non-terminal value spaces defined in `pre` tags
200
- // (remove note references as in:
201
- // https://drafts.csswg.org/css-syntax-3/#the-anb-type)
202
- parseProductionRules([...doc.querySelectorAll('pre.prod,span.prod')]
203
- .filter(el => !el.closest(informativeSelector))
204
- .map(el => {
205
- [...el.querySelectorAll('sup')]
206
- .map(sup => sup.parentNode.removeChild(sup));
207
- return el;
208
- })
209
- .map(el => el.textContent));
210
-
211
- // Complete with non-terminal value spaces defined in `pre` tags without
212
- // an explicit class, as in:
213
- // https://drafts.fxtf.org/compositing-1/#ltblendmodegt
214
- parseProductionRules([...doc.querySelectorAll('pre:not(.idl)')]
215
- .filter(el => !el.closest(informativeSelector))
216
- .filter(el => el.querySelector('dfn'))
217
- .map(el => el.textContent));
218
-
219
- // Complete with non-terminal value spaces defined in `dt` tags, as in:
220
- // https://drafts.csswg.org/css-shapes-1/#funcdef-inset
221
- // https://drafts.csswg.org/css-transforms/#funcdef-transform-matrix
222
- parseProductionRules([...doc.querySelectorAll('dt > dfn.css, dt > span.prod > dfn.css')]
223
- .filter(el => !el.closest(informativeSelector))
224
- .filter(el => el.parentNode.textContent.match(/\s?=\s/))
225
- .map(el => el.parentNode.textContent));
226
-
227
- // Complete with function values defined in `dt` tags where definition and
228
- // value are mixed together, as in:
229
- // https://drafts.csswg.org/css-overflow-4/#funcdef-text-overflow-fade
230
- parseProductionRules([...doc.querySelectorAll('dt > dfn.css')]
231
- .filter(el => !el.closest(informativeSelector))
232
- .filter(el => el.parentNode.textContent.trim().match(/^[a-zA-Z_][a-zA-Z0-9_\-]+\([^\)]*\)$/))
233
- .map(el => {
234
- let fn = el.parentNode.textContent.trim()
235
- .match(/^([a-zA-Z_][a-zA-Z0-9_\-]+)\([^\)]*\)$/)[1];
236
- return fn + '() = ' + el.parentNode.textContent;
237
- }));
238
-
239
-
240
- // Complete with non-terminal value spaces defined in `.definition`
241
- // paragraphs, as in:
242
- // https://svgwg.org/svg2-draft/painting.html#DataTypeDasharray
243
- parseProductionRules([...doc.querySelectorAll('.definition > dfn')]
244
- .filter(el => !el.closest(informativeSelector))
245
- .filter(el => el.parentNode.textContent.match(/\s?=\s/))
246
- .map(el => el.parentNode.textContent));
247
-
248
- // Complete with non-terminal value spaces defined in simple paragraphs,
249
- // as in:
250
- // https://drafts.csswg.org/css-animations-2/#typedef-single-animation-composition
251
- // https://drafts.csswg.org/css-transitions/#single-transition-property
252
- parseProductionRules([...doc.querySelectorAll('p > dfn, div.prod > dfn')]
253
- .filter(el => !el.closest(informativeSelector))
254
- .filter(el => el.parentNode.textContent.trim().match(/^<.*>\s?=\s/))
255
- .map(el => el.parentNode.textContent));
256
-
257
- // Complete with non-terminal value spaces defined in `dt` tags with
258
- // production rules (or prose) in `dd` tags, as in:
259
- // https://drafts.csswg.org/css-fonts/#absolute-size-value
260
- // https://drafts.csswg.org/css-content/#typedef-content-content-list
261
- [...doc.querySelectorAll('dt > dfn, dt > var')]
262
- .filter(el => !el.closest(informativeSelector))
263
- .filter(el => el.textContent.trim().match(/^<.*>$/))
264
- .filter(el => {
265
- let link = el.querySelector('a[href]');
266
- if (!link) {
267
- return true;
268
- }
269
- let href = (link ? link.getAttribute('href') : null);
270
- return (href === '#' + el.getAttribute('id'));
271
- })
272
- .map(el => {
273
- let dd = el.parentNode;
274
- while (dd && (dd.nodeName !== 'DD')) {
275
- dd = dd.nextSibling;
276
- }
277
- if (!dd) {
278
- return null;
279
- }
280
- let code = dd.querySelector('p > code, pre.prod');
281
- if (code) {
282
- return {
283
- name: el.textContent.trim(),
284
- value: code.textContent.trim().replace(/\s+/g, ' ')
285
- }
286
- }
287
- else {
288
- // Remove notes, details sections that link to tests, and subsections
289
- // that go too much into details
290
- dd = dd.cloneNode(true);
291
- [...dd.children].forEach(c => {
292
- if (c.tagName === 'DETAILS' ||
293
- c.tagName === 'DL' ||
294
- c.classList.contains('note')) {
295
- c.remove();
296
- }
297
- });
298
- return {
299
- name: el.textContent.trim(),
300
- prose: dd.textContent.trim().replace(/\s+/g, ' ')
301
- };
302
- }
303
- })
304
- .filter(space => !!space)
305
- .forEach(space => {
306
- if (!(space.name in res)) {
307
- res[space.name] = {};
308
- }
309
-
310
- if (!res[space.name].prose && space.prose) {
311
- res[space.name].prose = space.prose;
312
- }
313
- if (!res[space.name].value && space.value) {
314
- res[space.name].value = space.value;
315
- }
316
- });
317
-
318
- return res;
319
- }
1
+ import informativeSelector from './informative-selector.mjs';
2
+
3
+ /**
4
+ * Extract the list of CSS definitions in the current spec
5
+ *
6
+ * @function
7
+ * @public
8
+ * @return {Promise} The promise to get an extract of the CSS definitions, or
9
+ * an empty CSS description object if the spec does not contain any CSS
10
+ * definition. The return object will have properties named "properties",
11
+ * "descriptors", and "valuespaces".
12
+ */
13
+ export default function () {
14
+ let res = {
15
+ properties: extractTableDfns(document, 'propdef', { unique: true }),
16
+ descriptors: extractTableDfns(document, 'descdef', { unique: false }),
17
+ valuespaces: extractValueSpaces(document)
18
+ };
19
+
20
+ // Try old recipes if we couldn't extract anything
21
+ if ((Object.keys(res.properties).length === 0) &&
22
+ (Object.keys(res.descriptors).length === 0)) {
23
+ res.properties = extractDlDfns(document, 'propdef', { unique: true });
24
+ res.descriptors = extractDlDfns(document, 'descdef', { unique: false });
25
+ }
26
+
27
+ return res;
28
+ }
29
+
30
+
31
+ /**
32
+ * Converts a definition label as it appears in a CSS spec to a lower camel
33
+ * case property name.
34
+ *
35
+ * @param {String} label Definition label
36
+ * @return {String} lower camel case property name for the label
37
+ */
38
+ const dfnLabel2Property = label => label.trim()
39
+ .replace(/:/, '')
40
+ .split(' ')
41
+ .map((str, idx) => (idx === 0) ?
42
+ str.toLowerCase() :
43
+ str.charAt(0).toUpperCase() + str.slice(1))
44
+ .join('');
45
+
46
+
47
+ /**
48
+ * Extract a CSS definition from a table
49
+ *
50
+ * All recent CSS specs should follow that pattern
51
+ */
52
+ const extractTableDfn = table => {
53
+ let res = {};
54
+ const lines = [...table.querySelectorAll('tr')]
55
+ .map(line => {
56
+ const cleanedLine = line.cloneNode(true);
57
+ const annotations = cleanedLine.querySelectorAll("aside, .mdn-anno");
58
+ annotations.forEach(n => n.remove());
59
+ return {
60
+ name: dfnLabel2Property(cleanedLine.querySelector(':first-child').textContent),
61
+ value: cleanedLine.querySelector('td:last-child').textContent.trim().replace(/\s+/g, ' ')
62
+ };
63
+ });
64
+ for (let prop of lines) {
65
+ res[prop.name] = prop.value;
66
+ }
67
+ return res;
68
+ };
69
+
70
+
71
+ /**
72
+ * Extract a CSS definition from a dl list
73
+ *
74
+ * Used in "old" CSS specs
75
+ */
76
+ const extractDlDfn = dl => {
77
+ let res = {};
78
+ res.name = dl.querySelector('dt').textContent.replace(/'/g, '').trim();
79
+ const lines = [...dl.querySelectorAll('dd table tr')]
80
+ .map(line => Object.assign({
81
+ name: dfnLabel2Property(line.querySelector(':first-child').textContent),
82
+ value: line.querySelector('td:last-child').textContent.trim().replace(/\s+/g, ' ')
83
+ }));
84
+ for (let prop of lines) {
85
+ res[prop.name] = prop.value;
86
+ }
87
+ return res;
88
+ };
89
+
90
+
91
+ /**
92
+ * Merges CSS definitions for the same property into one
93
+ *
94
+ * The function runs a few simple sanity checks on the definitions. More checks
95
+ * would be needed to fully validate that merging is fine.
96
+ *
97
+ * The function returns null if CSS definitions cannot be merged.
98
+ */
99
+ const mergeDfns = (dfn1, dfn2) => {
100
+ // Definitions should be about the same property
101
+ if (dfn1.name !== dfn2.name) {
102
+ return null;
103
+ }
104
+
105
+ // There should never be two base definitions for the same CSS property
106
+ if (dfn1.value && dfn2.value) {
107
+ return null;
108
+ }
109
+
110
+ const [baseDfn, partialDfn] = dfn2.value ? [dfn2, dfn1] : [dfn1, dfn2];
111
+ if ((!baseDfn.value && !baseDfn.newValues) ||
112
+ !partialDfn.newValues ||
113
+ (partialDfn.initial && partialDfn.initial !== baseDfn.initial)) {
114
+ return null;
115
+ }
116
+
117
+ const merged = Object.assign(baseDfn);
118
+ if (merged.value) {
119
+ merged.value += ` | ${partialDfn.newValues}`;
120
+ }
121
+ else {
122
+ merged.newValues += ` | ${partialDfn.newValues}`;
123
+ }
124
+
125
+ return merged;
126
+ };
127
+
128
+
129
+ /**
130
+ * Extract CSS definitions in a spec using the given CSS selector and extractor
131
+ */
132
+ const extractDfns = (doc, selector, extractor, { unique } = { unique: true }) => {
133
+ let res = {};
134
+ [...doc.querySelectorAll(selector)]
135
+ .filter(el => !el.closest(informativeSelector))
136
+ .filter(el => !el.querySelector('ins, del'))
137
+ .map(extractor)
138
+ .filter(dfn => !!dfn.name)
139
+ .map(dfn => dfn.name.split(',').map(name => Object.assign({},
140
+ dfn, { name: name.trim() })))
141
+ .reduce((acc, val) => acc.concat(val), [])
142
+ .forEach(dfn => {
143
+ if (res[dfn.name]) {
144
+ if (unique) {
145
+ const merged = mergeDfns(res[dfn.name], dfn);
146
+ if (!merged) {
147
+ throw new Error(`More than one dfn found for CSS property "${dfn.name}" and dfns cannot be merged`);
148
+ }
149
+ res[dfn.name] = merged;
150
+ }
151
+ else {
152
+ res[dfn.name].push(dfn);
153
+ }
154
+ }
155
+ else {
156
+ res[dfn.name] = unique ? dfn : [dfn];
157
+ }
158
+ });
159
+ return res;
160
+ };
161
+
162
+
163
+ /**
164
+ * Extract CSS definitions in tables for the given class name
165
+ * (typically one of `propdef` or `descdef`)
166
+ */
167
+ const extractTableDfns = (doc, className, options) =>
168
+ extractDfns(doc, 'table.' + className + ':not(.attrdef)', extractTableDfn, options);
169
+
170
+
171
+ /**
172
+ * Extract CSS definitions in a dl list for the given class name
173
+ * (typically one of `propdef` or `descdef`)
174
+ */
175
+ const extractDlDfns = (doc, className, options) =>
176
+ extractDfns(doc, 'div.' + className + ' dl', extractDlDfn, options);
177
+
178
+
179
+ /**
180
+ * Extract value spaces (non-terminal values) defined in the specification
181
+ */
182
+ const extractValueSpaces = doc => {
183
+ let res = {};
184
+
185
+ const parseProductionRules = rules =>
186
+ rules
187
+ .map(val => val.split(/\n(?=[^\n]*\s?=\s)/m))
188
+ .reduce((acc, val) => acc.concat(val), [])
189
+ .map(line => line.split(/\s?=\s/).map(s => s.trim().replace(/\s+/g, ' ')))
190
+ .filter(val => val[0].match(/^<.*>$|^.*\(\)$/))
191
+ .filter(val => !!val[1])
192
+ .forEach(val => {
193
+ const name = val[0].replace(/^(.*\(\))$/, '<$1>')
194
+ if (!(name in res)) {
195
+ res[name] = { value: val[1] };
196
+ }
197
+ });
198
+
199
+ // Extract non-terminal value spaces defined in `pre` tags
200
+ // (remove note references as in:
201
+ // https://drafts.csswg.org/css-syntax-3/#the-anb-type)
202
+ parseProductionRules([...doc.querySelectorAll('pre.prod,span.prod')]
203
+ .filter(el => !el.closest(informativeSelector))
204
+ .map(el => {
205
+ [...el.querySelectorAll('sup')]
206
+ .map(sup => sup.parentNode.removeChild(sup));
207
+ return el;
208
+ })
209
+ .map(el => el.textContent));
210
+
211
+ // Complete with non-terminal value spaces defined in `pre` tags without
212
+ // an explicit class, as in:
213
+ // https://drafts.fxtf.org/compositing-1/#ltblendmodegt
214
+ parseProductionRules([...doc.querySelectorAll('pre:not(.idl)')]
215
+ .filter(el => !el.closest(informativeSelector))
216
+ .filter(el => el.querySelector('dfn'))
217
+ .map(el => el.textContent));
218
+
219
+ // Complete with non-terminal value spaces defined in `dt` tags, as in:
220
+ // https://drafts.csswg.org/css-shapes-1/#funcdef-inset
221
+ // https://drafts.csswg.org/css-transforms/#funcdef-transform-matrix
222
+ parseProductionRules([...doc.querySelectorAll('dt > dfn.css, dt > span.prod > dfn.css')]
223
+ .filter(el => !el.closest(informativeSelector))
224
+ .filter(el => el.parentNode.textContent.match(/\s?=\s/))
225
+ .map(el => el.parentNode.textContent));
226
+
227
+ // Complete with function values defined in `dt` tags where definition and
228
+ // value are mixed together, as in:
229
+ // https://drafts.csswg.org/css-overflow-4/#funcdef-text-overflow-fade
230
+ parseProductionRules([...doc.querySelectorAll('dt > dfn.css')]
231
+ .filter(el => !el.closest(informativeSelector))
232
+ .filter(el => el.parentNode.textContent.trim().match(/^[a-zA-Z_][a-zA-Z0-9_\-]+\([^\)]*\)$/))
233
+ .map(el => {
234
+ let fn = el.parentNode.textContent.trim()
235
+ .match(/^([a-zA-Z_][a-zA-Z0-9_\-]+)\([^\)]*\)$/)[1];
236
+ return fn + '() = ' + el.parentNode.textContent;
237
+ }));
238
+
239
+
240
+ // Complete with non-terminal value spaces defined in `.definition`
241
+ // paragraphs, as in:
242
+ // https://svgwg.org/svg2-draft/painting.html#DataTypeDasharray
243
+ parseProductionRules([...doc.querySelectorAll('.definition > dfn')]
244
+ .filter(el => !el.closest(informativeSelector))
245
+ .filter(el => el.parentNode.textContent.match(/\s?=\s/))
246
+ .map(el => el.parentNode.textContent));
247
+
248
+ // Complete with non-terminal value spaces defined in simple paragraphs,
249
+ // as in:
250
+ // https://drafts.csswg.org/css-animations-2/#typedef-single-animation-composition
251
+ // https://drafts.csswg.org/css-transitions/#single-transition-property
252
+ parseProductionRules([...doc.querySelectorAll('p > dfn, div.prod > dfn')]
253
+ .filter(el => !el.closest(informativeSelector))
254
+ .filter(el => el.parentNode.textContent.trim().match(/^<.*>\s?=\s/))
255
+ .map(el => el.parentNode.textContent));
256
+
257
+ // Complete with non-terminal value spaces defined in `dt` tags with
258
+ // production rules (or prose) in `dd` tags, as in:
259
+ // https://drafts.csswg.org/css-fonts/#absolute-size-value
260
+ // https://drafts.csswg.org/css-content/#typedef-content-content-list
261
+ [...doc.querySelectorAll('dt > dfn, dt > var')]
262
+ .filter(el => !el.closest(informativeSelector))
263
+ .filter(el => el.textContent.trim().match(/^<.*>$/))
264
+ .filter(el => {
265
+ let link = el.querySelector('a[href]');
266
+ if (!link) {
267
+ return true;
268
+ }
269
+ let href = (link ? link.getAttribute('href') : null);
270
+ return (href === '#' + el.getAttribute('id'));
271
+ })
272
+ .map(el => {
273
+ let dd = el.parentNode;
274
+ while (dd && (dd.nodeName !== 'DD')) {
275
+ dd = dd.nextSibling;
276
+ }
277
+ if (!dd) {
278
+ return null;
279
+ }
280
+ let code = dd.querySelector('p > code, pre.prod');
281
+ if (code) {
282
+ return {
283
+ name: el.textContent.trim(),
284
+ value: code.textContent.trim().replace(/\s+/g, ' ')
285
+ }
286
+ }
287
+ else {
288
+ // Remove notes, details sections that link to tests, and subsections
289
+ // that go too much into details
290
+ dd = dd.cloneNode(true);
291
+ [...dd.children].forEach(c => {
292
+ if (c.tagName === 'DETAILS' ||
293
+ c.tagName === 'DL' ||
294
+ c.classList.contains('note')) {
295
+ c.remove();
296
+ }
297
+ });
298
+ return {
299
+ name: el.textContent.trim(),
300
+ prose: dd.textContent.trim().replace(/\s+/g, ' ')
301
+ };
302
+ }
303
+ })
304
+ .filter(space => !!space)
305
+ .forEach(space => {
306
+ if (!(space.name in res)) {
307
+ res[space.name] = {};
308
+ }
309
+
310
+ if (!res[space.name].prose && space.prose) {
311
+ res[space.name].prose = space.prose;
312
+ }
313
+ if (!res[space.name].value && space.value) {
314
+ res[space.name].value = space.value;
315
+ }
316
+ });
317
+
318
+ return res;
319
+ }