I devised this, recently:
(just a thought experiment)
var someTinyInfosetSample = {
"doctype": "html",
"$": [
{ "": "html" },
[ { "": "head" },
[ { "": "title" }, "Document title" ]
],
[ { "": "body" },
[ { "": "h1" }, "Header 1" ],
[ { "": "p", "class": "content" },
"Paragraph... (line 1)", [ { "": "br" } ],
"... continued (line 2)"
]
]
] };
(at https://jsfiddle.net/YSharpLanguage/dzq4fe39)
Quick rationale:
XML elements are the only node type (besides the document root) which accepts mixed content (text nodes and/or other elements, comments, PIs, and defines an order of its child nodes; hence the use of JSON arrays (child indices being then 1-based, instead of 0-based, because of the reserved index 0 to carry the node type (element) info; but one can note that XPath nodesets also use a 1-based index, btw);
XML attribute name/value maps don't need any ordering of the keys (attribute names) wrt. their owner element, only uniqueness of those at that element node; hence the use of a JSON object at index 0 of the container array (corresp. to the owner element);
and finally, after all, while "" is a perfectly valid JSON key in object values, it's also the case that neither XML elements or attributes can have an empty name anyway... hence the use of "" as a special, conventional key, to provide the element name.
And here's what it takes to turn it into HTML using my small "JSLT" (at https://jsfiddle.net/YSharpLanguage/c7usrpsL/10):
var tinyInfosetJSLT = { $: [
[ [ function/*Root*/(node) { return node.$; } ],
function(root) { return Per(this).map(root.$); }
],
[ [ function/*Element*/(node) { return { }.toString.call(node) === "[object Array]"; } ],
function(element) {
var children = (element.length > 1 ? element.slice(1) : null),
startTag = element[0],
nodeName = startTag[""],
self = this;
return children ?
Per("\r\n<{stag}>{content}</{etag}>\r\n").map
({
stag: Per(this).map(startTag),
etag: nodeName,
content: Per(children).map(function(child) { return Per(self).map(child); }).join("")
})
:
Per("<{stag}/>").map({ stag: Per(this).map(startTag) });
}
],
[ [ function/*StartTag*/(node) { return node[""]; } ],
function(startTag) {
var tag = [ startTag[""] ];
for (var attribute in startTag) {
if (attribute !== "") {
tag.push
(
Per("{name}=\"{value}\"").
map({ name: attribute, value: startTag[attribute].replace('"', """) })
);
}
}
return tag.join(" ");
}
],
[ [ function/*Text*/(node) { return typeof node === "string"; } ],
function(text) {
return text.
replace("\t", "&x09;").
replace("\n", "&x0A;").
replace("\r", "&x0D;");
}
]
] };
(Cf. https://jsfiddle.net/YSharpLanguage/dzq4fe39/1)
where,
Per(tinyInfosetJSLT).map(someTinyInfosetSample)
yields (as a string):
<html>
<head>
<title>Document title</title>
</head>
<body>
<h1>Header 1</h1>
<p class="content">Paragraph... (line 1)<br/>... continued (line 2)</p>
</body>
</html>
(but above the transform could also be easily adapted to use a DOM node factory, and build an actual DOM document, instead of building a string)
'HTH,