From 5e0924d40608048bb335a6fc6fc2a6e48a3ed408 Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:48:30 -0700 Subject: [PATCH 01/14] closes #337 --- doc/main.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/main.html b/doc/main.html index 7d0255c..82da92b 100644 --- a/doc/main.html +++ b/doc/main.html @@ -824,7 +824,7 @@

Additional elements section

<section id="sec-elements">
   <ol>
     <li>
-      <a id="element-sample-text" title="Description of the element" href="./elements/form.docx"></a>
+      <a id="element-sample-text" title="Description of the element" href="elements/form.docx"></a>
     </li>
   </ol>
 </section>
From ab9d35f30ab937d4f3044685078844a91497e067 Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:51:16 -0700 Subject: [PATCH 02/14] closes #157 --- doc/main.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/main.html b/doc/main.html index 82da92b..34b8bdc 100644 --- a/doc/main.html +++ b/doc/main.html @@ -696,7 +696,7 @@

Terms and definitions section

<dl id="terms-int-defs"> <dt><dfn>key number</dfn></dt> <dd>number that is printed with ink or exposed onto the film at the time of - manufacture at regular intervals, typically one foot.</dd> + 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> From 9bb600a040db9a6ab01d0b438f712371a4776ae2 Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Thu, 26 Mar 2026 12:30:09 -0700 Subject: [PATCH 03/14] Closes #258, closes #193 --- doc/main.html | 118 +++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/doc/main.html b/doc/main.html index 34b8bdc..1557df5 100644 --- a/doc/main.html +++ b/doc/main.html @@ -391,14 +391,14 @@

body element

Overall structure

-

The body element shall consist of an ordered sequence of section elements, as specified at The body element shall consist of an ordered sequence of section elements to create their respective clauses, as specified at .

- + - + @@ -426,15 +426,15 @@

Overall structure

- + - + - + @@ -449,7 +449,7 @@

Overall structure

@@ -457,16 +457,16 @@

Overall structure

-

Foreword section

+

Foreword section

General
-

The Foreword section contains author-supplied prose, e.g. list of substantive changes since the last edition, +

The Foreword section contains author-supplied prose, e.g. list of substantive changes since the last edition, that is added to the SMPTE boilerplate text.

-

The absence of this section indicates that no author-supplied prose was provided.

+

The absence of this section indicates that no author-supplied prose was provided.

The id attribute shall be present on the section element and equal to sec-foreword.

@@ -484,7 +484,7 @@
General
RDD
-

When pubType is set to RDD, the Foreword section shall be present and there there shall be a single dl, after any author-supplied prose if supplied, which contains contact information about the proponent(s) of the RDD document.

+

When pubType is set to RDD, the Foreword section shall be present and there there shall be a single dl, after any author-supplied prose if supplied, which contains contact information about the proponent(s) of the RDD document.

The id attribute shall be present on the dl element and equal to element-proponent.

@@ -516,11 +516,11 @@
RDD
-

Introduction section

+

Introduction section

-

The Introduction section contains author-supplied prose that forms the introduction of the document.

+

The Introduction section contains author-supplied prose that forms the introduction of the document.

-

The absence of this section indicates that the document has no introduction.

+

The absence of this section indicates that the document has no introduction.

The id attribute shall be present on the section element and equal to sec-introduction.

@@ -537,9 +537,9 @@

Introduction section

-

Scope section

+

Scope section

-

The Scope section contains author-supplied prose that forms the scope of the document.

+

The Scope section contains author-supplied prose that forms the scope of the document.

The id attribute shall be present on the section element and equal to sec-scope.

@@ -555,12 +555,12 @@

Scope section

-

Conformance section

+

Conformance section

-

The Conformance section contains author-supplied, conformance-related prose that is added to the SMPTE +

The Conformance section contains author-supplied, conformance-related prose that is added to the SMPTE boilerplate text that is generated depending on pubType.

-

The absence of this section indicates that no author-supplied, conformance-related prose is provided.

+

The absence of this section indicates that no author-supplied, conformance-related prose is provided.

The id attribute shall be present on the section element and equal to sec-conformance.

@@ -574,23 +574,23 @@

Conformance section

-

This section is prohibited unless the document is a Standard or Recommended Practice.

+

This section is prohibited unless the document is a Standard or Recommended Practice.

-

Normative references section

+

Normative references section

-

The Normative references section collects normative references cited in the document.

+

The Normative references section collects normative references cited in the document.

The id attribute shall be present on the section element and equal to sec-normative-references.

The heading shall not be present.

-

The absence of this section indicates that no normative references are cited by the document.

+

The absence of this section indicates that no normative references are cited by the document.

-

If present, the section shall contain a single ul element where each li +

If present, the section shall contain a single ul element where each li element is a normative reference, such that:

    @@ -634,9 +634,9 @@

    Normative references section

-

Terms and definitions section

+

Terms and definitions section

-

The Terms and definitions section collects terms, abbreviations and symbols that are not defined inline the +

The Terms and definitions section collects terms, abbreviations and symbols that are not defined inline the clauses of the document, as described in .

The id attribute shall be present on the section element and equal to @@ -644,7 +644,7 @@

Terms and definitions section

The heading shall not be present.

-

The absence of this section indicates that all terms and abbreviations are defined inline the clauses of the +

The absence of this section indicates that all terms and abbreviations are defined inline the clauses of the document.

If present, the section contains one or both of the following elements:

@@ -706,26 +706,26 @@

Terms and definitions section

-

The boilerplate of the section will be generated automatically.

+

The boilerplate of the clause will be generated automatically.

-

Prose section(s)

+

Prose section(s)

-

Each Prose section contains author-supplied technical prose.

+

Each Prose sectioncontains author-supplied technical prose.

-

A Prose section may contain other nested Prose sections. If any child of a Prose section is a - Prose section, then all children shall be Prose sections.

+

A Prose section may contain other nested Prose section elements. If any child of a Prose section is a + Prose section, then all children shall be Prose section elements.

The id attribute shall be present and its value should be prefixed with sec-.

-

The class attribute of a Prose section shall not contain the annex class.

+

The class attribute of a Prose section shall not contain the annex class.

-

A section heading element of the appropriate level shall be present, starting with level 2 (h2) for all - Prose sections that are children of the body element.

+

A section heading element of the appropriate level shall be present, starting with level 2 (h2) for all + Prose section elements that are children of the body element.

-

Prose sections are automatically numbered, so neither the heading element nor its id attribute should +

Prose section elements are automatically numbered, so neither the heading element nor its id attribute should contain numbering information.

@@ -748,28 +748,28 @@

Prose section(s)

-

Annex section(s)

+

Annex section(s)

-

An Annex section contains an author-supplied technical annex.

+

An Annex section contains an author-supplied technical annex.

-

The requirements for an Annex section are the same as those of a Prose section, with the exception that the - Annex section shall contain the class attribute with value of annex.

+

The requirements for an Annex section are the same as those of a Prose section, with the exception that the + Annex section shall contain the class attribute with value of annex.

-

An Annex section shall not contain nested annex sections.

+

An Annex section shall not contain nested annex class elements.

- By default, an Annex section is labeled as (Normative). To label any Annex section as (Informative), the class attribute value of informative shall be present. + By default, an Annex section is labeled as (Normative). To label any Annex section as (Informative), the class attribute value of informative shall be present.

<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.

<a href="#sec-scope"></a> is rendered as Referencing clauses

Citing normative references

When citing a normative reference, an a element with its href attribute referencing a - cite element from the normative references section shall be used. The text of the link will be + cite element from the normative references section shall be used. The text of the link will be automatically set to the contents of the cite element.

@@ -896,7 +896,7 @@

Citing normative references

Citing non-prose elements

-

When citing an non-prose element of the document, an a element with its href referencing an a element from the additional elements section shall be used. The text of the link will be automatically set to the letter of the element in element list.

+

When citing an non-prose element of the document, an a element with its href referencing an a element from the additional elements section shall be used. The text of the link will be automatically set to the letter of the element in element list.

<a href="#element-sample-text"></a> is rendered as . @@ -908,7 +908,7 @@

Citing non-prose elements

Citing bibliographic references

When citing a bibliographic reference in the prose, an a element with its href attribute - referencing a cite element from the Bibliography section shall be used. The text of the link will be + referencing a cite element from the Bibliography section shall be used. The text of the link will be automatically set to the contents of the cite element.

@@ -1478,7 +1478,7 @@

Using class attributes

annex
-
Used on a section element to designate the section as an annex. See .
+
Used on a section element to designate the clause as an annex. See .
note
Applied to p, div, or dd elements to create a formatted and automatically numbered note. See .
From ec30c2b8186fa908bd7073d6d2947f2149688dff Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Thu, 26 Mar 2026 12:51:06 -0700 Subject: [PATCH 04/14] closes #403 --- doc/main.html | 2 ++ smpte.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/main.html b/doc/main.html index 1557df5..c9637b9 100644 --- a/doc/main.html +++ b/doc/main.html @@ -829,6 +829,8 @@

Additional elements section

</ol> </section>
+ +

This clause will automatically be labeled as the last Annex clause and marked as informative.

diff --git a/smpte.js b/smpte.js index f1622a6..5ba2656 100644 --- a/smpte.js +++ b/smpte.js @@ -593,7 +593,7 @@ function insertElementsAnnex(docMetadata) { return; } - sec.classList.add("unnumbered"); + sec.classList.add("annex", "informative"); const intro = document.createElement("p"); intro.innerText = "The following are the non-prose elements of this document:" From b39d49b56e4ff594fd90e1335970f3f492e50a90 Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:05:21 -0700 Subject: [PATCH 05/14] closes #370 --- doc/main.html | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/doc/main.html b/doc/main.html index c9637b9..acacc14 100644 --- a/doc/main.html +++ b/doc/main.html @@ -1822,6 +1822,37 @@

Validation

".../SMPTE/html-pub/./doc/main.html":1554.21-1555.9: error: End tag had attributes. ".../SMPTE/html-pub/./doc/main.html":1555.242-1555.245: error: No "p" element in scope but a "p" end tag seen. ERROR: "validate:html" exited with 5. + + +
+ +
+

Updating the tooling submodule

+

Document repositories include the tooling (./tooling/) as a git submodule. When the tooling is updated, each document repo must be manually bumped to point to the new tooling commit.

+ +

To check which commit the submodule is currently pinned to and whether it is behind the remote, run:

+
+git submodule status
+
+ +

The output lists each submodule with its current commit SHA. To determine whether the submodule is current, compare the commit SHA against the latest release tag and commit at https://github.com/SMPTE/html-pub.

+ +

To update the submodule to the latest commit on its tracked remote branch, run:

+
+git submodule update --remote
+
+ +

This command fetches from the submodule's remote and advances the pointer in the superproject to the latest available commit on the tracked branch. It does not automatically stage or commit the change in the parent repo.

+ +

After updating, the parent repository will show the submodule directory as modified. Stage and commit the bump with:

+
+git add tooling/
+git commit -m "Bump tooling to latest"
+
+ +

Push the change to the remote as normal:

+
+git push
 
From 05b04b4b290bc47f6b86873a0d6d18f0cada34cc Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:27:01 -0700 Subject: [PATCH 06/14] Closes #306 --- doc/main.html | 67 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/doc/main.html b/doc/main.html index acacc14..a5a5ff9 100644 --- a/doc/main.html +++ b/doc/main.html @@ -1783,10 +1783,43 @@

Repo setup

+
+

Updating the tooling submodule

+

Document repositories include the tooling (./tooling/) as a git submodule. When the tooling is updated, each document repo must be manually bumped to point to the new tooling commit. It is important to keep the submodule as up to date as possible, as updates may include bug fixes, validation improvements, and formatting changes that affect document output.

+ +

Individual changes to files within the ./tooling/ submodule directory are not permitted. All tooling changes must be made through the https://github.com/SMPTE/html-pub repository and picked up via a submodule bump.

+ +

To check which commit the submodule is currently pinned to and whether it is behind the remote, run:

+
+git submodule status
+
+ +

The output lists each submodule with its current commit SHA. To determine whether the submodule is current, compare the commit SHA against the latest release tag and commit at https://github.com/SMPTE/html-pub.

+ +

To update the submodule to the latest commit on its tracked remote branch, run:

+
+git submodule update --remote
+
+ +

This command fetches from the submodule's remote and advances the pointer in the superproject to the latest available commit on the tracked branch. It does not automatically stage or commit the change in the parent repo.

+ +

After updating, the parent repository will show the submodule directory as modified. Stage and commit the bump with:

+
+git add tooling/
+git commit -m "Bump tooling to latest"
+
+ +

Push the change to the remote as normal:

+
+git push
+
+ +
+

Validation

When creating a PR on GitHub, the tooling automatically performs a build process as noted in , and part of that process is doing validation, both the internal to the tooling and for general HTML 5 validation.

- +

While the internal tooling validation occurs at runtime while editing, waiting until the build action during a PR to check the HTML 5 validation can present an editor with unforseen issues (i.e. an invalid element or broken schema error), which then results in failures on the PR/build that must be fixed. Often times when this occurs the preview with redlines is not available in the PR, and/or the tooling will not allow a build to finish. Therefore it is highly recommended an editor runs the validation locally prior to pushing to GitHub to prevent these failures frmo occuring.

The command for this is:

@@ -1826,35 +1859,13 @@

Validation

-
-

Updating the tooling submodule

-

Document repositories include the tooling (./tooling/) as a git submodule. When the tooling is updated, each document repo must be manually bumped to point to the new tooling commit.

+
+

Handling new validation errors after a tooling bump

+

A tooling update may introduce new or stricter validation rules. After bumping the submodule, run validation locally as described in before committing or pushing.

-

To check which commit the submodule is currently pinned to and whether it is behind the remote, run:

-
-git submodule status
-
- -

The output lists each submodule with its current commit SHA. To determine whether the submodule is current, compare the commit SHA against the latest release tag and commit at https://github.com/SMPTE/html-pub.

- -

To update the submodule to the latest commit on its tracked remote branch, run:

-
-git submodule update --remote
-
- -

This command fetches from the submodule's remote and advances the pointer in the superproject to the latest available commit on the tracked branch. It does not automatically stage or commit the change in the parent repo.

- -

After updating, the parent repository will show the submodule directory as modified. Stage and commit the bump with:

-
-git add tooling/
-git commit -m "Bump tooling to latest"
-
- -

Push the change to the remote as normal:

-
-git push
-
+

If validation reports new errors that were not present before the bump, those errors must be resolved in the document source before the bump commit is pushed. Do not push a submodule bump that causes a document to fail validation.

+

Validation errors introduced by a tooling update reflect real issues in the document source. They should be corrected in the document, not worked around by reverting or pinning to an older tooling commit.

From f14883fa3acdd150045da0c507de52488d8f339c Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:37:08 -0700 Subject: [PATCH 07/14] closes #289 --- doc/main.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/main.html b/doc/main.html index a5a5ff9..6a9638c 100644 --- a/doc/main.html +++ b/doc/main.html @@ -762,7 +762,7 @@

Annex section(s)

-
<section class="annex informative" id="sec-addtional-info">
+
<section class="annex informative" id="sec-additional-info">
   <h2>Additional Info</h2>
   <section id="sec-additional-info-sub-section">
     <h3>Additional Info Sub Clause</h3>

From e0efa02de0bca9fb487f81de77eeb0f2086d04d0 Mon Sep 17 00:00:00 2001
From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com>
Date: Thu, 26 Mar 2026 14:37:07 -0700
Subject: [PATCH 08/14] Closes #328

---
 doc/main.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/doc/main.html b/doc/main.html
index 6a9638c..6f26d9d 100644
--- a/doc/main.html
+++ b/doc/main.html
@@ -1060,7 +1060,7 @@ 

Figures

is rendered as

- +
Example of a figure
@@ -1300,7 +1300,7 @@

Internal code snippets

PUBNUM = 1*(DIGIT) ["-" 1*(DIGIT)] REVISION = STABLEREV STABLEREV = 4(DIGIT) [2(DIGIT)] -<pre> +</pre> 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:
      -
    1. Definition: the first dd element shall be the definition of the term. This element is required. +
    2. 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.
    3. +
    4. 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.
    5. -
    6. 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.
    7. Examples: the next dd element(s), if present, shall each have a class attribute containing example.
    8. 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;
  • Ordered sequence of section elements comprising the body element.Ordered sequence of clause / section elements comprising the body element.
    Section NameClause name / section element Cardinality
    0 or 1
    Prose sectionProse 0 or more
    Annex sectionAnnex 0 or more
    Additional elements sectionAdditional elements 0 or 1