Home | History | Annotate | Download | only in xml
      1 /*
      2  * Copyright (C) 2011 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  * 1. Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *
     11  * 2. Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
     17  * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
     20  * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 var nodeParentPairs = [];
     30 
     31 // Script entry point.
     32 
     33 function prepareWebKitXMLViewer(noStyleMessage)
     34 {
     35     var html = createHTMLElement('html');
     36     var head = createHTMLElement('head');
     37     html.appendChild(head);
     38     var style = createHTMLElement('style');
     39     style.id = 'xml-viewer-style';
     40     head.appendChild(style);
     41     var body = createHTMLElement('body');
     42     html.appendChild(body);
     43     var sourceXML = createHTMLElement('div');
     44     sourceXML.id = 'webkit-xml-viewer-source-xml';
     45     body.appendChild(sourceXML);
     46 
     47     var child;
     48     while (child = document.firstChild) {
     49         document.removeChild(child);
     50         if (child.nodeType != Node.DOCUMENT_TYPE_NODE)
     51             sourceXML.appendChild(child);
     52     }
     53     document.appendChild(html);
     54 
     55     var header = createHTMLElement('div');
     56     body.appendChild(header);
     57     header.classList.add('header');
     58     var headerSpan = createHTMLElement('span');
     59     header.appendChild(headerSpan);
     60     headerSpan.textContent = noStyleMessage;
     61     header.appendChild(createHTMLElement('br'));
     62 
     63     var tree = createHTMLElement('div');
     64     body.appendChild(tree);
     65     tree.classList.add('pretty-print');
     66     tree.id = 'tree';
     67     window.onload = sourceXMLLoaded;
     68 }
     69 
     70 function sourceXMLLoaded()
     71 {
     72     var sourceXML = document.getElementById('webkit-xml-viewer-source-xml');
     73     if (!sourceXML)
     74         return; // Stop if some XML tree extension is already processing this document
     75     //var style = document.head.firstChild;
     76     //document.head.removeChild(style);
     77     //document.head.appendChild(style);
     78     var root = document.getElementById('tree');
     79 
     80     for (var child = sourceXML.firstChild; child; child = child.nextSibling)
     81         nodeParentPairs.push({parentElement: root, node: child});
     82 
     83     for (var i = 0; i < nodeParentPairs.length; i++)
     84         processNode(nodeParentPairs[i].parentElement, nodeParentPairs[i].node);
     85 
     86     drawArrows();
     87     initButtons();
     88 
     89     if (typeof(onAfterWebkitXMLViewerLoaded) == 'function')
     90       onAfterWebkitXMLViewerLoaded();
     91 }
     92 
     93 // Tree processing.
     94 
     95 function processNode(parentElement, node)
     96 {
     97     if (!processNode.processorsMap) {
     98         processNode.processorsMap = {};
     99         processNode.processorsMap[Node.PROCESSING_INSTRUCTION_NODE] = processProcessingInstruction;
    100         processNode.processorsMap[Node.ELEMENT_NODE] = processElement;
    101         processNode.processorsMap[Node.COMMENT_NODE] = processComment;
    102         processNode.processorsMap[Node.TEXT_NODE] = processText;
    103         processNode.processorsMap[Node.CDATA_SECTION_NODE] = processCDATA;
    104     }
    105     if (processNode.processorsMap[node.nodeType])
    106         processNode.processorsMap[node.nodeType].call(this, parentElement, node);
    107 }
    108 
    109 function processElement(parentElement, node)
    110 {
    111     if (!node.firstChild)
    112         processEmptyElement(parentElement, node);
    113     else {
    114         var child = node.firstChild;
    115         if (child.nodeType == Node.TEXT_NODE && isShort(child.nodeValue) && !child.nextSibling)
    116             processShortTextOnlyElement(parentElement, node);
    117         else
    118             processComplexElement(parentElement, node);
    119     }
    120 }
    121 
    122 function processEmptyElement(parentElement, node)
    123 {
    124     var line = createLine();
    125     line.appendChild(createTag(node, false, true));
    126     parentElement.appendChild(line);
    127 }
    128 
    129 function processShortTextOnlyElement(parentElement, node)
    130 {
    131     var line = createLine();
    132     line.appendChild(createTag(node, false, false));
    133     for (var child = node.firstChild; child; child = child.nextSibling)
    134         line.appendChild(createText(child.nodeValue));
    135     line.appendChild(createTag(node, true, false));
    136     parentElement.appendChild(line);
    137 }
    138 
    139 function processComplexElement(parentElement, node)
    140 {
    141     var collapsible = createCollapsible();
    142 
    143     collapsible.expanded.start.appendChild(createTag(node, false, false));
    144     for (var child = node.firstChild; child; child = child.nextSibling)
    145         nodeParentPairs.push({parentElement: collapsible.expanded.content, node: child});
    146     collapsible.expanded.end.appendChild(createTag(node, true, false));
    147 
    148     collapsible.collapsed.content.appendChild(createTag(node, false, false));
    149     collapsible.collapsed.content.appendChild(createText('...'));
    150     collapsible.collapsed.content.appendChild(createTag(node, true, false));
    151     parentElement.appendChild(collapsible);
    152 }
    153 
    154 function processComment(parentElement, node)
    155 {
    156     if (isShort(node.nodeValue)) {
    157         var line = createLine();
    158         line.appendChild(createComment('<!-- ' + node.nodeValue + ' -->'));
    159         parentElement.appendChild(line);
    160     } else {
    161         var collapsible = createCollapsible();
    162 
    163         collapsible.expanded.start.appendChild(createComment('<!--'));
    164         collapsible.expanded.content.appendChild(createComment(node.nodeValue));
    165         collapsible.expanded.end.appendChild(createComment('-->'));
    166 
    167         collapsible.collapsed.content.appendChild(createComment('<!--'));
    168         collapsible.collapsed.content.appendChild(createComment('...'));
    169         collapsible.collapsed.content.appendChild(createComment('-->'));
    170         parentElement.appendChild(collapsible);
    171     }
    172 }
    173 
    174 function processCDATA(parentElement, node)
    175 {
    176     if (isShort(node.nodeValue)) {
    177         var line = createLine();
    178         line.appendChild(createText('<![CDATA[ ' + node.nodeValue + ' ]]>'));
    179         parentElement.appendChild(line);
    180     } else {
    181         var collapsible = createCollapsible();
    182 
    183         collapsible.expanded.start.appendChild(createText('<![CDATA['));
    184         collapsible.expanded.content.appendChild(createText(node.nodeValue));
    185         collapsible.expanded.end.appendChild(createText(']]>'));
    186 
    187         collapsible.collapsed.content.appendChild(createText('<![CDATA['));
    188         collapsible.collapsed.content.appendChild(createText('...'));
    189         collapsible.collapsed.content.appendChild(createText(']]>'));
    190         parentElement.appendChild(collapsible);
    191     }
    192 }
    193 
    194 function processProcessingInstruction(parentElement, node)
    195 {
    196     if (isShort(node.nodeValue)) {
    197         var line = createLine();
    198         line.appendChild(createComment('<?' + node.nodeName + ' ' + node.nodeValue + '?>'));
    199         parentElement.appendChild(line);
    200     } else {
    201         var collapsible = createCollapsible();
    202 
    203         collapsible.expanded.start.appendChild(createComment('<?' + node.nodeName));
    204         collapsible.expanded.content.appendChild(createComment(node.nodeValue));
    205         collapsible.expanded.end.appendChild(createComment('?>'));
    206 
    207         collapsible.collapsed.content.appendChild(createComment('<?' + node.nodeName));
    208         collapsible.collapsed.content.appendChild(createComment('...'));
    209         collapsible.collapsed.content.appendChild(createComment('?>'));
    210         parentElement.appendChild(collapsible);
    211     }
    212 }
    213 
    214 function processText(parentElement, node)
    215 {
    216     parentElement.appendChild(createText(node.nodeValue));
    217 }
    218 
    219 // Processing utils.
    220 
    221 function trim(value)
    222 {
    223     return value.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    224 }
    225 
    226 function isShort(value)
    227 {
    228     return trim(value).length <= 50;
    229 }
    230 
    231 // Tree rendering.
    232 
    233 function createHTMLElement(elementName)
    234 {
    235     return document.createElementNS('http://www.w3.org/1999/xhtml', elementName)
    236 }
    237 
    238 function createCollapsible()
    239 {
    240     var collapsible = createHTMLElement('div');
    241     collapsible.classList.add('collapsible');
    242     collapsible.expanded = createHTMLElement('div');
    243     collapsible.expanded.classList.add('expanded');
    244     collapsible.appendChild(collapsible.expanded);
    245 
    246     collapsible.expanded.start = createLine();
    247     collapsible.expanded.start.appendChild(createCollapseButton());
    248     collapsible.expanded.appendChild(collapsible.expanded.start);
    249 
    250     collapsible.expanded.content = createHTMLElement('div');
    251     collapsible.expanded.content.classList.add('collapsible-content');
    252     collapsible.expanded.appendChild(collapsible.expanded.content);
    253 
    254     collapsible.expanded.end = createLine();
    255     collapsible.expanded.appendChild(collapsible.expanded.end);
    256 
    257     collapsible.collapsed = createHTMLElement('div');
    258     collapsible.collapsed.classList.add('collapsed');
    259     collapsible.collapsed.classList.add('hidden');
    260     collapsible.appendChild(collapsible.collapsed);
    261     collapsible.collapsed.content = createLine();
    262     collapsible.collapsed.content.appendChild(createExpandButton());
    263     collapsible.collapsed.appendChild(collapsible.collapsed.content);
    264 
    265     return collapsible;
    266 }
    267 
    268 function createButton()
    269 {
    270     var button = createHTMLElement('span');
    271     button.classList.add('button');
    272     return button;
    273 }
    274 
    275 function createCollapseButton(str)
    276 {
    277     var button = createButton();
    278     button.classList.add('collapse-button');
    279     return button;
    280 }
    281 
    282 function createExpandButton(str)
    283 {
    284     var button = createButton();
    285     button.classList.add('expand-button');
    286     return button;
    287 }
    288 
    289 function createComment(commentString)
    290 {
    291     var comment = createHTMLElement('span');
    292     comment.classList.add('webkit-html-comment');
    293     comment.textContent = commentString;
    294     return comment;
    295 }
    296 
    297 function createText(value)
    298 {
    299     var text = createHTMLElement('span');
    300     text.textContent = trim(value);
    301     text.classList.add('text');
    302     return text;
    303 }
    304 
    305 function createLine()
    306 {
    307     var line = createHTMLElement('div');
    308     line.classList.add('line');
    309     return line;
    310 }
    311 
    312 function createTag(node, isClosing, isEmpty)
    313 {
    314     var tag = createHTMLElement('span');
    315     tag.classList.add('webkit-html-tag');
    316 
    317     var stringBeforeAttrs = '<';
    318     if (isClosing)
    319         stringBeforeAttrs += '/';
    320     stringBeforeAttrs += node.nodeName;
    321     var textBeforeAttrs = document.createTextNode(stringBeforeAttrs);
    322     tag.appendChild(textBeforeAttrs);
    323 
    324     if (!isClosing) {
    325         for (var i = 0; i < node.attributes.length; i++)
    326             tag.appendChild(createAttribute(node.attributes[i]));
    327     }
    328 
    329     var stringAfterAttrs = '';
    330     if (isEmpty)
    331         stringAfterAttrs += '/';
    332     stringAfterAttrs += '>';
    333     var textAfterAttrs = document.createTextNode(stringAfterAttrs);
    334     tag.appendChild(textAfterAttrs);
    335 
    336     return tag;
    337 }
    338 
    339 function createAttribute(attributeNode)
    340 {
    341     var attribute = createHTMLElement('span');
    342     attribute.classList.add('webkit-html-attribute');
    343 
    344     var attributeName = createHTMLElement('span');
    345     attributeName.classList.add('webkit-html-attribute-name');
    346     attributeName.textContent = attributeNode.name;
    347 
    348     var textBefore = document.createTextNode(' ');
    349     var textBetween = document.createTextNode('="');
    350 
    351     var attributeValue = createHTMLElement('span');
    352     attributeValue.classList.add('webkit-html-attribute-value');
    353     attributeValue.textContent = attributeNode.value;
    354 
    355     var textAfter = document.createTextNode('"');
    356 
    357     attribute.appendChild(textBefore);
    358     attribute.appendChild(attributeName);
    359     attribute.appendChild(textBetween);
    360     attribute.appendChild(attributeValue);
    361     attribute.appendChild(textAfter);
    362     return attribute;
    363 }
    364 
    365 // Tree behaviour.
    366 
    367 function drawArrows()
    368 {
    369     var ctx = document.getCSSCanvasContext("2d", "arrowRight", 10, 11);
    370 
    371     ctx.fillStyle = "rgb(90,90,90)";
    372     ctx.beginPath();
    373     ctx.moveTo(0, 0);
    374     ctx.lineTo(0, 8);
    375     ctx.lineTo(7, 4);
    376     ctx.lineTo(0, 0);
    377     ctx.fill();
    378     ctx.closePath();
    379 
    380     var ctx = document.getCSSCanvasContext("2d", "arrowDown", 10, 10);
    381 
    382     ctx.fillStyle = "rgb(90,90,90)";
    383     ctx.beginPath();
    384     ctx.moveTo(0, 0);
    385     ctx.lineTo(8, 0);
    386     ctx.lineTo(4, 7);
    387     ctx.lineTo(0, 0);
    388     ctx.fill();
    389     ctx.closePath();
    390 }
    391 
    392 function expandFunction(sectionId)
    393 {
    394     return function()
    395     {
    396         document.querySelector('#' + sectionId + ' > .expanded').className = 'expanded';
    397         document.querySelector('#' + sectionId + ' > .collapsed').className = 'collapsed hidden';
    398     };
    399 }
    400 
    401 function collapseFunction(sectionId)
    402 {
    403     return function()
    404     {
    405         document.querySelector('#' + sectionId + ' > .expanded').className = 'expanded hidden';
    406         document.querySelector('#' + sectionId + ' > .collapsed').className = 'collapsed';
    407     };
    408 }
    409 
    410 function initButtons()
    411 {
    412     var sections = document.querySelectorAll('.collapsible');
    413     for (var i = 0; i < sections.length; i++) {
    414         var sectionId = 'collapsible' + i;
    415         sections[i].id = sectionId;
    416 
    417         var expandedPart = sections[i].querySelector('#' + sectionId + ' > .expanded');
    418         var collapseButton = expandedPart.querySelector('.collapse-button');
    419         collapseButton.onclick = collapseFunction(sectionId);
    420         collapseButton.onmousedown = handleButtonMouseDown;
    421 
    422         var collapsedPart = sections[i].querySelector('#' + sectionId + ' > .collapsed');
    423         var expandButton = collapsedPart.querySelector('.expand-button');
    424         expandButton.onclick = expandFunction(sectionId);
    425         expandButton.onmousedown = handleButtonMouseDown;
    426     }
    427 
    428 }
    429 
    430 function handleButtonMouseDown(e)
    431 {
    432    // To prevent selection on double click
    433    e.preventDefault();
    434 }
    435