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 root = document.getElementById('tree'); 76 77 for (var child = sourceXML.firstChild; child; child = child.nextSibling) 78 nodeParentPairs.push({parentElement: root, 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