<?xml version="1.0"?>
<!-- XML Source Viewer v2

Features:
(v2)
 - xpointer framework
 - element xpointer scheme
 - xmlns xpointer scheme
 - partial xpointer xpointer scheme

(v1)
 - XML syntax coloring and indenting
 - XPath selection via URI fragment
 - Interactive XPath selection via floating gray box
 - Interactive XPath namespace settings via floating gray box
 - Collapsable elements and comments (by clicking on element name or comment identifier)

Copyright 2007 David Risney. All rights reserved.
-->
<xslt:stylesheet xmlns:xslt="http://www.w3.org/1999/XSL/Transform" version="1.1">
    <xslt:template match="/">
        <html>
            <head>
                <style>
BODY { font:x-small 'Verdana'; }

#floatybar { border-color: gray; background-color: #d0d0d0; border-style: solid; border-width: 2px; display: none; text-align: right; float: right; }

.block { text-indent:-10px; margin-left: 35px; }
.dressing { color: blue; }
.highlight { background-color: yellow; border-width: 2px; border-color: blue; border-style: solid; }
.closed { text-decoration: underline; }

.elementnode .name { color: #990000; }
.attributenode .name { color: #990000; }
.attributenode .value { color: black; font-weight: bold; }
.pinode .name { color: blue; }
.pinode .value { color: blue; }
.textnode .value { color: black; white-space: pre; font-family: monospace; display: inline; }
.namespacenode .name { color: red; }
.namespacenode .value { color: red; font-weight: bold; }
.commentnode .value { color: #888888; white-space: pre; font-family: monospace; display: inline; }
                </style>
                <script><xslt:comment><![CDATA[
var selectedHTMLElemList = [];

function init() {
    document.XMLDocument.setProperty("SelectionLanguage", "XPath");
    defaultnsbutton.onclick();
    document.getElementById('floatybar').style.display = 'inline';

    var incomingxpath = document.location.hash.substr(1);
    if (incomingxpath != null && incomingxpath != "") {
        selectionpath.value = incomingxpath;
        selectbutton.onclick();
    }
}

function displayToggle(outerHtmlElementId, outerHtmlElementTogglerId) {
    var outerHtmlElement = document.getElementById(outerHtmlElementId);
    var togglers = document.getElementsByName(outerHtmlElementTogglerId);
    var childElement = null;
    var col = outerHtmlElement.childNodes;

    for (var idx = 0; idx < col.length; ++idx) {
        var ce = col[idx];
        if (ce.className != null && -1 != ce.className.indexOf('closeable'))
            childElement = ce;
    }

    if (childElement != null) {
        if (childElement.style.display == 'none') {
            childElement.style.display = 'inline';
            for (var idx = 0; idx < togglers.length; ++idx) {
                var toggler = togglers[idx];
                toggler.className -= ' closed';
            }
        }
        else {
            childElement.style.display = 'none';
            for (var idx = 0; idx < togglers.length; ++idx) {
                var toggler = togglers[idx];
                toggler.className += ' closed';
            }
        }
    }
}

function processInput(xpath) {
    var xmlNodes = null;

    try {
        xmlNodes = parseXPointerFramework(xpath); 

        var status = xpath + " selected " + (xmlNodes == null ? '0' : xmlNodes.length) + " nodes.";
        if (xmlNodes == null || xmlNodes.length == 0)
            alert(status);
        window.status = status;
    }
    catch (e) { alert("Error in XPointer \"" + xpath + "\":\n" + e.message); }

    if (xmlNodes != null) {
        for (var idx = 0; idx < xmlNodes.length; ++idx) {
            xmlId = createIdForXMLNode(xmlNodes[idx]);
            selectHTMLNode(document.getElementsByName(xmlId)[0], idx == 0);
        }
    }
}

function parseXPointerFramework(xpointer) {
   xpointer = decodeURIComponent(xpointer);

   var schemeName = null;
   var schemeNameStart = 0;
   var schemeData = null;
   var schemeDataStart = 0;
   var parenCount = 0;
   var skipNext = -1;
   var foundOne = false;

   for (var idx = 0; idx < xpointer.length; ++idx) {
      switch (xpointer.charAt(idx)) {
      case '^':
         skipNext = idx + 1;
         break;

      case '(':
         if (idx != skipNext) {
            if (parenCount == 0) {
               schemeDataStart = idx;
               schemeName = xpointer.substr(schemeNameStart, idx - schemeNameStart);
            }
            ++parenCount;
         }
         break;

      case ')':
         if (idx != skipNext) {
            if (parenCount == 0) {
               var exception = [];
               exception.message = ("Closing paren with no matching open paren:\n" 
                + xpointer.substr(0, idx) + " >>> ) <<< " + xpointer.substr(idx + 1));
               throw exception;
            }
            parenCount = parenCount - 1;
            if (parenCount == 0) {
               schemeData = xpointer.substr(schemeDataStart, (idx - schemeDataStart) + 1);
               schemeNameStart = idx + 1;
            }
         }
         break;

      default:
         break;
      }

      if (schemeName != null && schemeData != null) {
         foundOne = true;
         var result = parseSpecificScheme(schemeName, schemeData);
         schemeName = null;
         schemeData = null;

         if (result != null && result.length > 0)
            return result;
      }
   }

   if (!foundOne) {
      var selecting = "id('" + xpointer + "')";
      return document.XMLDocument.selectNodes(selecting);
   }

   return null;
}

function parseElementScheme(schemeData) {
   var splitted = schemeData.split('/');
   var curElement = document.XMLDocument.documentElement;

   for (var idx = 0; curElement != null && idx < splitted.length; ++idx) {
      if ((idx == 0 && splitted[idx] == "") || 
       (idx == 1 && splitted[idx - 1] == "" && splitted[idx] == "1")) {
         curElement = document.XMLDocument.documentElement;
      }
      else if (idx == 0 && ("" + parseInt(splitted[0])) != splitted[0]) {
         var selecting = ("id('" + splitted[idx] + "')");
         curElement = curElement.selectSingleNode(selecting);
      }
      else {
         var selecting = ("./*[" + splitted[idx] + "]");
         curElement = curElement.selectSingleNode(selecting);
      }
   }

   if (curElement != null) {
      var arr = [];
      arr[0] = curElement;
      return arr;
   }
   else
      return null;
}

function parseSpecificScheme(schemeName, schemeData) {
   schemeData = removeXPointerEscape(schemeData.substr(1, schemeData.length - 2));

   if (schemeName.toLowerCase() == 'xpath' || schemeName.toLowerCase() == 'xpointer')
      schemeName = "XPath";
   else if (schemeName.toLowerCase() == 'xslpattern')
      schemeName = "XSLPattern";

   switch (schemeName) {
   case "element":
      return parseElementScheme(schemeData);
      break;

   case "xmlns":
      addXMLNS(schemeData);
      break;

   default:
      document.XMLDocument.setProperty("SelectionLanguage", schemeName);
      return document.XMLDocument.selectNodes(schemeData);
   }

   return null;
}

function addXMLNS(data) {
   var start = data.indexOf('=');
   if (start <= 0) {
      var exception = [];
      exception.message = "xmlns scheme with no namespace specified.";
      throw exception;
   }

   var ns = "xmlns:" + data.substr(0, start) + '="' + data.substr(start + 1) + '"';
   setXPathNS(document.XMLDocument, getXPathNS(document.XMLDocument) + " " + ns + " ");
}

function removeXPointerEscape(input) {
   var idx = 0;
   while (idx < input.length) {
      if ('^' == input.charAt(idx))
         input = input.substr(0, idx) + input.substr(idx + 1);
      ++idx;
   }

   return input;
}

function setXPathNS(inDocument, namespaces) {
    inDocument.setProperty("SelectionNamespaces", namespaces);
}

function getXPathNS(inDocument) {
   return inDocument.getProperty("SelectionNamespaces");
}

function getDefaultXPathNS(inDocument) {
    var namespaceNodes = inDocument.selectNodes("//namespace::*");
    var namespaces = "";

    for (var idx = 0; idx < namespaceNodes.length; ++idx) {
        var curNS = namespaceNodes[idx];
        if (-1 == namespaces.indexOf(curNS.name) && curNS.name != 'xmlns:xml') {
            if (curNS.name == 'xmlns')
                namespaces += "xmlns:default='";
            else
                namespaces += curNS.name + "='"; 
            namespaces += curNS.value + "' ";
        }
    }

    return namespaces;
}

function createIdForXMLNode(xmlNode) {
    var result = "";
    var parentList;
    var parentNode = xmlNode.selectSingleNode("..");
    var root = xmlNode.selectSingleNode("/");

    if (parentNode == null) {
        return "e";
    }
    else {
        switch (xmlNode.nodeType) {
        case 1:
            result = 'e';
            parentList = parentNode.selectNodes("child::node()");
            break;
        case 2:
            if (0 == xmlNode.nodeName.indexOf('xmlns:')) {
                result = 'n';
                parentList = parentNode.selectNodes("namespace::*");
            }
            else {
                parentList = parentNode.selectNodes("attribute::*");
                result = 'a';
            }
            break;
        case 3:
        case 4:
            result = 't';
            parentList = parentNode.selectNodes("child::node()");
            break;
        case 7:
            result = 'p';
            parentList = parentNode.selectNodes("child::node()");
            break;
        case 8:
            result = 'c';
            parentList = parentNode.selectNodes("child::node()");
            break;
        default:
            alert('error');
            break;
        }

        var idx = 0;

        for (idx = 0; idx < parentList.length; ++idx)
            if (parentList[idx] == xmlNode)
                break;

        return result + (++idx) + "-" + createIdForXMLNode(parentNode);
    }
}

function selectHTMLNode(HTMLNode, scroll) {
    if (HTMLNode != null) {
        if (HTMLNode.className != null)
            HTMLNode.className += ' highlight';

        if (HTMLNode.scrollIntoView != null && scroll)
            HTMLNode.scrollIntoView();

        selectedHTMLElemList[selectedHTMLElemList.length] = HTMLNode;
    }
}

function deselectHTMLNode(HTMLNode, scroll) {
    if (HTMLNode != null) {
        if (HTMLNode.className != null) {
            var idxOfH = 0;

            while (-1 != (idxOfH = HTMLNode.className.indexOf('highlight'))) {
                HTMLNode.className = HTMLNode.className.substr(0, idxOfH) + HTMLNode.className.substr(idxOfH + ('highlight').length);
            }
        }
        if (HTMLNode.scrollIntoView != null && scroll)
            HTMLNode.scrollIntoView();
    }
}

function clearSelectedHTMLNodes() {
    for (var idx = 0; idx < selectedHTMLElemList.length; ++idx) {
        deselectHTMLNode(selectedHTMLElemList[idx], false);
        selectedHTMLElemList[idx] = null;
    }
    selectedHTMLElemList = [];
}

]]>
                </xslt:comment></script>
            </head>
            <body onload="init();" class="block">
                <div id="floatybar">
                    XPointer: <input style="width=50%" name="selectionpath" type="text" value=""/>
                    <button id="selectbutton" style="width=5em" onclick="clearbutton.onclick(); processInput(selectionpath.value); ">Select</button>
                    <button id="clearbutton" style="width=5em" onclick="clearSelectedHTMLNodes();">Clear</button>
                    <br/>
                    XPointer NS: <input style="width=50%" name="selectionns" type="text" value=""/>
                    <button id="setnsbutton" style="width=5em" onclick="setXPathNS(document.XMLDocument, selectionns.value);">Set</button>
                    <button id="defaultnsbutton" style="width=5em" onclick="selectionns.value = getDefaultXPathNS(document.XMLDocument); setnsbutton.onclick()">Default</button>
                </div>
                <xslt:apply-templates/>
            </body>
        </html>
    </xslt:template>

    <!-- element nodes with child nodes -->
    <xslt:template match="*[node()]">
        <div class="block elementnode">
            <xslt:variable name="elementId">
                <xslt:call-template name="produce-id-rec">
                    <xslt:with-param name="node-type">e</xslt:with-param>
                </xslt:call-template>
            </xslt:variable>
            <xslt:variable name="togglerHref">
                <xslt:text>javascript:displayToggle('</xslt:text>
                <xslt:value-of select="$elementId"/>
                <xslt:text>','</xslt:text>
                <xslt:value-of select="$elementId"/>
                <xslt:text>-toggler');</xslt:text>
            </xslt:variable>

            <xslt:attribute name="id">
                <xslt:value-of select="$elementId"/>
            </xslt:attribute>
            <span class="opening">
                <xslt:attribute name="id">
                    <xslt:value-of select="$elementId"/>
                    <xslt:text>-toggler</xslt:text>
                </xslt:attribute>
                <span class="dressing"><xslt:text>&lt;</xslt:text></span>
                <a class="name">
                    <xslt:attribute name="onclick">
                        <xslt:value-of select="$togglerHref"/>
                    </xslt:attribute>
                    <xslt:value-of select="name()"/>
                </a>
                <xslt:call-template name="process-attributes"/>
                <xslt:call-template name="process-namespaces"/> 
                <span class="dressing"><xslt:text>&gt;</xslt:text></span>
            </span>
            <span class="closeable">
                <xslt:apply-templates/>
            </span>
            <span class="closing">
                <xslt:attribute name="id">
                    <xslt:value-of select="$elementId"/>
                    <xslt:text>-toggler</xslt:text>
                </xslt:attribute>
                <span class="dressing"><xslt:text>&lt;/</xslt:text></span>
                <a class="name">
                    <xslt:attribute name="onclick">
                        <xslt:value-of select="$togglerHref"/>
                    </xslt:attribute>
                    <xslt:value-of select="name()"/>
                </a>
                <span class="dressing"><xslt:text>&gt;</xslt:text></span>
            </span>
        </div>
    </xslt:template>

    <!-- element nodes without child nodes -->
    <xslt:template match="*">
        <div class="block elementnode openclosed">
            <xslt:call-template name="add-id">
                <xslt:with-param name="node-type">e</xslt:with-param>
            </xslt:call-template>
            <span class="dressing"><xslt:text>&lt;</xslt:text></span>
            <span class="name"><xslt:value-of select="name()"/></span>
            <xslt:call-template name="process-attributes"/> 
            <xslt:call-template name="process-namespaces"/> 
            <span class="dressing"><xslt:text>/&gt;</xslt:text></span>
        </div>
    </xslt:template>

    <!-- attributes nodes -->
    <xslt:template name="process-attributes">
        <xslt:for-each select="@*">
            <xslt:text> </xslt:text>
            <span class="attributenode">
                <xslt:call-template name="add-id">
                    <xslt:with-param name="node-type">a</xslt:with-param>
                </xslt:call-template>
                <span class="name"><xslt:value-of select="name()"/></span>
                <span class="dressing">&#xa0;=&#xa0;"</span>
                <span class="value"><xslt:value-of select="."/></span>
                <span class="dressing"><xslt:text>"</xslt:text></span>
            </span>
        </xslt:for-each>
    </xslt:template>

    <!-- namespace nodes -->
    <xslt:template name="process-namespaces">
        <xslt:for-each select="namespace::*">
            <xslt:variable name="nsname" select="name()"/>
            <xslt:variable name="nsvalue" select="."/>
            <xslt:if test="not(../../namespace::*[name()=$nsname and .=$nsvalue])">
                <span class="namespacenode">
                    <xslt:call-template name="add-id">
                        <xslt:with-param name="node-type">n</xslt:with-param>
                    </xslt:call-template>
                    <span class="name">
                        <xslt:text> xmlns:</xslt:text>
                        <xslt:value-of select="$nsname"/>
                    </span>
                    <span class="dressing"><xslt:text>&#xa0;=&#xa0;"</xslt:text></span>
                    <span class="value"><xslt:value-of select="$nsvalue"/></span>
                    <span class="dressing"><xslt:text>"</xslt:text></span>
                </span>
            </xslt:if>
        </xslt:for-each>
    </xslt:template>

    <!-- comment nodes -->
    <xslt:template match="comment()">
        <xslt:variable name="elementId">
            <xslt:call-template name="produce-id-rec">
                <xslt:with-param name="node-type">c</xslt:with-param>
            </xslt:call-template>
        </xslt:variable>
        <xslt:variable name="togglerHref">
            <xslt:text>javascript:displayToggle('</xslt:text>
            <xslt:value-of select="$elementId"/>
            <xslt:text>','</xslt:text>
            <xslt:value-of select="$elementId"/>
            <xslt:text>-toggler');</xslt:text>
        </xslt:variable>

        <div class="block commentnode">
            <xslt:attribute name="id">
                <xslt:value-of select="$elementId"/>
            </xslt:attribute>
            <a class="dressing">
                <xslt:attribute name="onclick">
                    <xslt:value-of select="$togglerHref"/>
                </xslt:attribute>
                <xslt:text>&lt;!--</xslt:text>
            </a>
            <pre class="value closeable"><xslt:value-of select="."/></pre>
            <a class="dressing">
                <xslt:attribute name="onclick">
                    <xslt:value-of select="$togglerHref"/>
                </xslt:attribute>
                <xslt:text>--&gt;</xslt:text>
            </a>
        </div>
    </xslt:template>

    <!-- processing-instruction nodes -->
    <xslt:template match="processing-instruction()">
        <div class="block pinode">
            <xslt:call-template name="add-id">
                <xslt:with-param name="node-type">p</xslt:with-param>
            </xslt:call-template>
            <span class="dressing"><xslt:text>&lt;?</xslt:text></span>
            <span class="name"><xslt:value-of select="name()"/></span>
            <xslt:text> </xslt:text>
            <span class="value"><xslt:value-of select="."/></span>
            <span class="dressing"><xslt:text>?&gt;</xslt:text></span>
        </div>
    </xslt:template>

    <!-- text nodes -->
    <xslt:template match="text()">
        <span class="textnode">
            <xslt:call-template name="add-id">
                <xslt:with-param name="node-type">t</xslt:with-param>
            </xslt:call-template>
            <pre class="value"><xslt:value-of select="."/></pre>
        </span>
    </xslt:template>

    <!-- add the unique ID of an XML node as an attribute. -->
    <xslt:template name="add-id">
        <xslt:param name="node-type">e</xslt:param>
        <xslt:attribute name="id">
            <xslt:call-template name="produce-id-rec">
                <xslt:with-param name="node-type"><xslt:value-of select="$node-type"/></xslt:with-param>
            </xslt:call-template>
        </xslt:attribute>
    </xslt:template>

    <!-- generate a unique ID based on the node's document position. -->
    <xslt:template name="produce-id-rec">
        <xslt:param name="node-type">e</xslt:param>
        <xslt:variable name="node-type-element">e</xslt:variable>
        <xslt:variable name="node-type-attribute">a</xslt:variable>
        <xslt:variable name="node-type-pi">p</xslt:variable>
        <xslt:variable name="node-type-text">t</xslt:variable>
        <xslt:variable name="node-type-namespace">n</xslt:variable>
        <xslt:variable name="node-type-comment">c</xslt:variable>

        <xslt:variable name="current-node"><xslt:value-of select="generate-id(.)"/></xslt:variable>

        <xslt:value-of select="$node-type"/>

        <xslt:choose>
            <xslt:when test="$node-type = $node-type-attribute">
                <xslt:for-each select="../attribute::*">
                    <xslt:if test="generate-id(.) = $current-node">
                        <xslt:value-of select="position()"/>
                    </xslt:if>
                </xslt:for-each>
            </xslt:when>
            <xslt:when test="$node-type = $node-type-namespace">
                <xslt:for-each select="../namespace::*">
                    <xslt:if test="generate-id(.) = $current-node">
                        <xslt:value-of select="position()"/>
                    </xslt:if>
                </xslt:for-each>
            </xslt:when>
            <xslt:otherwise>
                <xslt:for-each select="../child::node()">
                    <xslt:if test="generate-id(.) = $current-node">
                        <xslt:value-of select="position()"/>
                    </xslt:if>
                </xslt:for-each>
            </xslt:otherwise>
        </xslt:choose>

        <xslt:for-each select="..">
            <xslt:text>-</xslt:text>
            <xslt:call-template name="produce-id-rec">
                <xslt:with-param name="node-type">e</xslt:with-param>
            </xslt:call-template>
        </xslt:for-each>
    </xslt:template>

</xslt:stylesheet>

