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