Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions css/smpte.css
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,9 @@ blockquote {
font-style: italic;
}

/* note and example */
/* note, example, footnote */

.note, .example, .deprecated {
.note, .example, .footnote, .deprecated {
font-size: 0.9rem;
margin-left: 1rem;
}
Expand Down
115 changes: 114 additions & 1 deletion doc/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -1142,10 +1142,120 @@ <h4>Tables</h4>
<code>&lt;a href=&quot;#tab-sample-tabledata&quot;&gt;&lt;/a&gt;</code> is rendered as <a href="#tab-sample-tabledata"></a>.
</div>

<p>
<p>
For more examples of various table layouts and optional cell/column text alignment, see <a href="#sec-table-examples"></a>
</p>

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

<p class="note">Footnotes are only permitted within tables. They must not be used outside of a <code>table</code> element.</p>

<p class="note">Each footnote reference (<code>a</code> element) shall be in the same table as the footnote it references, and each footnote shall be referenced exactly once.</p>

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

<p>is rendered as</p>

<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>
</div>

<p>Notes are permitted in tables cells or <code>tfoot</code> elements and are automatically numbered. When a note is also present in the <code>tfoot</code>, it shall appear before all footnotes:</p>

<div class="example">
<pre>
&lt;tfoot&gt;
&lt;tr&gt;
&lt;td colspan=&quot;2&quot;&gt;
&lt;p class=&quot;note&quot;&gt;Note content goes here.&lt;/p&gt;
&lt;p class=&quot;footnote&quot; id=&quot;fn-doc-example-2&quot;&gt;Footnote content goes here.&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tfoot&gt;
</pre>

<p>is rendered as</p>

<table id="tab-sample-footnote-note">
<caption>Sample Table with Note and Footnote in Footer</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-2"></a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<p class="note">Note content goes here.</p>
<p class="footnote" id="fn-doc-example-2">Footnote content goes here.</p>
</td>
</tr>
</tfoot>
</table>
</div>

</section>

<section id="sec-snippets-element">
Expand Down Expand Up @@ -1369,6 +1479,9 @@ <h4>Using <code>class</code> attributes</h4>
<dt><code>example</code></dt>
<dd>Applied to <code>p</code> or <code>div</code> elements to create a formatted and automatically numbered example. See <a href="#sec-example-class"></a>.</dd>

<dt><code>footnote</code></dt>
<dd>Applied to a <code>p</code> element inside a <code>tfoot</code> element to create a table footnote. Footnotes are only permitted inside a <code>tfoot</code> element — use outside of tables is not allowed. See <a href="#sec-table-element"></a>.</dd>

<dt><code>center-cell</code>;</dt>
<dt><code>right-cell</code>;</dt>
<dt><code>left-cell</code>;</dt>
Expand Down
47 changes: 47 additions & 0 deletions js/validate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,57 @@ 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);
}
}

function validateTfootNoteOrder(root, logger) {
for (const tfoot of root.querySelectorAll('tfoot')) {
let seenFootnote = false;
for (const p of tfoot.querySelectorAll('p')) {
if (p.classList.contains('footnote')) {
seenFootnote = true;
} else if (p.classList.contains('note') && seenFootnote) {
logger.error(`Notes in tfoot must appear before all footnotes`, p);
}
}
}
}

function validateFootnoteReferences(root, logger) {
const refCounts = new Map();

for (const anchor of root.querySelectorAll('a[href^="#"]')) {
const id = anchor.getAttribute('href').substring(1);
const target = root.ownerDocument.getElementById(id);

if (!target || !target.classList.contains('footnote'))
continue;

/* check same table */
const anchorTable = anchor.closest('table');
const footnoteTable = target.closest('table');

if (!anchorTable || anchorTable !== footnoteTable)
logger.error(`Footnote reference must be in the same table as its footnote`, anchor);

/* track reference count */
refCounts.set(id, (refCounts.get(id) ?? 0) + 1);
if (refCounts.get(id) > 1)
logger.error(`Footnote "${id}" is referenced more than once`, anchor);
}
}

export function smpteValidate(doc, logger) {
const docMetadata = smpte.validateHead(doc.head, logger);
validateDisallowedHeadLinks(doc.head, logger);
validateDisallowedStyleAttributes(doc.documentElement, logger);
validateFootnoteLocation(doc.documentElement, logger);
validateTfootNoteOrder(doc.documentElement, logger);
validateFootnoteReferences(doc.documentElement, logger);
validateBody(doc.body, logger);
return docMetadata;
}
Expand Down
41 changes: 40 additions & 1 deletion smpte.js
Original file line number Diff line number Diff line change
Expand Up @@ -1104,14 +1104,15 @@ 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")
if (child.localName === "section" || child.localName === "table")
numberSectionNotes(child);
else if (child.classList.contains("note"))
notes.push(child);
Expand Down Expand Up @@ -1194,6 +1195,36 @@ 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++);

const headingLabel = document.createElement("span");
headingLabel.className = "heading-label";

const sup = document.createElement("sup");
sup.textContent = letter;

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}"]`)) {
ref.textContent = letter;
}
}
}
}

function markDeprecated() {
const terms = document.getElementById("terms-int-defs");
if (!terms) return;
Expand Down Expand Up @@ -1394,6 +1425,13 @@ function resolveLinks(docMetadata) {
anchor.innerText = t;
}

} else if (target.classList.contains("footnote")) {

/* footnote ref — letter already filled by numberTableFootnotes; wrap in <sup> */
const sup = document.createElement("sup");
anchor.replaceWith(sup);
sup.appendChild(anchor);

} else if (target.localName === "section") {

anchor.innerText = _getSectionReference(target);
Expand Down Expand Up @@ -1499,6 +1537,7 @@ async function render() {
numberFormulae();
numberNotes();
numberExamples();
numberTableFootnotes();
markDeprecated();
numberTerms();
resolveLinks(docMetadata);
Expand Down
51 changes: 51 additions & 0 deletions test/resources/html/validation/footnote-invalid.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!doctype html>
<html>
<head itemscope="itemscope" itemtype="http://smpte.org/standards/documents">
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script type="module" src="../../../../smpte.js"></script>
<meta itemprop="test" content="invalid" />
<meta itemprop="pubType" content="ST" />
<meta itemprop="pubNumber" content="429" />
<meta itemprop="pubPart" content="6" />
<meta itemprop="pubSuiteTitle" content="Suite title" />
<meta itemprop="pubTC" content="27C" />
<meta itemprop="pubStage" content="WD" />
<meta itemprop="pubState" content="draft" />
<title>Title of the document</title>
</head>
<body>
<section id="sec-scope">
<p>This is the scope of the document.</p>
</section>

<section id="sec-footnote">
<h2>Footnote</h2>

<table id="tab-sample-tabledata">
<caption>Sample Table</caption>
<thead>
<tr>
<th>Sample Number</th>
<th>Sample Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>0001</td>
<td>Name 01 <a href="#fn-1-example"></a></td>
</tr>
<tr>
<td>0002</td>
<td>Name 02</td>
</tr>
</tbody>
</table>

<p class="footnote" id="fn-1-example">Footnote content goes here.</p>

</section>

</body>
</html>
56 changes: 56 additions & 0 deletions test/resources/html/validation/footnote-valid.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!doctype html>
<html>
<head itemscope="itemscope" itemtype="http://smpte.org/standards/documents">
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script type="module" src="../../../../smpte.js"></script>
<meta itemprop="test" content="valid" />
<meta itemprop="pubType" content="ST" />
<meta itemprop="pubNumber" content="429" />
<meta itemprop="pubPart" content="6" />
<meta itemprop="pubSuiteTitle" content="Suite title" />
<meta itemprop="pubTC" content="27C" />
<meta itemprop="pubStage" content="WD" />
<meta itemprop="pubState" content="draft" />
<title>Title of the document</title>
</head>
<body>
<section id="sec-scope">
<p>This is the scope of the document.</p>
</section>

<section id="sec-footnote">
<h2>Footnote</h2>

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

</section>

</body>
</html>
3 changes: 3 additions & 0 deletions test/resources/html/validation/st-valid.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@
<section id="sec-scope">
<p>This is the scope of the document.</p>
</section>



</body>
</html>
Loading