XMLViewer.js 14 KB

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