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