diff --git a/css/elements.css b/css/elements.css index 6ed12734..fdf008fe 100644 --- a/css/elements.css +++ b/css/elements.css @@ -83,17 +83,27 @@ emu-eqn div:first-child { emu-note { display: block; - margin-left: 5em; + margin-left: 6em; color: #666; } emu-note span.note { text-transform: uppercase; - margin-left: -5em; + margin-left: -6em; display: block; float: left; } +emu-example { + display: block; + margin: 1em 3em; +} + +emu-example figure figcaption { + margin-top: 0.5em; + text-align: left; +} + emu-production { display: block; margin-top: 1em; diff --git a/lib/Clause.js b/lib/Clause.js index 06640a6b..5aef9dd8 100644 --- a/lib/Clause.js +++ b/lib/Clause.js @@ -2,6 +2,7 @@ const CLAUSE_ELEMS = ['EMU-INTRO', 'EMU-CLAUSE', 'EMU-ANNEX']; const Note = require('./Note'); +const Example = require('./Example'); const emd = require('ecmarkdown'); const utils = require('./utils'); const Builder = require('./Builder'); @@ -56,7 +57,9 @@ module.exports = class Clause extends Builder { number: this.number }; - this.notes = this.getNotes(); + const record = this.getNotesAndExamples(); + this.notes = record[0]; + this.examples = record[1]; } build() { @@ -69,6 +72,7 @@ module.exports = class Clause extends Builder { processEmd(this); this.buildNotes(); + this.buildExamples(); } buildNotes() { @@ -82,8 +86,20 @@ module.exports = class Clause extends Builder { } } - getNotes() { + buildExamples() { + if (this.examples.length === 1) { + this.examples[0].build(); + } else { + // pass along example index + this.examples.forEach(function (example, i) { + example.build(i + 1); + }, this); + } + } + + getNotesAndExamples() { const notes = []; + const examples = []; utils.domWalk(this.node, function (child) { if (CLAUSE_ELEMS.indexOf(child.nodeName) > -1) { @@ -93,9 +109,13 @@ module.exports = class Clause extends Builder { if (child.nodeName === 'EMU-NOTE') { notes.push(new Note(this.spec, child)); } + + if (child.nodeName === 'EMU-EXAMPLE') { + examples.push(new Example(this.spec, child, this)); + } }.bind(this)); - return notes; + return [notes, examples]; } buildUtils() { diff --git a/lib/Example.js b/lib/Example.js new file mode 100644 index 00000000..d5277ce7 --- /dev/null +++ b/lib/Example.js @@ -0,0 +1,42 @@ +'use strict'; +const Builder = require('./Builder'); + +module.exports = class Example extends Builder { + constructor(spec, node, clause) { + super(spec, node); + this.clause = clause; + this.caption = this.node.getAttribute('caption'); + if (this.node.hasAttribute('id')) { + this.id = this.node.getAttribute('id'); + } + } + + build(number) { + // biblio is added during the build step as we don't know + // the number at build time. Could probably be fixed. + this.spec.biblio.examples[this.id] = { + location: '', + id: this.id, + number: number || 1, + clauseId: this.clause.id + }; + + this.node.innerHTML = '
' + this.node.innerHTML + '
'; + + let caption = 'Example'; + if (number) { + caption += ' ' + number; + } + + caption += ' (Informative)'; + + if (this.caption) { + caption += ': ' + this.caption; + } + + const captionElem = this.spec.doc.createElement('figcaption'); + captionElem.textContent = caption; + this.node.childNodes[0].appendChild(captionElem); + } +}; + diff --git a/lib/Spec.js b/lib/Spec.js index e7dbb4cc..26dd4a06 100644 --- a/lib/Spec.js +++ b/lib/Spec.js @@ -40,13 +40,15 @@ module.exports = class Spec { clauses: {}, ops: {}, productions: {}, - terms: {} + terms: {}, + examles: {} }; this.biblio = { clauses: {}, ops: {}, productions: {}, - terms: {} + terms: {}, + examples: {} }; this._prodsByName = {}; @@ -61,6 +63,8 @@ module.exports = class Spec { buildAll(selector, Builder, opts) { opts = opts || {}; + this._log('Building ' + selector + '...'); + let elems; if (typeof selector === 'string') { elems = this.doc.querySelectorAll(selector); @@ -106,6 +110,7 @@ module.exports = class Spec { if (this.opts.toc) { p = p.then(function() { + this._log('Building table of contents...'); const tb = new Toc(this); return tb.build(); }.bind(this)); @@ -231,6 +236,19 @@ module.exports = class Spec { } } + lookupBiblioEntryById(id) { + const types = ['clause', 'production', 'example']; + for (let i = 0; i < types.length; i++) { + const type = types[i]; + const entry = this.spec.biblio[type + 's'][id] || this.spec.externalBiblio[type + 's'][id]; + if (entry) { + return { type: type, entry: entry }; + } + } + + return null; + } + _log() { if (!this.opts.verbose) return; console.log.apply(console, arguments); diff --git a/lib/Xref.js b/lib/Xref.js index cad9c53b..6c886bd2 100644 --- a/lib/Xref.js +++ b/lib/Xref.js @@ -23,16 +23,26 @@ module.exports = class Xref extends Builder { return; } - let entry = this.spec.biblio.clauses[href.slice(1)] || this.spec.externalBiblio.clauses[href.slice(1)]; - if (entry) { - buildClauseLink(xref, entry); + const id = href.slice(1); + + const entry = this.spec.lookupBiblioEntryById(id); + if (!entry) { + console.log('Warning: can\'t find clause, production or example with id ' + href); return; } - entry = this.spec.biblio.productions[href.slice(1)] || this.spec.externalBiblio.productions[href.slice(1)]; - if (entry) { - buildProductionLink(xref, entry); - return; + switch (entry.type) { + case 'clause': + buildClauseLink(xref, entry.entry); + break; + case 'production': + buildProductionLink(xref, entry.entry); + break; + case 'example': + buildExampleLink(this.spec, xref, entry.entry); + break; + default: + console.log('Warning: found unknown biblio entry (this is a bug, please file it)'); } } else if (aoid) { const entry = this.spec.biblio.ops[aoid] || this.spec.externalBiblio.ops[aoid]; @@ -41,9 +51,10 @@ module.exports = class Xref extends Builder { buildAOLink(xref, entry); return; } + + console.log('Warning: can\'t find abstract op with aoid ' + aoid); } - console.log('Warning: can\'t find clause or production with id ' + href); } }; @@ -75,6 +86,24 @@ function buildAOLink(xref, entry) { } } +function buildExampleLink(spec, xref, entry) { + if (xref.textContent.trim() === '') { + // first need to find the associated clause + const clauseEntry = spec.lookupBiblioEntryById(entry.clauseId); + if (clauseEntry.type !== 'clause') { + console.log('Warning: could not find parent clause for example id ' + entry.id); + } + + if (xref.hasAttribute('title')) { + xref.innerHTML = buildXrefLink(entry, clauseEntry.entry.title + ' Example ' + entry.number); + } else { + xref.innerHTML = buildXrefLink(entry, clauseEntry.entry.number + ' Example ' + entry.number); + } + } else { + xref.innerHTML = buildXrefLink(entry, xref.innerHTML); + } +} + function buildXrefLink(entry, contents) { return '' + contents + ''; diff --git a/spec/index.html b/spec/index.html index d79b4e2b..68f75fd1 100644 --- a/spec/index.html +++ b/spec/index.html @@ -41,6 +41,7 @@

Metadata

To add metadata to your document, use yaml syntax inside a `<pre class=metadata>` element somewhere in the root of your document.

The following table lists the currently supported metadata:

+
OptionDescription
tocEmit table of contents. Boolean, default true.

Example document with metadata

@@ -126,24 +127,56 @@

Example

emu-xref

-

Cross-reference another clause. If the text content of this element is empty, the target clause's section number or title will be used.

+

Cross-reference another clause, production, example, or abstract operation. If the text content of this element is empty, a suitable default is used. The `title` attribute controls this default - when present, clauses are referred to using their title rather than number. This also applies to examples which are indexed first by their containing clause and then their example number.

+

Cross-references to an id check for clauses, productions, and examples in this order. For each type, the local document is consulted before looking for external sources including the default ES6 biblio.

+

Attributes

-

href: Required: URL of the target clause to cross-reference. May simply be a fragment identifier (see examples below).

+

href: Optional: URL of the target clause, production, or example to cross-reference.

title: Optional: If present, xref will be filled in with the referenced clause's title. Otherwise, the clause's section number is used.

+

aoid: Optional: aoid of an abstract operation to reference.

+ +

One of aoid or href must be specified.

Example


     <p>The clause element is specified in <emu-xref href="#emu-clause"></emu-xref>.</p>
     <p>See <emu-xref href="#emu-note" title></emu-xref> for information on the emu-note element.</p>
-    <p>The <emu-xref href="#emu-biblio">biblio element</emu-xref> will eventually support xref to external specs.</p>
+    <p>The <emu-xref href="#emu-biblio">biblio element</emu-xref> supports xref to external specs.</p>
+    <p><emu-xref aoid="Get"></emu-xref> is an abstract operation from ES6</p>
   
Result

The clause element is specified in .

See for information on the emu-note element.

The biblio element will eventually support xref to external specs.

+

is an abstract operation from ES6

+ +

emu-example

+

Creates an informative example. Examples are numbered based on how many are present in the example's containing clause. Can be xrefed by ID using `emu-xref`.

+ +

Attributes

+

caption: Optional: Caption for the example

+ +

Example

+

+<emu-example caption="Example Example">
+  This is an example.
+</emu-example>
+
+<emu-example caption="Another Example Example">
+  This is also an example.
+</emu-example>
+  
+ + This is an example. + + + + This is also an example. + +

emu-biblio

Links a bibliography file. The bibliography file is a JSON document containing URLs for referenced documents along with any algorithms they define.

diff --git a/test/example.html b/test/example.html new file mode 100644 index 00000000..25c360bf --- /dev/null +++ b/test/example.html @@ -0,0 +1,24 @@ +
toc: false
+

Examples outside of clauses aren't processed specially.

+ + This is an example. + + + +

Example Section

+ + Examples inside clauses are numbered and have captions. Captions are optional. + + + +

Example Subclause

+ + Multiple examples are numbered similar to notes + + + + So this becomes example 2. + +
+
+ diff --git a/test/example.html.baseline b/test/example.html.baseline new file mode 100644 index 00000000..90899019 --- /dev/null +++ b/test/example.html.baseline @@ -0,0 +1,26 @@ + + +

Examples outside of clauses aren't processed specially.

+ + This is an example. + + + +

1Example Section#

+
+ Examples inside clauses are numbered and have captions. Captions are optional. +
Example (Informative)
+ + +

1.1Example Subclause#

+
+ Multiple examples are numbered similar to notes +
Example 1 (Informative): An example
+ +
+ So this becomes example 2. +
Example 2 (Informative): A second example
+
+
+ + \ No newline at end of file diff --git a/test/xref.html b/test/xref.html index 75df95ca..2aecd402 100644 --- a/test/xref.html +++ b/test/xref.html @@ -33,12 +33,30 @@

Clause Title

Return if abrupt + + + + + with link text +

AbstractOp

1. Return. + + + This is an example + + + + This is an example +
+ + + This is an example +
diff --git a/test/xref.html.baseline b/test/xref.html.baseline index 3f1dc2e9..597823bd 100644 --- a/test/xref.html.baseline +++ b/test/xref.html.baseline @@ -34,10 +34,28 @@ ReturnIfAbrupt Return if abrupt + + 1.1 Example 1 + Clause Title Example 1 + AbstractOp Example 2 + with link text +

1.1AbstractOp#

  1. Return.
+ +
+ This is an example +
Example 1 (Informative): Foo Caption
+ +
+ This is an example +
Example 2 (Informative): Foo Caption
+ +
+ This is an example +
Example (Informative): Foo Caption