From d5cb7d798408d097ac39ea3fbcd755b108095614 Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:38:59 -0700 Subject: [PATCH 1/3] First pass on table notes --- smpte.js | 73 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/smpte.js b/smpte.js index 408fe2a..d4bf9b3 100644 --- a/smpte.js +++ b/smpte.js @@ -1104,24 +1104,10 @@ function numberNotesToEntry(internalTermsSection) { child.insertBefore(headingLabel, child.firstChild); } + } -function numberSectionNotes(section) { - let notes = []; - - function _findNotes(e) { - for (const child of e.children) { - if (child.localName === "section") - numberSectionNotes(child); - else if (child.classList.contains("note")) - notes.push(child); - else - _findNotes(child); - } - } - - _findNotes(section); - +function numberNoteGroup(notes) { let counter = 1; for (let note of notes) { const headingLabel = document.createElement("span"); @@ -1140,6 +1126,29 @@ function numberSectionNotes(section) { } } +function numberSectionNotes(section) { + let notes = []; + + function _findNotes(e) { + for (const child of e.children) { + if (child.localName === "section" || child.localName === "table") + continue; + else if (child.classList.contains("note")) + notes.push(child); + else + _findNotes(child); + } + } + + _findNotes(section); + numberNoteGroup(notes); + + for (const table of section.querySelectorAll("table")) { + const tableNotes = Array.from(table.querySelectorAll(".note")); + numberNoteGroup(tableNotes); + } +} + function numberNotes() { for (const element of document.body.children) { if (element.localName !== "section") @@ -1194,6 +1203,30 @@ function numberExamples() { } } +function numberTableFootnotes() { + for (const table of document.querySelectorAll("table")) { + const footnotes = Array.from(table.querySelectorAll("tfoot p.footnote")); + if (footnotes.length === 0) continue; + + let charCode = "a".charCodeAt(0); + + for (const fn of footnotes) { + if (!fn.id) continue; + const letter = String.fromCharCode(charCode++); + + /* prepend superscript letter to footnote text */ + const sup = document.createElement("sup"); + sup.textContent = letter; + fn.insertBefore(sup, fn.firstChild); + + /* fill all reference anchors pointing to this footnote */ + for (const ref of table.querySelectorAll(`a[href="#${fn.id}"]`)) { + ref.textContent = letter; + } + } + } +} + function numberTerms() { const termsSection = document.getElementById("sec-terms-and-definitions"); const terms = document.getElementById("terms-int-defs"); @@ -1382,6 +1415,13 @@ function resolveLinks(docMetadata) { anchor.innerText = t; } + } else if (target.classList.contains("footnote")) { + + /* footnote ref — letter already filled by numberTableFootnotes; wrap in */ + const sup = document.createElement("sup"); + anchor.replaceWith(sup); + sup.appendChild(anchor); + } else if (target.localName === "section") { anchor.innerText = _getSectionReference(target); @@ -1487,6 +1527,7 @@ async function render() { numberFormulae(); numberNotes(); numberExamples(); + numberTableFootnotes(); numberTerms(); resolveLinks(docMetadata); insertTOC(docMetadata); From c2e684446453aa3bb8c7d5c11383c6572ed154fe Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:01:31 -0700 Subject: [PATCH 2/3] fix funciton --- smpte.js | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/smpte.js b/smpte.js index d4bf9b3..1248d5c 100644 --- a/smpte.js +++ b/smpte.js @@ -1107,32 +1107,13 @@ function numberNotesToEntry(internalTermsSection) { } -function numberNoteGroup(notes) { - let counter = 1; - for (let note of notes) { - const headingLabel = document.createElement("span"); - headingLabel.className = "heading-label"; - - const headingNumberElement = document.createElement("span"); - headingNumberElement.className = "heading-number"; - if (notes.length !== 1) - headingNumberElement.innerText = counter++; - - headingLabel.appendChild(document.createTextNode("NOTE ")); - headingLabel.appendChild(headingNumberElement); - headingLabel.appendChild(document.createTextNode(" —⁠ ")); - - note.insertBefore(headingLabel, note.firstChild); - } -} - function numberSectionNotes(section) { let notes = []; function _findNotes(e) { for (const child of e.children) { if (child.localName === "section" || child.localName === "table") - continue; + numberSectionNotes(child); else if (child.classList.contains("note")) notes.push(child); else @@ -1141,11 +1122,22 @@ function numberSectionNotes(section) { } _findNotes(section); - numberNoteGroup(notes); - for (const table of section.querySelectorAll("table")) { - const tableNotes = Array.from(table.querySelectorAll(".note")); - numberNoteGroup(tableNotes); + let counter = 1; + for (let note of notes) { + const headingLabel = document.createElement("span"); + headingLabel.className = "heading-label"; + + const headingNumberElement = document.createElement("span"); + headingNumberElement.className = "heading-number"; + if (notes.length !== 1) + headingNumberElement.innerText = counter++; + + headingLabel.appendChild(document.createTextNode("NOTE ")); + headingLabel.appendChild(headingNumberElement); + headingLabel.appendChild(document.createTextNode(" —⁠ ")); + + note.insertBefore(headingLabel, note.firstChild); } } From 3f8c0e3b1b5a8729aecb9aae43a66077eec31c97 Mon Sep 17 00:00:00 2001 From: Steve LLamb <38917682+SteveLLamb@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:46:17 -0700 Subject: [PATCH 3/3] validation, tests, and documentation added --- css/smpte.css | 4 +- doc/main.html | 73 ++++++++++++++++++- js/validate.mjs | 8 ++ smpte.js | 10 ++- .../html/validation/footnote-invalid.html | 51 +++++++++++++ .../html/validation/footnote-valid.html | 56 ++++++++++++++ test/resources/html/validation/st-valid.html | 3 + 7 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 test/resources/html/validation/footnote-invalid.html create mode 100644 test/resources/html/validation/footnote-valid.html diff --git a/css/smpte.css b/css/smpte.css index 280b1eb..80056b3 100644 --- a/css/smpte.css +++ b/css/smpte.css @@ -207,9 +207,9 @@ blockquote { font-style: italic; } -/* note and example */ +/* note, example, footnote */ -.note, .example { +.note, .example, .footnote { font-size: 0.9rem; margin-left: 1rem; } diff --git a/doc/main.html b/doc/main.html index 834d7d3..99a09bf 100644 --- a/doc/main.html +++ b/doc/main.html @@ -1133,10 +1133,78 @@

Tables

<a href="#tab-sample-tabledata"></a> is rendered as . -

+

For more examples of various table layouts and optional cell/column text alignment, see

+

Tables may include footnotes. Footnotes shall appear in a tfoot element at the foot of the table, + each as a p element with class="footnote" and a unique id attribute. Footnotes are automatically assigned superscript lowercase letters (a, b, c, …) in the order they appear in the tfoot element. + References to footnotes within the table body are marked with an a element whose href + attribute references the footnote's id.

+ +

Footnotes are only permitted within tables. They shall not be used outside of a table element.

+ +
+
+<table id="tab-sample-footnote">
+  <caption>Sample Table with Footnotes</caption>
+  <thead>
+    <tr>
+      <th>Sample Number</th>
+      <th>Sample Name</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td>0001</td>
+      <td>Name 01 <a href="#fn-doc-example"></a></td>
+    </tr>
+    <tr>
+      <td>0002</td>
+      <td>Name 02</td>
+    </tr>
+  </tbody>
+  <tfoot>
+    <tr>
+      <td colspan="2">
+        <p class="footnote" id="fn-doc-example">Footnote content goes here.</p>
+      </td>
+    </tr>
+  </tfoot>
+</table>
+
+ +

is rendered as

+ + + + + + + + + + + + + + + + + + + + + + + + +
Sample Table with Footnotes
Sample NumberSample Name
0001Name 01
0002Name 02
+

Footnote content goes here.

+
+
+ +
@@ -1360,6 +1428,9 @@

Using class attributes

example
Applied to p or div elements to create a formatted and automatically numbered example. See .
+
footnote
+
Applied to a p element inside a tfoot element to create a table footnote. Footnotes are only permitted inside a tfoot element — use outside of tables is not allowed. See .
+
center-cell;
right-cell;
left-cell;
diff --git a/js/validate.mjs b/js/validate.mjs index 039193e..323e3bc 100644 --- a/js/validate.mjs +++ b/js/validate.mjs @@ -65,10 +65,18 @@ function validateDisallowedStyleAttributes(root, logger) { } } +function validateFootnoteLocation(root, logger) { + for (const fn of root.querySelectorAll('.footnote')) { + if (!fn.closest('tfoot')) + logger.error(`Footnotes are only permitted inside a table footer`, fn); + } +} + export function smpteValidate(doc, logger) { const docMetadata = smpte.validateHead(doc.head, logger); validateDisallowedHeadLinks(doc.head, logger); validateDisallowedStyleAttributes(doc.documentElement, logger); + validateFootnoteLocation(doc.documentElement, logger); validateBody(doc.body, logger); return docMetadata; } diff --git a/smpte.js b/smpte.js index 1248d5c..5b334b9 100644 --- a/smpte.js +++ b/smpte.js @@ -1206,10 +1206,16 @@ function numberTableFootnotes() { if (!fn.id) continue; const letter = String.fromCharCode(charCode++); - /* prepend superscript letter to footnote text */ + const headingLabel = document.createElement("span"); + headingLabel.className = "heading-label"; + const sup = document.createElement("sup"); sup.textContent = letter; - fn.insertBefore(sup, fn.firstChild); + + headingLabel.appendChild(sup); + headingLabel.appendChild(document.createTextNode("\u00a0")); + + fn.insertBefore(headingLabel, fn.firstChild); /* fill all reference anchors pointing to this footnote */ for (const ref of table.querySelectorAll(`a[href="#${fn.id}"]`)) { diff --git a/test/resources/html/validation/footnote-invalid.html b/test/resources/html/validation/footnote-invalid.html new file mode 100644 index 0000000..a74f517 --- /dev/null +++ b/test/resources/html/validation/footnote-invalid.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + Title of the document + + +
+

This is the scope of the document.

+
+ +
+

Footnote

+ + + + + + + + + + + + + + + + + + + +
Sample Table
Sample NumberSample Name
0001Name 01
0002Name 02
+ +

Footnote content goes here.

+ +
+ + + \ No newline at end of file diff --git a/test/resources/html/validation/footnote-valid.html b/test/resources/html/validation/footnote-valid.html new file mode 100644 index 0000000..de7f324 --- /dev/null +++ b/test/resources/html/validation/footnote-valid.html @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + Title of the document + + +
+

This is the scope of the document.

+
+ +
+

Footnote

+ + + + + + + + + + + + + + + + + + + + + + + + +
Sample Table
Sample NumberSample Name
0001Name 01
0002Name 02
+

Footnote content goes here.

+
+ +
+ + + \ No newline at end of file diff --git a/test/resources/html/validation/st-valid.html b/test/resources/html/validation/st-valid.html index 9f9f2ff..258d097 100644 --- a/test/resources/html/validation/st-valid.html +++ b/test/resources/html/validation/st-valid.html @@ -19,5 +19,8 @@

This is the scope of the document.

+ + + \ No newline at end of file