Skip to content

Commit

Permalink
CSS extracts: Link entries back to spec anchor (#1468)
Browse files Browse the repository at this point in the history
Requested in w3c/webref#642

It was possible to link entries in CSS extracts back to entries in the dfns
extract with a bit of logic but:
1. This requires having both CSS extracts and dfns extracts at hand, and the
dfns extracts typically aren't included in the `@webref/idl` package.
2. This is somewhat error-prone.

This updates the CSS definitions extraction logic to also extract the absolute
URL (with fragment) of the definition whenever possible. A slight change of
approach was needed as previous extraction logic handled "multi-definitions"
(e.g., a table definition that defines multiple properties at once) through a
text-based approach, and the code now also needs to track the IDs.

Tests already contained IDs, so this update merely refreshes the expected
results.
  • Loading branch information
tidoust authored Jan 29, 2024
1 parent a0afae7 commit dead276
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 39 deletions.
5 changes: 5 additions & 0 deletions schemas/browserlib/extract-cssdfn.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"required": ["name"],
"properties": {
"name": { "$ref": "../common.json#/$defs/cssPropertyName" },
"href": { "$ref": "../common.json#/$defs/url" },
"value": { "$ref": "../common.json#/$defs/cssValue" },
"newValues": { "$ref": "../common.json#/$defs/cssValue" },
"values": { "$ref": "../common.json#/$defs/cssValues" },
Expand All @@ -34,6 +35,7 @@
"additionalProperties": false,
"properties": {
"name": { "type": "string", "pattern": "^@" },
"href": { "$ref": "../common.json#/$defs/url" },
"value": { "$ref": "../common.json#/$defs/cssValue" },
"prose": { "type": "string" },
"descriptors": {
Expand All @@ -45,6 +47,7 @@
"properties": {
"name": { "type": "string" },
"for": { "type": "string" },
"href": { "$ref": "../common.json#/$defs/url" },
"value": { "$ref": "../common.json#/$defs/cssValue" },
"values": { "$ref": "../common.json#/$defs/cssValues" }
}
Expand All @@ -63,6 +66,7 @@
"additionalProperties": false,
"properties": {
"name": { "$ref": "../common.json#/$defs/cssPropertyName" },
"href": { "$ref": "../common.json#/$defs/url" },
"prose": { "type": "string" },
"value": { "$ref": "../common.json#/$defs/cssValue" },
"values": { "$ref": "../common.json#/$defs/cssValues" }
Expand All @@ -78,6 +82,7 @@
"additionalProperties": false,
"properties": {
"name": { "type": "string", "pattern": "^<[^>]+>$|^.*()$" },
"href": { "$ref": "../common.json#/$defs/url" },
"type": { "type": "string", "enum": ["type", "function"] },
"prose": { "type": "string" },
"value": { "$ref": "../common.json#/$defs/cssValue" },
Expand Down
1 change: 1 addition & 0 deletions schemas/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"properties": {
"name": { "$ref": "#/$defs/cssValue" },
"type": { "type": "string", "enum": ["type", "function", "value", "selector"] },
"href": { "$ref": "#/$defs/url" },
"prose": { "type": "string" },
"value": { "$ref": "#/$defs/cssValue" },
"values": { "$ref": "#/$defs/cssValues" }
Expand Down
124 changes: 89 additions & 35 deletions src/browserlib/extract-cssdfn.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import informativeSelector from './informative-selector.mjs';
import getAbsoluteUrl from './get-absolute-url.mjs';


/**
* Extract the list of CSS definitions in the current spec
Expand All @@ -19,9 +21,8 @@ export default function () {
// Properties are always defined in dedicated tables in modern CSS specs
properties: extractDfns({
selector: 'table.propdef:not(.attrdef)',
extractor: extractTableDfn,
extractor: extractTableDfns,
duplicates: 'merge',
mayReturnMultipleDfns: true,
warnings
}),

Expand Down Expand Up @@ -64,9 +65,8 @@ export default function () {
// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
let descriptors = extractDfns({
selector: 'table.descdef:not(.attrdef)',
extractor: extractTableDfn,
extractor: extractTableDfns,
duplicates: 'push',
mayReturnMultipleDfns: true,
keepDfnType: true,
warnings
});
Expand All @@ -76,16 +76,14 @@ export default function () {
if (res.properties.length === 0 && descriptors.length === 0) {
res.properties = extractDfns({
selector: 'div.propdef dl',
extractor: extractDlDfn,
extractor: extractDlDfns,
duplicates: 'merge',
mayReturnMultipleDfns: true,
warnings
});
descriptors = extractDfns({
selector: 'div.descdef dl',
extractor: extractDlDfn,
extractor: extractDlDfns,
duplicates: 'push',
mayReturnMultipleDfns: true,
warnings
});
}
Expand Down Expand Up @@ -353,52 +351,106 @@ const asideSelector = 'aside, .mdn-anno, .wpt-tests-block';


/**
* Extract a CSS definition from a table
* Extract CSS definitions from a table.
*
* Tables often contain one CSS definition, but they may actual contain a whole
* list of them, as in:
* https://drafts.csswg.org/css-borders-4/#corner-sizing-side-shorthands
*
* All recent CSS specs should follow that pattern
* The "Name" line contains the list of definitions, the other lines are the
* properties shared by all of these definitions.
*
* All recent CSS specs should follow that pattern.
*/
const extractTableDfn = table => {
let res = {};
const lines = [...table.querySelectorAll('tr')]
const extractTableDfns = table => {
// Remove annotations that we do not want to extract
const tableCopy = table.cloneNode(true);
const annotations = tableCopy.querySelectorAll(asideSelector);
annotations.forEach(n => n.remove());

let res = [];
const properties = [...table.querySelectorAll('tr')]
.map(line => {
const cleanedLine = line.cloneNode(true);
const annotations = cleanedLine.querySelectorAll(asideSelector);
annotations.forEach(n => n.remove());
const nameEl = cleanedLine.querySelector(':first-child');
const valueEl = cleanedLine.querySelector('td:last-child');
if (nameEl && valueEl) {
const nameEl = line.querySelector(':first-child');
const valueEl = line.querySelector('td:last-child');
if (!nameEl || !valueEl) {
return null;
}
const propName = dfnLabel2Property(nameEl.textContent);
if (propName === 'name') {
const dfns = [...valueEl.querySelectorAll('dfn[id]')];
if (dfns.length > 0) {
res = dfns.map(dfn => Object.assign({
name: normalize(dfn.textContent),
href: getAbsoluteUrl(dfn)
}));
}
else {
// Some tables may not have proper dfns, we won't be able to extract
// IDs, but we can still extract the text
const value = normalize(valueEl.textContent);
res = value.split(',').map(name => Object.assign({
name: name.trim()
}));
}
return null;
}
else if (propName) {
return {
name: dfnLabel2Property(nameEl.textContent),
name: propName,
value: normalize(valueEl.textContent)
};
}
else {
return null;
}
})
.filter(line => !!line);
for (let prop of lines) {
res[prop.name] = prop.value;
.filter(property => !!property);

for (const dfn of res) {
for (const property of properties) {
dfn[property.name] = property.value;
}
}
return res;
};


/**
* Extract a CSS definition from a dl list
* Extract CSS definitions from a dl list.
*
* Used in "old" CSS specs
* As with tables, a dl list often contains one CSS definition, but it may
* contain a whole list of them, as in:
* https://www.w3.org/TR/CSS21/box.html#border-width-properties
*
* Used in "old" CSS specs.
*/
const extractDlDfn = dl => {
let res = {};
res.name = dl.querySelector('dt').textContent.replace(/'/g, '').trim();
const lines = [...dl.querySelectorAll('dd table tr')]
const extractDlDfns = dl => {
let res = [];
const dfns = [...dl.querySelectorAll('dt:first-child dfn[id],dt:first-child a[name]')];
if (dfns.length > 0) {
res = dfns.map(dfn => Object.assign({
name: normalize(dfn.textContent.replace(/'/g, '')),
href: getAbsoluteUrl(dfn, { attribute: dfn.id ? 'id' : 'name' })
}));
}
else {
// Markup does not seem to contain IDs, let's extract the text instead
const value = normalize(cleanedLine.querySelector('td:last-child').textContent);
res = value.split(',').map(name => Object.assign({
name: normalize(name.replace(/'/g, ''))
}));
}

const properties = [...dl.querySelectorAll('dd table tr')]
.map(line => Object.assign({
name: dfnLabel2Property(line.querySelector(':first-child').textContent),
value: normalize(line.querySelector('td:last-child').textContent)
}));
for (let prop of lines) {
res[prop.name] = prop.value;
for (const dfn of res) {
for (const property of properties) {
dfn[property.name] = property.value;
}
}
return res;
};
Expand Down Expand Up @@ -457,18 +509,16 @@ const extractDfns = ({ root = document,
selector,
extractor,
duplicates = 'reject',
mayReturnMultipleDfns = false,
keepDfnType = false,
warnings = [] }) => {
const res = [];
[...root.querySelectorAll(selector)]
.filter(el => !el.closest(informativeSelector))
.filter(el => !el.querySelector('ins, del'))
.map(extractor)
.filter(dfn => !!dfn?.name)
.map(dfn => !mayReturnMultipleDfns ? [dfn] :
dfn.name.split(',').map(name => Object.assign({}, dfn, { name: name.trim() })))
.map(dfns => Array.isArray(dfns) ? dfns : [dfns])
.reduce((acc, val) => acc.concat(val), [])
.filter(dfn => !!dfn?.name)
.forEach(dfn => {
if (dfn.type && !keepDfnType) {
delete dfn.type;
Expand Down Expand Up @@ -704,6 +754,10 @@ const extractTypedDfn = dfn => {
res = { name: getDfnName(dfn) };
}

if (dfn.id) {
res.href = getAbsoluteUrl(dfn);
}

res.type = dfnType;
if (dfnType === 'value') {
res.value = normalize(res.name);
Expand Down
3 changes: 2 additions & 1 deletion src/postprocessing/csscomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ module.exports = {
propDfn.linkingText.forEach(lt => {
if (!spec.css.properties.find(p => p.name === lt)) {
spec.css.properties.push({
name: lt
name: lt,
href: propDfn.href
});
}
});
Expand Down
Loading

0 comments on commit dead276

Please sign in to comment.