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 var tree;
     34 
     35 function prepareWebKitXMLViewer(noStyleMessage)
     36 {
     37     var html = createHTMLElement('html');
     38     var head = createHTMLElement('head');
     39     html.appendChild(head);
     40     var style = createHTMLElement('style');
     41     style.id = 'xml-viewer-style';
     42     head.appendChild(style);
     43     var body = createHTMLElement('body');
     44     html.appendChild(body);
     45     var sourceXML = createHTMLElement('div');
     46     sourceXML.id = 'webkit-xml-viewer-source-xml';
     47     body.appendChild(sourceXML);
     48 
     49     var child;
     50     while (child = document.firstChild) {
     51         document.removeChild(child);
     52         if (child.nodeType != Node.DOCUMENT_TYPE_NODE)
     53             sourceXML.appendChild(child);
     54     }
     55     document.appendChild(html);
     56 
     57     var header = createHTMLElement('div');
     58     body.appendChild(header);
     59     header.classList.add('header');
     60     var headerSpan = createHTMLElement('span');
     61     header.appendChild(headerSpan);
     62     headerSpan.textContent = noStyleMessage;
     63     header.appendChild(createHTMLElement('br'));
     64 
     65     tree = createHTMLElement('div');
     66     body.appendChild(tree);
     67     tree.classList.add('pretty-print');
     68     window.onload = sourceXMLLoaded;
     69 }
     70 
     71 function sourceXMLLoaded()
     72 {
     73     var sourceXML = document.getElementById('webkit-xml-viewer-source-xml');
     74     if (!sourceXML)
     75         return; // Stop if some XML tree extension is already processing this document
     76 
     77     for (var child = sourceXML.firstChild; child; child = child.nextSibling)
     78         nodeParentPairs.push({parentElement: tree, node: child});
     79 
     80     for (var i = 0; i < nodeParentPairs.length; i++)
     81         processNode(nodeParentPairs[i].parentElement, nodeParentPairs[i].node);
     82 
     83     drawArrows();
     84     initButtons();
     85 
     86     if (typeof(onAfterWebkitXMLViewerLoaded) == 'function')
     87       onAfterWebkitXMLViewerLoaded();
     88 }
     89 
     90 // Tree processing.
     91 
     92 function processNode(parentElement, node)
     93 {
     94     if (!processNode.processorsMap) {
     95         processNode.processorsMap = {};
     96         processNode.processorsMap[Node.PROCESSING_INSTRUCTION_NODE] = processProcessingInstruction;
     97         processNode.processorsMap[Node.ELEMENT_NODE] = processElement;
     98         processNode.processorsMap[Node.COMMENT_NODE] = processComment;
     99         processNode.processorsMap[Node.TEXT_NODE] = processText;
    100         processNode.processorsMap[Node.CDATA_SECTION_NODE] = processCDATA;
    101     }
    102     if (processNode.processorsMap[node.nodeType])
    103         processNode.processorsMap[node.nodeType].call(this, parentElement, node);
    104 }
    105 
    106 function processElement(parentElement, node)
    107 {
    108     if (!node.firstChild)
    109         processEmptyElement(parentElement, node);
    110     else {
    111         var child = node.firstChild;
    112         if (child.nodeType == Node.TEXT_NODE && isShort(child.nodeValue) && !child.nextSibling)
    113             processShortTextOnlyElement(parentElement, node);
    114         else
    115             processComplexElement(parentElement, node);
    116     }
    117 }
    118 
    119 function processEmptyElement(parentElement, node)
    120 {
    121     var line = createLine();
    122     line.appendChild(createTag(node, false, true));
    123     parentElement.appendChild(line);
    124 }
    125 
    126 function processShortTextOnlyElement(parentElement, node)
    127 {
    128     var line = createLine();
    129     line.appendChild(createTag(node, false, false));
    130     for (var child = node.firstChild; child; child = child.nextSibling)
    131         line.appendChild(createText(child.nodeValue));
    132     line.appendChild(createTag(node, true, false));
    133     parentElement.appendChild(line);
    134 }
    135 
    136 function processComplexElement(parentElement, node)
    137 {
    138     var collapsible = createCollapsible();
    139 
    140     collapsible.expanded.start.appendChild(createTag(node, false, false));
    141     for (var child = node.firstChild; child; child = child.nextSibling)
    142         nodeParentPairs.push({parentElement: collapsible.expanded.content, node: child});
    143     collapsible.expanded.end.appendChild(createTag(node, true, false));
    144 
    145     collapsible.collapsed.content.appendChild(createTag(node, false, false));
    146     collapsible.collapsed.content.appendChild(createText('...'));
    147     collapsible.collapsed.content.appendChild(createTag(node, true, false));
    148     parentElement.appendChild(collapsible);
    149 }
    150 
    151 function processComment(parentElement, node)
    152 {
    153     if (isShort(node.nodeValue)) {
    154         var line = createLine();
    155         line.appendChild(createComment('<!-- ' + node.nodeValue + ' -->'));
    156         parentElement.appendChild(line);
    157     } else {
    158         var collapsible = createCollapsible();
    159 
    160         collapsible.expanded.start.appendChild(createComment('<!--'));
    161         collapsible.expanded.content.appendChild(createComment(node.nodeValue));
    162         collapsible.expanded.end.appendChild(createComment('-->'));
    163 
    164         collapsible.collapsed.content.appendChild(createComment('<!--'));
    165         collapsible.collapsed.content.appendChild(createComment('...'));
    166         collapsible.collapsed.content.appendChild(createComment('-->'));
    167         parentElement.appendChild(collapsible);
    168     }
    169 }
    170 
    171 function processCDATA(parentElement, node)
    172 {
    173     if (isShort(node.nodeValue)) {
    174         var line = createLine();
    175         line.appendChild(createText('<![CDATA[ ' + node.nodeValue + ' ]]>'));
    176         parentElement.appendChild(line);
    177     } else {
    178         var collapsible = createCollapsible();
    179 
    180         collapsible.expanded.start.appendChild(createText('<![CDATA['));
    181         collapsible.expanded.content.appendChild(createText(node.nodeValue));
    182         collapsible.expanded.end.appendChild(createText(']]>'));
    183 
    184         collapsible.collapsed.content.appendChild(createText('<![CDATA['));
    185         collapsible.collapsed.content.appendChild(createText('...'));
    186         collapsible.collapsed.content.appendChild(createText(']]>'));
    187         parentElement.appendChild(collapsible);
    188     }
    189 }
    190 
    191 function processProcessingInstruction(parentElement, node)
    192 {
    193     if (isShort(node.nodeValue)) {
    194         var line = createLine();
    195         line.appendChild(createComment('<?' + node.nodeName + ' ' + node.nodeValue + '?>'));
    196         parentElement.appendChild(line);
    197     } else {
    198         var collapsible = createCollapsible();
    199 
    200         collapsible.expanded.start.appendChild(createComment('<?' + node.nodeName));
    201         collapsible.expanded.content.appendChild(createComment(node.nodeValue));
    202         collapsible.expanded.end.appendChild(createComment('?>'));
    203 
    204         collapsible.collapsed.content.appendChild(createComment('<?' + node.nodeName));
    205         collapsible.collapsed.content.appendChild(createComment('...'));
    206         collapsible.collapsed.content.appendChild(createComment('?>'));
    207         parentElement.appendChild(collapsible);
    208     }
    209 }
    210 
    211 function processText(parentElement, node)
    212 {
    213     parentElement.appendChild(createText(node.nodeValue));
    214 }
    215 
    216 // Processing utils.
    217 
    218 function trim(value)
    219 {
    220     return value.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    221 }
    222 
    223 function isShort(value)
    224 {
    225     return trim(value).length <= 50;
    226 }
    227 
    228 // Tree rendering.
    229 
    230 function createHTMLElement(elementName)
    231 {
    232     return document.createElementNS('http://www.w3.org/1999/xhtml', elementName)
    233 }
    234 
    235 function createCollapsible()
    236 {
    237     var collapsible = createHTMLElement('div');
    238     collapsible.classList.add('collapsible');
    239     collapsible.expanded = createHTMLElement('div');
    240     collapsible.expanded.classList.add('expanded');
    241     collapsible.appendChild(collapsible.expanded);
    242 
    243     collapsible.expanded.start = createLine();
    244     collapsible.expanded.start.appendChild(createCollapseButton());
    245     collapsible.expanded.appendChild(collapsible.expanded.start);
    246 
    247     collapsible.expanded.content = createHTMLElement('div');
    248     collapsible.expanded.content.classList.add('collapsible-content');
    249     collapsible.expanded.appendChild(collapsible.expanded.content);
    250 
    251     collapsible.expanded.end = createLine();
    252     collapsible.expanded.appendChild(collapsible.expanded.end);
    253 
    254     collapsible.collapsed = createHTMLElement('div');
    255     collapsible.collapsed.classList.add('collapsed');
    256     collapsible.collapsed.classList.add('hidden');
    257     collapsible.appendChild(collapsible.collapsed);
    258     collapsible.collapsed.content = createLine();
    259     collapsible.collapsed.content.appendChild(createExpandButton());
    260     collapsible.collapsed.appendChild(collapsible.collapsed.content);
    261 
    262     return collapsible;
    263 }
    264 
    265 function createButton()
    266 {
    267     var button = createHTMLElement('span');
    268     button.classList.add('button');
    269     return button;
    270 }
    271 
    272 function createCollapseButton(str)
    273 {
    274     var button = createButton();
    275     button.classList.add('collapse-button');
    276     return button;
    277 }
    278 
    279 function createExpandButton(str)
    280 {
    281     var button = createButton();
    282     button.classList.add('expand-button');
    283     return button;
    284 }
    285 
    286 function createComment(commentString)
    287 {
    288     var comment = createHTMLElement('span');
    289     comment.classList.add('comment');
    290     comment.classList.add('webkit-html-comment');
    291     comment.textContent = commentString;
    292     return comment;
    293 }
    294 
    295 function createText(value)
    296 {
    297     var text = createHTMLElement('span');
    298     text.textContent = trim(value);
    299     text.classList.add('text');
    300     return text;
    301 }
    302 
    303 function createLine()
    304 {
    305     var line = createHTMLElement('div');
    306     line.classList.add('line');
    307     return line;
    308 }
    309 
    310 function createTag(node, isClosing, isEmpty)
    311 {
    312     var tag = createHTMLElement('span');
    313     tag.classList.add('webkit-html-tag');
    314 
    315     var stringBeforeAttrs = '<';
    316     if (isClosing)
    317         stringBeforeAttrs += '/';
    318     stringBeforeAttrs += node.nodeName;
    319     var textBeforeAttrs = document.createTextNode(stringBeforeAttrs);
    320     tag.appendChild(textBeforeAttrs);
    321 
    322     if (!isClosing) {
    323         for (var i = 0; i < node.attributes.length; i++)
    324             tag.appendChild(createAttribute(node.attributes[i]));
    325     }
    326 
    327     var stringAfterAttrs = '';
    328     if (isEmpty)
    329         stringAfterAttrs += '/';
    330     stringAfterAttrs += '>';
    331     var textAfterAttrs = document.createTextNode(stringAfterAttrs);
    332     tag.appendChild(textAfterAttrs);
    333 
    334     return tag;
    335 }
    336 
    337 function createAttribute(attributeNode)
    338 {
    339     var attribute = createHTMLElement('span');
    340     attribute.classList.add('webkit-html-attribute');
    341 
    342     var attributeName = createHTMLElement('span');
    343     attributeName.classList.add('webkit-html-attribute-name');
    344     attributeName.textContent = attributeNode.name;
    345 
    346     var textBefore = document.createTextNode(' ');
    347     var textBetween = document.createTextNode('="');
    348 
    349     var attributeValue = createHTMLElement('span');
    350     attributeValue.classList.add('webkit-html-attribute-value');
    351     attributeValue.textContent = attributeNode.value;
    352 
    353     var textAfter = document.createTextNode('"');
    354 
    355     attribute.appendChild(textBefore);
    356     attribute.appendChild(attributeName);
    357     attribute.appendChild(textBetween);
    358     attribute.appendChild(attributeValue);
    359     attribute.appendChild(textAfter);
    360     return attribute;
    361 }
    362 
    363 // Tree behaviour.
    364 
    365 function drawArrows()
    366 {
    367     var ctx = document.getCSSCanvasContext("2d", "arrowRight", 10, 11);
    368 
    369     ctx.fillStyle = "rgb(90,90,90)";
    370     ctx.beginPath();
    371     ctx.moveTo(0, 0);
    372     ctx.lineTo(0, 8);
    373     ctx.lineTo(7, 4);
    374     ctx.lineTo(0, 0);
    375     ctx.fill();
    376     ctx.closePath();
    377 
    378     var ctx = document.getCSSCanvasContext("2d", "arrowDown", 10, 10);
    379 
    380     ctx.fillStyle = "rgb(90,90,90)";
    381     ctx.beginPath();
    382     ctx.moveTo(0, 0);
    383     ctx.lineTo(8, 0);
    384     ctx.lineTo(4, 7);
    385     ctx.lineTo(0, 0);
    386     ctx.fill();
    387     ctx.closePath();
    388 }
    389 
    390 function expandFunction(sectionId)
    391 {
    392     return function()
    393     {
    394         document.querySelector('#' + sectionId + ' > .expanded').className = 'expanded';
    395         document.querySelector('#' + sectionId + ' > .collapsed').className = 'collapsed hidden';
    396     };
    397 }
    398 
    399 function collapseFunction(sectionId)
    400 {
    401     return function()
    402     {
    403         document.querySelector('#' + sectionId + ' > .expanded').className = 'expanded hidden';
    404         document.querySelector('#' + sectionId + ' > .collapsed').className = 'collapsed';
    405     };
    406 }
    407 
    408 function initButtons()
    409 {
    410     var sections = document.querySelectorAll('.collapsible');
    411     for (var i = 0; i < sections.length; i++) {
    412         var sectionId = 'collapsible' + i;
    413         sections[i].id = sectionId;
    414 
    415         var expandedPart = sections[i].querySelector('#' + sectionId + ' > .expanded');
    416         var collapseButton = expandedPart.querySelector('.collapse-button');
    417         collapseButton.onclick = collapseFunction(sectionId);
    418         collapseButton.onmousedown = handleButtonMouseDown;
    419 
    420         var collapsedPart = sections[i].querySelector('#' + sectionId + ' > .collapsed');
    421         var expandButton = collapsedPart.querySelector('.expand-button');
    422         expandButton.onclick = expandFunction(sectionId);
    423         expandButton.onmousedown = handleButtonMouseDown;
    424     }
    425 
    426 }
    427 
    428 function handleButtonMouseDown(e)
    429 {
    430    // To prevent selection on double click
    431    e.preventDefault();
    432 }
    433