diff --git a/js/HTML.js b/js/HTML.js
new file mode 100644
index 000000000..b653a99bd
--- /dev/null
+++ b/js/HTML.js
@@ -0,0 +1,253 @@
+/*\
+title: js/HTML.js
+
+Represents a fragment of HTML as a JavaScript object tree structure. Helper methods are provided to simplify
+constructing HTML trees and to render the tree as an HTML string.
+
+The nodes in the tree have a `type` field that is the name of the node for HTML elements:
+
+ {type: "br", attributes: {name: "value"}}
+
+Attributes values can be strings, arrays of strings or hashmaps. String arrays are
+rendered by joining them together with a space. Hashmaps are rendered as `attr="name1:value1;name2:value2;"`.
+
+Elements with child nodes are expressed as:
+
+ {type: "div", children: []}
+
+Text nodes are represented as:
+
+ {type: "text", value: "A string"}
+
+HTML entities are represented as:
+
+ {type: "entity", value: "quot"}
+
+It is sometimes useful to be able to mix raw strings of HTML too:
+
+ {type: "raw", value: "Something
"}
+
+Other types of node can also be placed in the tree, but they will be ignored by the built-in render function.
+For example, nodes of type `"macro"` are used by the WikiTextParser.
+
+\*/
+(function(){
+
+/*jslint node: true */
+"use strict";
+
+var utils = require("./Utils.js");
+
+/*
+Constructs an HTMLParseTree from a tree of nodes. A single node or an array of nodes can be passed.
+
+As a shortcut, the constructor can be called as an ordinary function without the new keyword, in which case
+it automatically returns the `text/html` rendering of the tree.
+*/
+var HTML = function(tree,type) {
+ if(this instanceof HTML) {
+ // Called as a constructor
+ this.tree = tree;
+ } else {
+ // Called as a function
+ type = type || "text/html";
+ return (new HTML(tree)).render(type);
+ }
+};
+
+/*
+Static method to simplify constructing an HTML element node
+ type: element name
+ attributes: hashmap of element attributes to add
+ options: hashmap of options
+The attributes hashmap can contain strings or hashmaps of strings, (they are processed to attr="name1:value1;name2:value2;")
+The options include:
+ content: a string to include as content in the element (also generates closing tag)
+ classes: an array of classnames to apply to the element
+ selfClosing: causes the element to be rendered with a trailing /, as in
+ insertAfterAttributes: a string to insert after the attribute section of the element
+*/
+HTML.elem = function(type,attributes,children) {
+ var e = {type: type};
+ if(attributes) {
+ e.attributes = attributes;
+ }
+ if(children) {
+ e.children = children;
+ }
+ return e;
+};
+
+/*
+Static method to construct a text node
+*/
+HTML.text = function(value) {
+ return {type: "text", value: value};
+};
+
+/*
+Static method to construct an entity
+*/
+HTML.entity = function(value) {
+ return {type: "entity", value: value};
+};
+
+/*
+Static method to construct a raw HTML node
+*/
+HTML.raw = function(value) {
+ return {type: "raw", value: value};
+};
+
+/*
+Static method to construct a split label
+*/
+HTML.splitLabel = function(type,left,right,classes) {
+ classes = (classes || []).slice(0);
+ classes.push("splitLabel");
+ return HTML.elem("span",{
+ "class": classes
+ },[
+ HTML.elem("span",{
+ "class": ["splitLabelLeft"],
+ "data-tw-label-type": type
+ },left),
+ HTML.elem("span",{
+ "class": ["splitLabelRight"]
+ },right)
+ ]);
+};
+
+/*
+Static method to construct a slider
+*/
+HTML.slider = function(type,label,tooltip,body) {
+ var attributes = {
+ "class": "tw-slider"
+ };
+ if(tooltip) {
+ attributes.alt = tooltip;
+ attributes.title = tooltip;
+ }
+ return HTML.elem("div",
+ attributes,
+ [
+ HTML.elem("a",
+ {
+ "class": ["tw-slider-label"]
+ },[
+ HTML.text(label)
+ ]
+ ),
+ HTML.elem("div",
+ {
+ "class": ["tw-slider-body"]
+ },
+ body
+ )
+ ]
+ );
+};
+
+/*
+Render the HTML tree to a string, either of "text/html" or "text/plain"
+*/
+HTML.prototype.render = function(targetType) {
+ if(targetType == "text/plain") {
+ return this.renderPlain().join("");
+ } else if(targetType == "text/html") {
+ return this.renderHtml().join("");
+ } else {
+ return null;
+ }
+};
+
+/*
+Render the HTML tree to a "text/html" string, returned as a string array
+*/
+HTML.prototype.renderHtml = function(output,node) {
+ output = output || [];
+ node = node || this.tree;
+ if(node instanceof Array) {
+ for(var t=0; t");
+ if(node.children) {
+ this.renderHtml(output,node.children);
+ output.push("",node.type,">");
+ }
+ break;
+ }
+ }
+ return output;
+};
+
+/*
+Render the HTML tree to a "text/plain" string, returned as a string array
+*/
+HTML.prototype.renderPlain = function(output,node) {
+ output = output || [];
+ node = node || this.tree;
+ if(node instanceof Array) {
+ for(var t=0; t