From dc725f5d47416c8602d0473b902d00d71fd0bb0b Mon Sep 17 00:00:00 2001 From: Francois Daoust Date: Wed, 21 Feb 2024 17:35:28 +0100 Subject: [PATCH] Add links back to spec in elements extracts Extraction of elements now also adds an `href` property with a link back to the right fragment in the spec. This works in all known cases, allowing us to turn that into a package guarantee for `@webref/elements`. --- schemas/browserlib/extract-elements.json | 1 + src/browserlib/extract-elements.mjs | 38 +++++++++++++++++++++--- tests/extract-elements.js | 35 ++++++++++++++-------- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/schemas/browserlib/extract-elements.json b/schemas/browserlib/extract-elements.json index 4d92ee80..78050fb4 100644 --- a/schemas/browserlib/extract-elements.json +++ b/schemas/browserlib/extract-elements.json @@ -10,6 +10,7 @@ "properties": { "name": { "type": "string" }, "interface": { "$ref": "../common.json#/$defs/interface" }, + "href": { "$ref": "../common.json#/$defs/url" }, "obsolete": { "type": "boolean" } } } diff --git a/src/browserlib/extract-elements.mjs b/src/browserlib/extract-elements.mjs index eeb3340a..1fed7db0 100644 --- a/src/browserlib/extract-elements.mjs +++ b/src/browserlib/extract-elements.mjs @@ -1,3 +1,5 @@ +import getAbsoluteUrl from './get-absolute-url.mjs'; + /** * Extract the list of markup elements that the spec defines * @@ -43,7 +45,10 @@ export default function (spec) { // In most cases, there will be only one element, but some elements are // defined together, typically h1-h6 or sub and sup return dfns.map(dfn => { - const res = { name: getText(dfn) }; + const res = { + name: getText(dfn), + href: getAbsoluteUrl(dfn) + }; const dts = [...el.querySelectorAll('dt')]; dts.forEach(dt => { const prop = ({ @@ -143,8 +148,27 @@ export default function (spec) { if (!name) { throw new Error('Could not extract name from element-summary element'); } + let dfn = el.querySelector('dfn'); + if (!dfn) { + // The SVG 1.1 spec does not use dfns, look for an ID on the parent div + // if defined (happens when there are multiple elements defined in the + // same section) or at a nearby heading (all other cases). + dfn = el.parentElement; + if (!dfn.id) { + dfn = el.previousElementSibling; + while (dfn && !dfn.nodeName.match(/^H\d$/)) { + dfn = dfn.previousElementSibling; + } + if (!dfn) { + throw new Error('Could not locate heading associated with element ' + getText(name)); + } + } + } - const res = { name: getText(name).replace(/‘|’/g, '') }; + const res = { + name: getText(name).replace(/‘|’/g, ''), + href: getAbsoluteUrl(dfn) + }; const dts = [...el.querySelectorAll('dt')]; dts.forEach(dt => { const prop = ({ @@ -180,7 +204,10 @@ export default function (spec) { throw new Error('Could not extract name from definition-table element'); } - const res = { name: getText(dfn) }; + const res = { + name: getText(dfn), + href: getAbsoluteUrl(dfn) + }; const ths = [...el.querySelectorAll('th')]; ths.forEach(th => { const prop = ({ @@ -212,7 +239,10 @@ export default function (spec) { const shortname = (typeof spec === 'string') ? spec : spec.shortname; const otherElements = [...document.querySelectorAll('dfn[data-dfn-type="element"]')] .map(el => { - const elInfo = { "name": el.textContent.trim()}; + const elInfo = { + name: el.textContent.trim(), + href: getAbsoluteUrl(el) + }; // All elements defined in MathML Core // use the MathMLElement interface if (shortname === "mathml-core") { diff --git a/tests/extract-elements.js b/tests/extract-elements.js index c9d5a4b8..f8c10047 100644 --- a/tests/extract-elements.js +++ b/tests/extract-elements.js @@ -8,7 +8,7 @@ const tests = [ { title: "extracts an HTML element that defines its own interface", spec: "html", - html: `

4.4.1 The p element

+ html: `

4.4.1 The p element

DOM interface:
@@ -23,7 +23,8 @@ const tests = [ res: [ { name: "p", - interface: "HTMLParagraphElement" + interface: "HTMLParagraphElement", + href: "about:blank#the-p" } ] }, @@ -31,7 +32,7 @@ const tests = [ { title: "extracts an HTML element that uses another interface", spec: "html", - html: `

4.9.6 The thead element

+ html: `

4.9.6 The thead element

Categories:
None.
@@ -59,7 +60,8 @@ const tests = [ res: [ { name: "thead", - interface: "HTMLTableSectionElement" + interface: "HTMLTableSectionElement", + href: "about:blank#thead" } ] }, @@ -73,11 +75,13 @@ const tests = [ res: [ { name: "sub", - interface: "HTMLElement" + interface: "HTMLElement", + href: "about:blank#the-sub-element" }, { name: "sup", - interface: "HTMLElement" + interface: "HTMLElement", + href: "about:blank#the-sup-element" } ] }, @@ -99,7 +103,8 @@ const tests = [ res: [ { name: 'animate', - interface: 'SVGAnimateElement' + interface: 'SVGAnimateElement', + href: "about:blank#elementdef-animate" } ] }, @@ -138,7 +143,8 @@ const tests = [ res: [ { name: 'feBlend', - interface: 'SVGFEBlendElement' + interface: 'SVGFEBlendElement', + href: "about:blank#elementdef-feblend" } ] }, @@ -159,7 +165,8 @@ const tests = [
`, res: [ { - name: 'discard' + name: 'discard', + href: "about:blank#elementdef-discard" } ] }, @@ -168,12 +175,13 @@ const tests = [ title: "extracts a MathMLElement", spec: "mathml-core", html: `

- The mmm element is a MathML element. + The mmm element is a MathML element.

`, res: [ { name: "mmm", - interface: "MathMLElement" + interface: "MathMLElement", + href: "about:blank#mmm" } ] }, @@ -182,13 +190,14 @@ const tests = [ title: "links an element with its interface in simple case", spec: "portals", html: `

- The portal element uses the + The portal element uses the HTMLPortalElement interface.

`, res: [ { name: "portal", - interface: "HTMLPortalElement" + interface: "HTMLPortalElement", + href: "about:blank#portal" } ] }