render Common Forms in HTML
const html = require('commonform-html')
Call the exported function with a Common Form, receive a string of HTML:
const assert = require('assert')
assert.deepStrictEqual(
html({ content: ['Just a test'] }),
'<div class="article"><p>Just a test</p></div>'
)
assert.deepStrictEqual(
html({
content: [
{
heading: 'A',
form: { content: ['This is A'] }
},
{
heading: 'B',
form: { content: ['This is B'] }
},
{
heading: 'C',
form: { content: [{ link: 'https://example.com' }] }
}
]
}),
[
'<div class="article">',
'<div class="section">',
'<h1>A</h1>',
'<p>This is A</p>',
'</div>',
'<div class="section">',
'<h1>B</h1>',
'<p>This is B</p>',
'</div>',
'<div class="section">',
'<h1>C</h1>',
'<p><a href="https://example.com">https://example.com</a></p>',
'</div>',
'</div>'
]
.join('')
)
You can also pass an Array
of fill-in-the-blank values:
assert.deepStrictEqual(
html(
{
content: [
{ blank: '' },
{ form: { content: ['Another ', { blank: '' }] } }
]
},
[
{ blank: ['content', 0], value: 'Joe' },
{ blank: ['content', 1, 'form', 'content', 1], value: 'Bob' }
]
),
(
'<div class="article">' +
'<p><span class="blank">Joe</span></p>' +
'<div class="section">' +
'<p>Another <span class="blank">Bob</span></p>' +
'</div>' +
'</div>'
)
)
A final argument of { html5: true }
specifies HTML5 output:
assert.deepStrictEqual(
html({ content: ['Just a test'] }, [], { html5: true }),
'<article><p>Just a test</p></article>'
)
assert.deepStrictEqual(
html(
{
content: [
'First text defines a ',
{ definition: 'Term' },
{
heading: 'A',
form: { content: ['This is A'] }
},
'Middle text uses a ',
{ use: 'Term' },
{
heading: 'B',
form: { content: ['This is B'] }
},
'Last text references ',
{ reference: 'Elsewhere' }
]
},
[],
{ html5: true }
),
[
'<article>', // not <div class="article">
'<p>',
'First text defines a ',
'<dfn>Term</dfn>', // not <span class="definition">
'</p>',
'<section>', // not <div class="section">
'<h1>A</h1>',
'<p>This is A</p>',
'</section>',
'<p>',
'Middle text uses a ',
'<span class="term">Term</span>',
'</p>',
'<section>',
'<h1>B</h1>',
'<p>This is B</p>',
'</section>',
'<p>',
'Last text references ',
'<span class="reference">Elsewhere</span>',
'</p>',
'</article>'
]
.join('')
)
The option { lists: true }
renders any series of forms without headings as an ordered list:
assert.deepStrictEqual(
html(
{
content: [
{
heading: 'First-Level Heading',
form: {
content: [
'Here comes a list.',
{ form: { content: ['Apple'] } },
{ form: { content: ['Orange'] } },
'And another one!',
{
form: {
content: [
'Red',
{ form: { content: ['Red-Orange'] } },
{ form: { content: ['Red-Yellow'] } }
]
}
},
{ form: { content: ['Purple'] } },
{
form: {
content: [
{ form: { content: ['More'] } },
{ form: { content: ['Even More'] } }
]
}
},
'Last text!'
]
}
}
]
},
[],
{ html5: true, lists: true }
),
[
'<article>',
'<section>',
'<h1>First-Level Heading</h1>',
'<p>Here comes a list.</p>',
'<ol>',
'<li><p>Apple</p></li>',
'<li><p>Orange</p></li>',
'</ol>',
'<p>And another one!</p>',
'<ol>',
'<li>',
'<p>Red</p>',
'<ol>',
'<li><p>Red-Orange</p></li>',
'<li><p>Red-Yellow</p></li>',
'</ol>',
'</li>',
'<li><p>Purple</p></li>',
'<li>',
'<ol>',
'<li><p>More</p></li>',
'<li><p>Even More</p></li>',
'</ol>',
'</li>',
'</ol>',
'<p>Last text!</p>',
'</section>',
'</article>'
]
.join('')
)
The option { ids: true }
renders headings and references with IDs:
assert.deepStrictEqual(
html(
{
content: [
{
heading: 'First Heading',
form: {
content: [
'first heading content'
]
}
},
{
heading: 'Second Heading',
form: {
content: [
'reference to ',
{ reference: 'First Heading' }
]
}
}
]
},
[],
{ html5: true, ids: true }
),
[
'<article>',
'<section>',
'<h1 id="first-heading">First Heading</h1>',
'<p>first heading content</p>',
'</section>',
'<section>',
'<h1 id="second-heading">Second Heading</h1>',
'<p>reference to <a class="reference" href="#first-heading">First Heading</a></p>',
'</section>',
'</article>'
]
.join('')
)
You can also set a title, version, or both:
assert.deepStrictEqual(
html(
{ content: ['Hello, ', { blank: '' }] },
[{ blank: ['content', 1], value: 'Joe' }],
{ title: 'Welcome' }
),
[
'<div class="article">',
'<h1>Welcome</h1>',
'<p>',
'Hello, <span class="blank">Joe</span>',
'</p>',
'</div>'
]
.join('')
)
assert.deepStrictEqual(
html(
{ content: ['Hello, ', { blank: '' }] },
[{ blank: ['content', 1], value: 'Joe' }],
{ title: 'Welcome', version: '1.0.0' }
),
[
'<div class="article">',
'<h1>Welcome</h1>',
'<p class="version">1.0.0</p>',
'<p>',
'Hello, <span class="blank">Joe</span>',
'</p>',
'</div>'
]
.join('')
)
Set options.hash
to print the form hash at the top:
assert.deepStrictEqual(
html(
{ content: ['Hello, ', { blank: '' }] },
[{ blank: ['content', 1], value: 'Joe' }],
{ title: 'Welcome', version: '1.0.0', hash: true }
),
[
'<div class="article">',
'<h1>Welcome</h1>',
'<p class="version">1.0.0</p>',
'<p class="hash"><code>' +
'd36c54da27de611b3a9ce7d08638bbd2' +
'00cf5f3bb41d59320d04bba02ca48f85' +
'</code></p>',
'<p>',
'Hello, <span class="blank">Joe</span>',
'</p>',
'</div>'
]
.join('')
)
The option { classNames: ["test"] }
adds custom class names to the root element.
assert.deepStrictEqual(
html(
{ content: ['Hello, Joe.'] },
[],
{ classNames: ['test'] }
),
[
'<div class="article test">',
'<p>',
'Hello, Joe.',
'</p>',
'</div>'
]
.join('')
)
assert.deepStrictEqual(
html(
{ content: ['Hello, Joe.'] },
[],
{ html5: true, classNames: ['test'] }
),
[
'<article class="test">',
'<p>',
'Hello, Joe.',
'</p>',
'</article>'
]
.join('')
)
The option { depth: n }
demotes all headings by n
levels. For example, { depth: 1 }
demotes <h1>
s to <h2>
s, and so on.
assert.deepStrictEqual(
html(
{
content: [
{
heading: 'A',
form: { content: ['This is A'] }
},
{
heading: 'B',
form: { content: ['This is B'] }
}
]
},
[],
{ depth: 2 }
),
[
'<div class="article">',
'<div class="section">',
'<h3>A</h3>',
'<p>This is A</p>',
'</div>',
'<div class="section">',
'<h3>B</h3>',
'<p>This is B</p>',
'</div>',
'</div>'
]
.join('')
)
Set options.smartify
to replace ASCII punctuation with Unicode punctuation:
assert.deepStrictEqual(
html(
{ content: [{ use: 'Purchaser' }, "'s address is ", { blank: '' }] },
// ^ straight
[],
{ smartify: true }
),
[
'<div class="article">',
'<p>',
'<span class="term">Purchaser</span>’s ',
// ^ curly
'address is ',
'<span class="blank">[•]</span>',
'</p>',
'</div>'
]
.join('')
)
The option { annotations: [] }
renders annotations in context.
assert.deepStrictEqual(
html(
{
content: [
{
heading: 'A',
form: { content: ['This is A'] }
},
{
heading: 'B',
form: { content: ['This is B'] }
},
{
heading: 'C',
form: {
content: [
{ form: { content: ['1'] } },
{ form: { content: ['2'] } },
{ form: { content: ['3'] } }
]
}
}
]
},
[],
{
lists: true,
annotations: [
{
path: ['content', 0, 'form', 'content', 0],
level: 'info',
message: 'Annotation to A.'
},
{
path: ['content', 1, 'form', 'content', 0],
level: 'error',
message: 'Annotation to B.'
},
{
path: ['content', 2, 'form', 'content', 1, 'form', 'content', 0],
level: 'error',
message: 'Annotation to 2.'
}
]
}
),
[
'<div class="article">',
'<div class="section">',
'<h1>A</h1>',
'<div class="annotation info"><p>Annotation to A.</p></div>',
'<p>This is A</p>',
'</div>',
'<div class="section">',
'<h1>B</h1>',
'<div class="annotation error"><p>Annotation to B.</p></div>',
'<p>This is B</p>',
'</div>',
'<div class="section">',
'<h1>C</h1>',
'<ol>',
'<li><p>1</p></li>',
'<li>',
'<div class="annotation error"><p>Annotation to 2.</p></div>',
'<p>2</p>',
'</li>',
'<li><p>3</p></li>',
'</ol>',
'</div>',
'</div>'
]
.join('')
)
With { html5: true }
, annotations render as <aside>
s.
assert.deepStrictEqual(
html(
{
content: [
{
heading: 'A',
form: { content: ['This is A'] }
},
{
heading: 'B',
form: { content: ['This is B'] }
},
{
heading: 'C',
form: {
content: [
{ form: { content: ['1'] } },
{ form: { content: ['2'] } },
{ form: { content: ['3'] } }
]
}
}
]
},
[],
{
html5: true,
lists: true,
annotations: [
{
path: ['content', 0, 'form', 'content', 0],
level: 'info',
message: 'Annotation to A.'
},
{
path: ['content', 1, 'form', 'content', 0],
level: 'error',
message: 'Annotation to B.'
},
{
path: ['content', 2, 'form', 'content', 1, 'form', 'content', 0],
level: 'error',
message: 'Annotation to 2.'
}
]
}
),
[
'<article>',
'<section>',
'<h1>A</h1>',
'<aside class="annotation info"><p>Annotation to A.</p></aside>',
'<p>This is A</p>',
'</section>',
'<section>',
'<h1>B</h1>',
'<aside class="annotation error"><p>Annotation to B.</p></aside>',
'<p>This is B</p>',
'</section>',
'<section>',
'<h1>C</h1>',
'<ol>',
'<li><p>1</p></li>',
'<li>',
'<aside class="annotation error"><p>Annotation to 2.</p></aside>',
'<p>2</p>',
'</li>',
'<li><p>3</p></li>',
'</ol>',
'</section>',
'</article>'
]
.join('')
)
Annotations to the root of the form appear just within the root element:
const lint = require('commonform-lint')
const form = { content: ['See ', { reference: 'Nonexistent' }] }
const annotations = lint(form)
assert.deepStrictEqual(
html(form, [], {
html5: true,
lists: true,
annotations: annotations
}),
[
'<article>',
'<aside class="annotation error">',
'<p>The heading "Nonexistent" is referenced, but not used.</p>',
'</aside>',
'<p>See <span class="reference">Nonexistent</span></p>',
'</article>'
]
.join('')
)
Supports components:
assert.deepStrictEqual(
html(
{
content: [
{
heading: 'License Grant',
component: 'https://commonform.org/kemitchell/apache-style-license-grant',
version: '1.0.0',
substitutions: {
terms: {
Licensor: 'Vendor',
Licensee: 'Customer',
Program: 'Software'
},
headings: {
'Express Warranties': 'Guarantees'
},
blanks: {
1: 'United States'
}
}
}
]
},
[],
{
html5: true,
lists: true,
ids: true
}
),
[
'<article>',
'<section class="component">',
'<h1 id="license-grant">License Grant</h1>',
'<p>',
'Incorporate ',
'<a href="https://commonform.org/kemitchell/apache-style-license-grant/1.0.0">',
'https://commonform.org/kemitchell/apache-style-license-grant/1.0.0',
'</a>',
' substituting:',
'</p>',
'<ul>',
'<li>the term <span class="term">Customer</span> for the term <span class="term">Licensee</span></li>',
'<li>the term <span class="term">Vendor</span> for the term <span class="term">Licensor</span></li>',
'<li>the term <span class="term">Software</span> for the term <span class="term">Program</span></li>',
'<li>references to <a class="reference" href="#guarantees">Guarantees</a> for references to <span class="reference">Express Warranties</span></li>',
'<li>"United States" for the first blank</li>',
'</ul>',
'</section>',
'</article>'
]
.join('')
)
assert.deepStrictEqual(
html(
{
content: [
{
component: 'https://commonform.org/kemitchell/apache-style-license-grant',
version: '1.0.0',
substitutions: {
terms: {
Licensor: 'Vendor',
Licensee: 'Customer',
Program: 'Software'
},
headings: {
'Express Warranties': 'Guarantees'
},
blanks: {
1: 'United States'
}
}
}
]
},
[],
{
html5: true,
lists: true,
ids: true
}
),
[
'<article>',
'<ol>',
'<li class="component">',
'<p>',
'Incorporate ',
'<a href="https://commonform.org/kemitchell/apache-style-license-grant/1.0.0">',
'https://commonform.org/kemitchell/apache-style-license-grant/1.0.0',
'</a>',
' substituting:',
'</p>',
'<ul>',
'<li>the term <span class="term">Customer</span> for the term <span class="term">Licensee</span></li>',
'<li>the term <span class="term">Vendor</span> for the term <span class="term">Licensor</span></li>',
'<li>the term <span class="term">Software</span> for the term <span class="term">Program</span></li>',
'<li>references to <a class="reference" href="#guarantees">Guarantees</a> for references to <span class="reference">Express Warranties</span></li>',
'<li>"United States" for the first blank</li>',
'</ul>',
'</li>',
'</ol>',
'</article>'
]
.join('')
)
If you pass a form with resolved components labeled with appropriate metadata, you can specify one of three styles for rendering them specially:
const formWithLoaded = {
content: [
{
form: {
content: [
'Except under ', { reference: 'Warranties' },
', the ', { use: 'Vendor' },
' disclaimers all warranties to the ', { use: 'Customer' },
' related to the ', { use: 'Software' }, '.'
]
},
reference: {
component: 'https://example.com/toy-disclaimer',
version: '1.0.0',
substitutions: {
terms: {
Seller: 'Vendor',
Buyer: 'Customer',
Product: 'Software'
},
headings: {
Warranties: 'Quality Assurances'
},
blanks: {}
}
},
component: {
publisher: 'Example Publisher',
name: 'Toy Disclaimer',
version: '1.0.0'
}
}
]
}
assert.deepStrictEqual(
html(formWithLoaded, [], {
html5: true,
lists: true,
loadedComponentStyle: 'both' // reference and copy
}),
[
'<article>',
'<ol>',
'<li class="component">',
'<p>',
'Incorporate ',
'<a href="https://example.com/toy-disclaimer/1.0.0">Example Publisher Toy Disclaimer Version 1.0.0</a>',
' substituting:',
'</p>',
'<ul>',
'<li>the term <span class="term">Customer</span> for the term <span class="term">Buyer</span></li>',
'<li>the term <span class="term">Software</span> for the term <span class="term">Product</span></li>',
'<li>the term <span class="term">Vendor</span> for the term <span class="term">Seller</span></li>',
'<li>references to <span class="reference">Quality Assurances</span> for references to <span class="reference">Warranties</span></li>',
'</ul>',
'<p>Quoting for convenience, with any conflicts resolved in favor of the standard:</p>',
'<blockquote>',
'<p>',
'Except under <span class="reference">Warranties</span>, ',
'the <span class="term">Vendor</span> ',
'disclaimers all warranties to the <span class="term">Customer</span> ',
'related to the <span class="term">Software</span>.',
'</p>',
'</blockquote>',
'</li>',
'</ol>',
'</article>'
]
.join('')
)
assert.deepStrictEqual(
html(formWithLoaded, [], {
html5: true,
lists: true,
loadedComponentStyle: 'both',
quoteComponentText: 'For reference:' // custom text between
}),
[
'<article>',
'<ol>',
'<li class="component">',
'<p>',
'Incorporate ',
'<a href="https://example.com/toy-disclaimer/1.0.0">Example Publisher Toy Disclaimer Version 1.0.0</a>',
' substituting:',
'</p>',
'<ul>',
'<li>the term <span class="term">Customer</span> for the term <span class="term">Buyer</span></li>',
'<li>the term <span class="term">Software</span> for the term <span class="term">Product</span></li>',
'<li>the term <span class="term">Vendor</span> for the term <span class="term">Seller</span></li>',
'<li>references to <span class="reference">Quality Assurances</span> for references to <span class="reference">Warranties</span></li>',
'</ul>',
'<p>',
'For reference:', // here
'</p>',
'<blockquote>',
'<p>',
'Except under <span class="reference">Warranties</span>, ',
'the <span class="term">Vendor</span> ',
'disclaimers all warranties to the <span class="term">Customer</span> ',
'related to the <span class="term">Software</span>.',
'</p>',
'</blockquote>',
'</li>',
'</ol>',
'</article>'
]
.join('')
)
assert.deepStrictEqual(
html(formWithLoaded, [], {
html5: true,
lists: true,
incorporateComponentText: 'Include',
loadedComponentStyle: 'reference' // just reference
}),
[
'<article>',
'<ol>',
'<li class="component">',
'<p>',
'Include ',
'<a href="https://example.com/toy-disclaimer/1.0.0">Example Publisher Toy Disclaimer Version 1.0.0</a>',
' substituting:',
'</p>',
'<ul>',
'<li>the term <span class="term">Customer</span> for the term <span class="term">Buyer</span></li>',
'<li>the term <span class="term">Software</span> for the term <span class="term">Product</span></li>',
'<li>the term <span class="term">Vendor</span> for the term <span class="term">Seller</span></li>',
'<li>references to <span class="reference">Quality Assurances</span> for references to <span class="reference">Warranties</span></li>',
'</ul>',
'</li>',
'</ol>',
'</article>'
]
.join('')
)
assert.deepStrictEqual(
html(formWithLoaded, [], {
html5: true,
lists: true,
loadedComponentStyle: 'copy'
}),
[
'<article>',
'<ol>',
'<li class="component">',
'<p>',
'Except under <span class="reference">Warranties</span>, ',
'the <span class="term">Vendor</span> ',
'disclaimers all warranties to the <span class="term">Customer</span> ',
'related to the <span class="term">Software</span>.',
'</p>',
'</li>',
'</ol>',
'</article>'
]
.join('')
)