<section class="annex informative" id="sec-addtional-info">
<h2>Additional Info</h2>
<section id="sec-additional-info-sub-section">
- <h3>Additional Info Sub Section</h3>
+ <h3>Additional Info Sub Clause</h3>
<p>Some technical content</p>
</section>
<section id="sec-additional-info-sub-section-more">
- <h3>Additional Info Sub Section More</h3>
+ <h3>Additional Info Sub Clause More</h3>
<p>Some technical content</p>
</section>
</section>
@@ -779,18 +779,18 @@ Annex section(s)
- Additional elements section
+ Additional elements section
- The additional elements section collects the non-prose elements of the document.
+ The additional elements section collects the non-prose elements of the document.
The id attribute shall be present on the section element and equal to
sec-elements.
- The heading of the section shall not be present.
+ The heading of thesection shall not be present.
- If present, the section shall:
+ If present, the section shall:
- - come immediately before the Bibliography section; and
+ - come immediately before the Bibliography
section; and
- contain a single ordered list
ol where each element li contains exactly one a
that links to an element of the document.
@@ -832,19 +832,19 @@ Additional elements section
- Bibliography section
+ Bibliography section
- The Bibliography section contains author-supplied bibliographic references.
+ The Bibliography section contains author-supplied bibliographic references.
The id attribute shall be present on the section element and equal to
sec-bibliography.
The heading shall not be present.
- The absence of this section indicates that no bibliographic references are cited by the document.
+ The absence of this section indicates that no bibliographic references are cited by the document.
- If present, the section shall contain a single ul element that conforms to the same requirements as the
- ul element of the normative references section.
+ If present, the section shall contain a single ul element that conforms to the same requirements as the
+ ul element of the normative references section.
<section id="sec-bibliography">
@@ -869,7 +869,7 @@ Other elements
Referencing clauses
When referencing a clause, an a element with its href attribute referencing a
- section element shall be used. The text of the link will be automatically set to section number.
+ section element shall be used. The text of the link will be automatically set to clause number.
From cec909dddbd5945fbb6f62872e5aae6402321bb7 Mon Sep 17 00:00:00 2001
From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com>
Date: Thu, 26 Mar 2026 15:02:07 -0700
Subject: [PATCH 09/14] Closes #290
---
js/validate.mjs | 7 +++++++
smpte.js | 11 ++++++-----
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/js/validate.mjs b/js/validate.mjs
index 06a1035..5698044 100644
--- a/js/validate.mjs
+++ b/js/validate.mjs
@@ -115,6 +115,13 @@ function validateFootnoteReferences(root, logger) {
}
}
+export function validateDataIncludes(doc, logger) {
+ for (const el of doc.querySelectorAll("pre[data-include]")) {
+ if (el.textContent.trim() === "")
+ logger.error(`data-include file not found: ${el.getAttribute("data-include")}`, el);
+ }
+}
+
export function smpteValidate(doc, logger) {
const docMetadata = smpte.validateHead(doc.head, logger);
validateDisallowedHeadLinks(doc.head, logger);
diff --git a/smpte.js b/smpte.js
index 5ba2656..b50fe60 100644
--- a/smpte.js
+++ b/smpte.js
@@ -28,7 +28,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-import { smpteValidate } from "./js/validate.mjs";
+import { smpteValidate, validateDataIncludes } from "./js/validate.mjs";
import * as smpte from "./js/common.mjs";
class Logger {
@@ -77,7 +77,7 @@ function resolveScriptRelativePath(path) {
function asyncFetchLocal(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest
- xhr.onload = () => resolve(xhr.responseText);
+ xhr.onload = () => xhr.status === 200 ? resolve(xhr.responseText) : reject(new TypeError(`File not found: ${url}`));
xhr.onerror = () => reject(new TypeError('Local request failed'));
xhr.open('GET', url);
xhr.send(null);
@@ -91,7 +91,7 @@ async function asyncAddStylesheet(url) {
s.textContent = data;
document.head.appendChild(s);
})
- .catch(err => logError("Cannot fetch: " + err));
+ .catch(err => logger_.error("Cannot fetch: " + err));
}
function fillTemplate(template, data) {
@@ -1475,9 +1475,9 @@ function asyncInsertSnippets() {
return Promise.all(Array.from(
document.querySelectorAll("pre[data-include]"),
(e) => {
- asyncFetchLocal(e.getAttribute("data-include"))
+ return asyncFetchLocal(e.getAttribute("data-include"))
.then(data => e.textContent = data)
- .catch(err => logError("Cannot fetch: " + err));
+ .catch(() => {});
}
));
}
@@ -1564,6 +1564,7 @@ document.addEventListener('DOMContentLoaded', async () => {
try {
smpteValidate(window.document, logger_);
await render();
+ validateDataIncludes(window.document, logger_);
window._smpteRenderComplete = true;
} catch (e) {
logger_.error(e);
From 82906db5aba04f0bf6fac777b184d5d705896d4e Mon Sep 17 00:00:00 2001
From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com>
Date: Thu, 26 Mar 2026 15:18:24 -0700
Subject: [PATCH 10/14] closes #288
---
css/smpte.css | 23 ++++++++++-------------
smpte.js | 44 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 54 insertions(+), 13 deletions(-)
diff --git a/css/smpte.css b/css/smpte.css
index a29cff2..e1adba2 100644
--- a/css/smpte.css
+++ b/css/smpte.css
@@ -232,19 +232,16 @@ a.heading-link {
}
@media only screen {
- h2:hover .heading-link {
- visibility: visible;
- }
- h3:hover .heading-link {
- visibility: visible;
- }
- h4:hover .heading-link {
- visibility: visible;
- }
- h5:hover .heading-link {
- visibility: visible;
- }
- h6:hover .heading-link {
+ h2:hover .heading-link,
+ h3:hover .heading-link,
+ h4:hover .heading-link,
+ h5:hover .heading-link,
+ h6:hover .heading-link,
+ caption:hover .heading-link,
+ figcaption:hover .heading-link,
+ div.formula:hover .heading-link,
+ .note:hover .heading-link,
+ .example:hover .heading-link {
visibility: visible;
}
}
diff --git a/smpte.js b/smpte.js
index b50fe60..af2c011 100644
--- a/smpte.js
+++ b/smpte.js
@@ -900,6 +900,50 @@ function addHeadingLinks(docMetadata) {
heading.appendChild(headingLink);
}
+
+ for (const table of document.querySelectorAll("table[id]")) {
+ const caption = table.querySelector("caption");
+ if (!caption) continue;
+ const link = document.createElement("a");
+ link.className = "heading-link";
+ link.href = `#${table.id}`;
+ link.innerHTML = "🔗";
+ caption.appendChild(link);
+ }
+
+ for (const figure of document.querySelectorAll("figure[id]")) {
+ const figcaption = figure.querySelector("figcaption");
+ if (!figcaption) continue;
+ const link = document.createElement("a");
+ link.className = "heading-link";
+ link.href = `#${figure.id}`;
+ link.innerHTML = "🔗";
+ figcaption.appendChild(link);
+ }
+
+ for (const formula of document.querySelectorAll("div.formula[id]")) {
+ const link = document.createElement("a");
+ link.className = "heading-link";
+ link.href = `#${formula.id}`;
+ link.innerHTML = "🔗";
+ formula.appendChild(link);
+ }
+
+ for (const note of document.querySelectorAll(".note[id]")) {
+ const link = document.createElement("a");
+ link.className = "heading-link";
+ link.href = `#${note.id}`;
+ link.innerHTML = "🔗";
+ note.appendChild(link);
+ }
+
+ for (const example of document.querySelectorAll(".example[id]")) {
+ const link = document.createElement("a");
+ link.className = "heading-link";
+ link.href = `#${example.id}`;
+ link.innerHTML = "🔗";
+ example.appendChild(link);
+ }
}
function numberSections(element, curHeadingNumber) {
From 2d18c6cd7989459690c9b5428590fe7fd00dc46c Mon Sep 17 00:00:00 2001
From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com>
Date: Thu, 26 Mar 2026 15:30:05 -0700
Subject: [PATCH 11/14] closes #280
---
js/validate.mjs | 6 ++----
smpte.js | 2 +-
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/js/validate.mjs b/js/validate.mjs
index 5698044..9bf0707 100644
--- a/js/validate.mjs
+++ b/js/validate.mjs
@@ -116,10 +116,8 @@ function validateFootnoteReferences(root, logger) {
}
export function validateDataIncludes(doc, logger) {
- for (const el of doc.querySelectorAll("pre[data-include]")) {
- if (el.textContent.trim() === "")
- logger.error(`data-include file not found: ${el.getAttribute("data-include")}`, el);
- }
+ for (const el of doc.querySelectorAll("pre[data-include]"))
+ logger.error(`data-include file not found: ${el.getAttribute("data-include")}`, el);
}
export function smpteValidate(doc, logger) {
diff --git a/smpte.js b/smpte.js
index af2c011..acef508 100644
--- a/smpte.js
+++ b/smpte.js
@@ -1520,7 +1520,7 @@ function asyncInsertSnippets() {
document.querySelectorAll("pre[data-include]"),
(e) => {
return asyncFetchLocal(e.getAttribute("data-include"))
- .then(data => e.textContent = data)
+ .then(data => { e.textContent = data; e.removeAttribute("data-include"); })
.catch(() => {});
}
));
From ff8ed9fe3aef6df37d1e4727329b80aaffb8c83b Mon Sep 17 00:00:00 2001
From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com>
Date: Thu, 26 Mar 2026 15:56:28 -0700
Subject: [PATCH 12/14] closes #256
---
css/smpte.css | 10 ++++++++++
smpte.js | 1 -
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/css/smpte.css b/css/smpte.css
index e1adba2..ab57dec 100644
--- a/css/smpte.css
+++ b/css/smpte.css
@@ -214,6 +214,16 @@ blockquote {
margin-left: 1rem;
}
+.example {
+ padding-left: 1rem;
+}
+
+.example > .heading-label {
+ display: block;
+ margin-left: -1rem;
+ margin-bottom: 0.25em;
+}
+
/* lists */
li {
diff --git a/smpte.js b/smpte.js
index acef508..6ad8422 100644
--- a/smpte.js
+++ b/smpte.js
@@ -1231,7 +1231,6 @@ function numberExamples() {
headingLabel.appendChild(document.createTextNode("EXAMPLE "));
headingLabel.appendChild(headingNumberElement);
- headingLabel.appendChild(document.createTextNode(" — "));
example.insertBefore(headingLabel, example.firstChild);
}
From 690f8c341586d1ecf43938774f33d847f33b656a Mon Sep 17 00:00:00 2001
From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com>
Date: Fri, 27 Mar 2026 10:16:20 -0700
Subject: [PATCH 13/14] fix order of terms
---
doc/main.html | 10 +++++-----
js/validate.mjs | 18 ++++++++++--------
.../terms-note-to-entry-2-invalid.html | 1 +
.../terms-note-to-entry-2-valid.html | 1 +
4 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/doc/main.html b/doc/main.html
index 6f26d9d..440472a 100644
--- a/doc/main.html
+++ b/doc/main.html
@@ -665,13 +665,13 @@ Terms and definitions section
can follow the first dt element, in which case all the terms, abbreviations and symbols in the immediately
preceding dt elements are synonyms.
The dd elements following the dt element(s) shall appear in the following
- order — each group is optional, but if present, it shall appear in this sequence:
+ order — each group is optional (except the definition), but if present, it shall appear in this sequence:
- - Definition: the first
dd element shall be the definition of the term. This element is required.
+ - Deprecated: the first
dd element, if present, shall have a class
+ attribute containing deprecated, indicating the term is deprecated. At most one such element is permitted.
+ - Definition: the next
dd element shall be the definition of the term. This element is required.
References to other defined terms and dfn elements are permitted using <a> elements without an
href attribute. Citations to clauses or external references (i.e. <a href="#bib-HTML-5">) are not permitted.
- - Deprecated: the next
dd element, if present, shall have a class
- attribute containing deprecated, indicating the term is deprecated. At most one such element is permitted.
- Examples: the next
dd element(s), if present, shall each have a class
attribute containing example.
- Notes: the next
dd element(s), if present, shall each have a class
@@ -695,9 +695,9 @@ Terms and definitions section
</ul>
<dl id="terms-int-defs">
<dt><dfn>key number</dfn></dt>
+ <dd class="deprecated">deprecated term</dd>
<dd>number that is printed with ink or exposed onto the film at the time of
manufacture at regular intervals, typically one foot</dd>
- <dd class="deprecated">deprecated term</dd>
<dd class="example">An example of a key number is 12345.</dd>
<dd class="note">Key number shall not be confused with key frame.</dd>
<dd class="source"><a href="#bib-key-number-spec"></a>, modified — definition has been updated.</dd>
diff --git a/js/validate.mjs b/js/validate.mjs
index 9bf0707..344d48d 100644
--- a/js/validate.mjs
+++ b/js/validate.mjs
@@ -741,10 +741,18 @@ class InternalDefinitionsMatcher {
if (count === 0) {
const next = children[0];
- logger.error(`Out of order or unrecognized element in definition${next ? `: ${next.localName}${next.className ? `.${next.className}` : ""}` : ""}
Required order is: definition, deprecated, example, note, source`, next || element);
+ logger.error(`Out of order or unrecognized element in definition${next ? `: ${next.localName}${next.className ? `.${next.className}` : ""}` : ""}
Required order is: deprecated, definition, example, note, source`, next || element);
break;
}
+ /* look for deprecated marker (at most one) */
+
+ if (children.length > 0 && DefinitionDeprecatedMatcher.match(children[0], logger)) {
+ children.shift();
+ if (children.length > 0 && DefinitionDeprecatedMatcher.match(children[0], logger))
+ logger.error(`Only one dd.deprecated is permitted per term`, children[0]);
+ }
+
/* look for definition */
if (children.length > 0 &&
@@ -753,13 +761,7 @@ class InternalDefinitionsMatcher {
children.shift();
}
- /* look for deprecated marker (at most one) */
-
- if (children.length > 0 && DefinitionDeprecatedMatcher.match(children[0], logger)) {
- children.shift();
- if (children.length > 0 && DefinitionDeprecatedMatcher.match(children[0], logger))
- logger.error(`Only one dd.deprecated is permitted per term`, children[0]);
- }
+
/* look for examples to entry */
count = 0;
diff --git a/test/resources/html/validation/terms-note-to-entry-2-invalid.html b/test/resources/html/validation/terms-note-to-entry-2-invalid.html
index ab7f029..d907a74 100644
--- a/test/resources/html/validation/terms-note-to-entry-2-invalid.html
+++ b/test/resources/html/validation/terms-note-to-entry-2-invalid.html
@@ -31,6 +31,7 @@
- Clear
- CLR
- something that is clean
+ - old
- The term shall not be used to refer to clarity of mind.
- The term is not intended to be used ever.
diff --git a/test/resources/html/validation/terms-note-to-entry-2-valid.html b/test/resources/html/validation/terms-note-to-entry-2-valid.html
index fbfca81..9ef723a 100644
--- a/test/resources/html/validation/terms-note-to-entry-2-valid.html
+++ b/test/resources/html/validation/terms-note-to-entry-2-valid.html
@@ -30,6 +30,7 @@
- Clear
- CLR
+ - sub-black
- something that is clean
- The term shall not be used to refer to clarity of mind.
- The term is not intended to be used ever.
From 66489abc36c7f86927de360a39aa60fd16c616b4 Mon Sep 17 00:00:00 2001
From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com>
Date: Fri, 27 Mar 2026 10:52:00 -0700
Subject: [PATCH 14/14] add snippet tests and pass logger to validation
---
doc/main.html | 2 +-
js/validate.mjs | 13 +++++---
scripts/validate.mjs | 7 +++--
test/resources/html/snippets/example.txt | 1 +
.../html/validation/snippet-invalid.html | 30 +++++++++++++++++++
.../html/validation/snippet-valid.html | 30 +++++++++++++++++++
test/src/testValidation.mjs | 12 ++++----
7 files changed, 83 insertions(+), 12 deletions(-)
create mode 100644 test/resources/html/snippets/example.txt
create mode 100644 test/resources/html/validation/snippet-invalid.html
create mode 100644 test/resources/html/validation/snippet-valid.html
diff --git a/doc/main.html b/doc/main.html
index 440472a..f45670e 100644
--- a/doc/main.html
+++ b/doc/main.html
@@ -14,7 +14,7 @@
-
+
Tooling and documentation for HTML documents
diff --git a/js/validate.mjs b/js/validate.mjs
index 344d48d..1145f57 100644
--- a/js/validate.mjs
+++ b/js/validate.mjs
@@ -115,12 +115,15 @@ function validateFootnoteReferences(root, logger) {
}
}
-export function validateDataIncludes(doc, logger) {
- for (const el of doc.querySelectorAll("pre[data-include]"))
- logger.error(`data-include file not found: ${el.getAttribute("data-include")}`, el);
+export function validateDataIncludes(doc, logger, fileExists = null) {
+ for (const el of doc.querySelectorAll("pre[data-include]")) {
+ const src = el.getAttribute("data-include");
+ if (fileExists === null || !fileExists(src))
+ logger.error(`data-include file not found: ${src}`, el);
+ }
}
-export function smpteValidate(doc, logger) {
+export function smpteValidate(doc, logger, fileExists = null) {
const docMetadata = smpte.validateHead(doc.head, logger);
validateDisallowedHeadLinks(doc.head, logger);
validateDisallowedStyleAttributes(doc.documentElement, logger);
@@ -129,6 +132,8 @@ export function smpteValidate(doc, logger) {
validateTfootNoteOrder(doc.documentElement, logger);
validateFootnoteReferences(doc.documentElement, logger);
validateBody(doc.body, logger);
+ if (fileExists !== null)
+ validateDataIncludes(doc, logger, fileExists);
return docMetadata;
}
diff --git a/scripts/validate.mjs b/scripts/validate.mjs
index b3d2533..5dd1428 100644
--- a/scripts/validate.mjs
+++ b/scripts/validate.mjs
@@ -28,11 +28,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
import * as jsdom from "jsdom"
import process from "process";
import * as fs from "fs";
+import * as path from "path";
import {smpteValidate} from "../js/validate.mjs";
async function main() {
- const dom = new jsdom.JSDOM(fs.readFileSync(process.argv[2]));
- smpteValidate(dom.window.document, console);
+ const filePath = path.resolve(process.argv[2]);
+ const dom = new jsdom.JSDOM(fs.readFileSync(filePath));
+ const fileExists = (src) => fs.existsSync(path.resolve(path.dirname(filePath), src));
+ smpteValidate(dom.window.document, console, fileExists);
}
main().catch(e => { console.error(e); process.exitCode = 1; });
diff --git a/test/resources/html/snippets/example.txt b/test/resources/html/snippets/example.txt
new file mode 100644
index 0000000..d457d9a
--- /dev/null
+++ b/test/resources/html/snippets/example.txt
@@ -0,0 +1 @@
+This an example.
\ No newline at end of file
diff --git a/test/resources/html/validation/snippet-invalid.html b/test/resources/html/validation/snippet-invalid.html
new file mode 100644
index 0000000..1ffff6b
--- /dev/null
+++ b/test/resources/html/validation/snippet-invalid.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Title of the document
+
+
+
+ This is the scope of the document.
+
+
+
+ External code snippets
+ External code snippet:
+
+
+
+
+
\ No newline at end of file
diff --git a/test/resources/html/validation/snippet-valid.html b/test/resources/html/validation/snippet-valid.html
new file mode 100644
index 0000000..f8ba91f
--- /dev/null
+++ b/test/resources/html/validation/snippet-valid.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Title of the document
+
+
+
+ This is the scope of the document.
+
+
+
+ External code snippets
+ External code snippet:
+
+
+
+
+
\ No newline at end of file
diff --git a/test/src/testValidation.mjs b/test/src/testValidation.mjs
index 10c38e0..ce99e29 100644
--- a/test/src/testValidation.mjs
+++ b/test/src/testValidation.mjs
@@ -33,8 +33,8 @@ import {smpteValidate, ErrorLogger} from "../../js/validate.mjs";
const testDirPath = "test/resources/html/validation";
-async function _test(path) {
- const dom = new jsdom.JSDOM(fs.readFileSync(path));
+async function _test(filePath) {
+ const dom = new jsdom.JSDOM(fs.readFileSync(filePath));
const expectation = dom.window.document.head.querySelector("meta[itemprop='test']").getAttribute("content");
@@ -42,8 +42,10 @@ async function _test(path) {
let hasThrown = false;
+ const fileExists = (src) => fs.existsSync(path.resolve(path.dirname(filePath), src));
+
try {
- smpteValidate(dom.window.document, logger);
+ smpteValidate(dom.window.document, logger, fileExists);
} catch (e) {
logger.error(`Exception: ${e.stack}`);
hasThrown = true;
@@ -52,11 +54,11 @@ async function _test(path) {
const hasPassed = !logger.hasFailed() && !hasThrown;
if ((expectation === "valid" && hasPassed) || (expectation !== "valid" && !hasPassed)) {
- console.log(`${path} passed.`);
+ console.log(`${filePath} passed.`);
return true;
}
- console.log(`**** ${path} failed.`);
+ console.log(`**** ${filePath} failed.`);
logger.errorList().map(msg => console.log(` ${msg.message}`));
return false;