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