(function()
{
var XHTML = "http://www.w3.org/1999/xhtml";
// Time slicing constants
var LIMIT = 10; // Maximum number of nodes to process before checking time
var DURATION = 200; // Maximum amount of time (ms) to process before unblocking UI
var DELAY = 15; // Amount of time (ms) to unblock UI
// Tree building state
var iterator;
var nextNode;
var root;
var rootFirstChild;
var time;
// Template References
var attrTemplate, attrName, attrValue;
var elmStartTemplate, elmStartName;
var elmEndTemplate, elmEndName;
var cdataTemplate, cdataValue;
var commentTemplate, commentValue;
var style;
// Only invoke this script if it was injected by our parser. Test for a condition that is
// impossible for a markup to create - two direct children of the document.
var secondRootElement = document.documentElement.nextElementSibling;
if (secondRootElement == null)
{
// We've been included externally, bail
return;
}
// Remove the script element from the document so that it doesn't show up in the XML tree view
document.removeChild(secondRootElement);
// Main entry point. Build the tree view.
function buildTree()
{
time = (new Date()).getTime();
// Cache references to the root element and its first child for later use
root = document.documentElement;
rootFirstChild = root.firstChild;
// Initialize templates
buildAttributeTemplate();
buildCDATASectionTemplate();
buildCommentTemplate();
buildElementTemplate();
buildStyles();
// Start processing the document
buildXMLDeclaration();
iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, null, false);
nextNode = iterator.nextNode();
buildNode();
}
// Resume tree building after pausing to unblock UI
function resumeBuilder()
{
time = (new Date()).getTime();
buildNode();
}
// Build the tree incrementally and delegate to appropriate node function
function buildNode()
{
var node, i = 0;
while(nextNode)
{
if(i++ <= LIMIT)
{
node = nextNode;
nextNode = iterator.nextNode();
switch(node.nodeType)
{
case Node.CDATA_SECTION_NODE : buildCDATASection (node); break;
case Node.COMMENT_NODE : buildComment (node); break;
case Node.DOCUMENT_TYPE_NODE : buildDocumentType (node); break;
case Node.ELEMENT_NODE : buildElement (node); break;
case Node.PROCESSING_INSTRUCTION_NODE : buildProcessingInstruction(node); break;
// No additional handling is needed for text nodes
}
}
else if((new Date()).getTime() - time >= DURATION)
{
setTimeout(resumeBuilder, DELAY);
break;
}
else
{
i = 0;
}
}
}
// Attr
function buildAttribute(attr, treeNode)
{
attrName.textContent = attr.nodeName;
attrValue.textContent = attr.nodeValue;
// Custom Styling for xmlns or xml
if(attr.namespaceURI && (attr.namespaceURI == "http://www.w3.org/2000/xmlns/" || attr.namespaceURI == "http://www.w3.org/XML/1998/namespace"))
{
attrName.style.color = "red";
attrValue.style.color = "red";
}
else
{
attrName.style.color = "#900";
attrValue.style.color = "black";
}
treeNode.appendChild(attrTemplate.cloneNode(true));
}
// Attr Template
function buildAttributeTemplate()
{
// Template root
attrTemplate = document.createDocumentFragment();
attrTemplate.appendChild(document.createTextNode(" "));
// Attribute name
attrName = document.createElementNS(XHTML, "span");
attrTemplate.appendChild(attrName);
attrTemplate.appendChild(document.createTextNode('="'));
// Attribute value
attrValue = document.createElementNS(XHTML, "span");
attrValue.style.fontWeight = "bold";
attrTemplate.appendChild(attrValue);
attrTemplate.appendChild(document.createTextNode('"'));
}
// CDATASection
function buildCDATASection(cdata)
{
cdataValue.textContent = cdata.nodeValue;
cdata.parentNode.insertBefore(cdataTemplate.cloneNode(true), cdata);
cdata.parentNode.removeChild(cdata);
}
// CDATASection Template
function buildCDATASectionTemplate()
{
cdataTemplate = document.createElementNS(XHTML, "div");
cdataTemplate.style.color = "blue";
cdataTemplate.style.fontWeight = "normal";
cdataTemplate.style.marginLeft = "-2em";
var start = document.createTextNode("");
cdataTemplate.appendChild(end);
}
// Comment
function buildComment(comment)
{
commentValue.textContent = comment.nodeValue;
insertBefore(commentTemplate.cloneNode(true), comment);
}
// Comment Template
function buildCommentTemplate()
{
commentTemplate = document.createElementNS(XHTML, "div");
commentTemplate.style.color = "blue";
commentTemplate.style.fontWeight = "normal";
var start = document.createTextNode("");
commentTemplate.appendChild(end);
}
// DocumentType
function buildDocumentType(doctype)
{
var treeNode = document.createElementNS(XHTML, "div");
treeNode.style.fontWeight = "normal";
treeNode.style.color = "blue";
treeNode.textContent = "";
insertBefore(treeNode, doctype);
}
// Element
function buildElement(elm)
{
// Skip HTML elements since they are part of the tree view, not the original XML
if(elm instanceof HTMLElement) return;
// Retrieve information about this element's content model (has children, needs to be collapsable)
var firstChild = (elm == root) ? rootFirstChild : elm.firstChild;
var hasChildren = (null != firstChild);
var isCollapsable = (hasChildren && (null != firstChild.nextSibling || firstChild.nodeType != Node.TEXT_NODE));
// Build the start tag from the element start template
elmStartName.textContent = elm.nodeName;
var treeNode = elmStartTemplate.cloneNode(true);
// Check if this needs to be a collapsable element
if(isCollapsable)
{
// Set the appropriate state for expand/collapse behavior
treeNode.href = "#";
treeNode.className = "collapse";
treeNode.style.position = "relative";
treeNode.onclick = toggle;
}
// Insert the generated start tag into the tree
if(elm == root)
{
// Ensure the start tag for the root element appears after
// the display for preceeding comments, processing instructions, etc.
elm.insertBefore(treeNode, rootFirstChild);
}
else
{
// For other elements, simply place make the start tag the first child
elm.insertBefore(treeNode, elm.firstChild);
}
// Generate representation of attributes
for(var i = 0; i < elm.attributes.length; i++)
{
buildAttribute(elm.attributes[i], treeNode);
}
// Close the start tag appropriately
treeNode.appendChild(document.createTextNode((hasChildren) ? ">" : "/>"));
// Create an end tag (if needed)
if(hasChildren)
{
elmEndName.textContent = elm.nodeName;
treeNode = elmEndTemplate.cloneNode(true);
if(isCollapsable)
{
treeNode.className = "block";
treeNode.style.marginLeft = "-2em";
}
elm.appendChild(treeNode);
}
}
// Element Template
function buildElementTemplate()
{
// Start tag
elmStartTemplate = document.createElementNS(XHTML, "a");
elmStartTemplate.style.color = "blue";
elmStartTemplate.style.marginLeft = "-2em";
elmStartTemplate.appendChild(document.createTextNode("<"));
elmStartName = document.createElementNS(XHTML, "span");
elmStartName.style.color = "#900";
elmStartTemplate.appendChild(elmStartName);
// End tag
elmEndTemplate = document.createElementNS(XHTML, "span");
elmEndTemplate.style.color = "blue";
elmEndTemplate.appendChild(document.createTextNode(""));
elmEndName = document.createElementNS(XHTML, "span");
elmEndName.style.color = "#900";
elmEndTemplate.appendChild(elmEndName);
elmEndTemplate.appendChild(document.createTextNode(">"));
}
// ProcessingInstruction
function buildProcessingInstruction(pi)
{
var treeNode = document.createElementNS(XHTML, "div");
treeNode.style.color = "blue";
treeNode.style.fontWeight = "normal";
treeNode.textContent = "" + pi.nodeName + " " + pi.nodeValue + "?>";
insertBefore(treeNode, pi);
}
// Create a stylesheet to apply general styles to the tree view
function buildStyles()
{
// Style definitions
var styles = "@namespace html url(" + XHTML + ");"
// Setup styles on the root element
+ " :root { "
+ " font:small Verdana; "
+ " font-weight: bold; "
+ " padding: 2em; "
+ " padding-left:4em; "
+ " } "
// Default all elements to block styling
// This allows generic elements to provide structure
+ " * { "
+ " display: block; "
+ " padding-left: 2em; "
+ " } "
// Ensure the style element stays hidden
+ " html|style { "
+ " display: none; "
+ " } "
// Default styling for tag-like spans/anchors
+ " html|span, html|a { "
+ " display: inline; "
+ " padding: 0; "
+ " font-weight: normal; "
+ " text-decoration: none; "
+ " } "
+ " html|span.block { "
+ " display: block; "
+ " } "
// Hide content under a collapsed element
+ " *[html|hidden], "
+ " span.block[html|hidden] { "
+ " display: none; "
+ " } "
// Show "+" for elements that can be expanded
+ " .expand { "
+ " display: block; "
+ " } "
+ " .expand:before { "
+ " content: '+'; "
+ " color: red; "
+ " position: absolute; "
+ " left: -1em; "
+ " } "
// Show "-" for elements that can be collapsed
+ " .collapse { "
+ " display: block; "
+ " } "
+ " .collapse:before { "
+ " content: '-'; "
+ " color: red; "
+ " position: absolute; "
+ " left:-1em; "
+ " } ";
// Build a