I developed 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) that accepts mixed content (text nodes and / or other elements, comments, PIs and determines the order of its child nodes, so using JSON arrays (child indexes are then 1-based, not based on 0 due to the reserved index 0 for transferring type (element) node information, but you can see that XPath node sets also use an index based on 1, btw);
XML attributes / attribute value maps do not need key ordering (attribute names) wrt. their owner, only the uniqueness of those elements in this node element; therefore, the use of the JSON object at index 0 of the container array (corresponds to the owner element);
and finally, in the end, while "is a valid JSON key in object values, this is also the case when none of the XML elements or attributes can have an empty name anyway ... hence the use of" " as a special, ordinary key to provide the element name.
And here is what it takes to turn it into HTML using my little "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)
gives (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 the conversion can also be easily adapted to use the DOM node factory and create the actual DOM document instead of creating a string)
'NTN,