| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <title>HTMLFormatter Tool</title> |
| |
| <style>:root { color-scheme: light dark; }</style> |
| <link rel="stylesheet" href="../../UserInterface/External/CodeMirror/codemirror.css"> |
| <link rel="stylesheet" href="../../UserInterface/Views/Variables.css"> |
| <link rel="stylesheet" href="../../UserInterface/Views/CodeMirrorOverrides.css"> |
| <link rel="stylesheet" href="../../UserInterface/Views/SyntaxHighlightingDefaultTheme.css"> |
| <link rel="stylesheet" href="styles.css"> |
| |
| <script src="../../UserInterface/External/CodeMirror/codemirror.js"></script> |
| <script src="../../UserInterface/External/CodeMirror/css.js"></script> |
| <script src="../../UserInterface/External/CodeMirror/htmlmixed.js"></script> |
| <script src="../../UserInterface/External/CodeMirror/javascript.js"></script> |
| <script src="../../UserInterface/External/CodeMirror/xml.js"></script> |
| <script src="../../UserInterface/Views/CodeMirrorLocalOverrideURLMode.js"></script> |
| <script src="../../UserInterface/Views/CodeMirrorRegexMode.js"></script> |
| |
| <script src="HTMLTreeBuilderDebug.js"></script> |
| <script src="../../UserInterface/External/Esprima/esprima.js"></script> |
| <script src="../../UserInterface/Workers/Formatter/CSSFormatter.js"></script> |
| <script src="../../UserInterface/Workers/Formatter/ESTreeWalker.js"></script> |
| <script src="../../UserInterface/Workers/Formatter/FormatterContentBuilder.js"></script> |
| <script src="../../UserInterface/Workers/Formatter/FormatterUtilities.js"></script> |
| <script src="../../UserInterface/Workers/Formatter/HTMLFormatter.js"></script> |
| <script src="../../UserInterface/Workers/Formatter/HTMLParser.js"></script> |
| <script src="../../UserInterface/Workers/Formatter/HTMLTreeBuilderFormatter.js"></script> |
| <script src="../../UserInterface/Workers/Formatter/JSFormatter.js"></script> |
| </head> |
| <body> |
| <h1>Debug HTMLFormatter</h1> |
| |
| <!-- Controls --> |
| <select id="populate"> |
| <option value="html-simple">Simple HTML Document</option> |
| <option value="svg-simple">Simple SVG Document</option> |
| <option value="html-css-js">HTML with Styles and Script</option> |
| <option value="self">Self</option> |
| </select> |
| <select id="source-type"> |
| <option value="text/html">HTML</option> |
| <option value="text/xml">XML</option> |
| </select> |
| <button id="format">Format</button> |
| <button id="select-output">Select Output</button> |
| <button id="save-as-url">Save URL</button> |
| <small id="time"></small> |
| <br><br> |
| |
| <!-- Editor --> |
| <textarea id="code" name="code"></textarea> |
| |
| <!-- Output --> |
| <h3>Formatted</h3> |
| <pre id="pretty"></pre> |
| <h3>Tree</h3> |
| <pre id="debug-tree"></pre> |
| <h3>Tokens</h3> |
| <pre id="debug"></pre> |
| |
| <script> |
| // Elements. |
| const populatePicker = document.getElementById("populate"); |
| const sourceTypePicker = document.getElementById("source-type"); |
| const timeOutput = document.getElementById("time"); |
| const prettyPre = document.getElementById("pretty"); |
| const debugPre = document.getElementById("debug"); |
| const debugTreePre = document.getElementById("debug-tree"); |
| |
| // Editor. |
| let cm = CodeMirror.fromTextArea(document.getElementById("code"), {lineNumbers: true}); |
| cm.setOption("mode", "text/html"); |
| |
| // Refresh after changes after a short delay. |
| let timer = null; |
| cm.on("change", function(codeMirror, change) { |
| if (timer) |
| clearTimeout(timer) |
| timer = setTimeout(function() { |
| clearTimeout(timer); |
| timer = null; |
| refresh(); |
| }, 500); |
| }); |
| |
| function refresh() { |
| if (timer) |
| clearTimeout(timer); |
| |
| // Time the formatter. |
| let sourceType = sourceTypePicker.value === "text/html" ? HTMLFormatter.SourceType.HTML : HTMLFormatter.SourceType.XML; |
| let startTime = Date.now(); |
| let formatter = new HTMLFormatter(cm.getValue(), sourceType); |
| let endTime = Date.now(); |
| |
| // Show debug parser info. |
| let debugText = ""; |
| try { |
| let options = {isXML: sourceType === HTMLFormatter.SourceType.XML}; |
| let parser = new HTMLParser; |
| let treeBuilder = new HTMLTreeBuilderDebug; |
| parser.parseDocument(cm.getValue(), treeBuilder, options); |
| debugText = treeBuilder.debugText; |
| } catch (error) { |
| debugText = "Parse error: " + JSON.stringify(error, null, 2); |
| } |
| |
| // Show debug tree info. |
| let debugTreeText = ""; |
| try { |
| let parser = new HTMLParser; |
| let treeBuilder = new HTMLTreeBuilderFormatter; |
| parser.parseDocument(cm.getValue(), treeBuilder); |
| console.log("TreeBuilder DOM", treeBuilder.dom); |
| |
| let lines = []; |
| let indentString = " "; |
| function filter(key, value) { |
| if (key === "children") |
| return undefined; |
| return value; |
| } |
| function stringifyNode(node) { |
| switch (node.type) { |
| case HTMLTreeBuilderFormatter.NodeType.Text: |
| return `TEXT: ${JSON.stringify(node.data)}`; |
| case HTMLTreeBuilderFormatter.NodeType.Comment: |
| return `COMMENT: (${node.data})`; |
| case HTMLTreeBuilderFormatter.NodeType.Doctype: |
| return `DOCTYPE: (${node.data})`; |
| case HTMLTreeBuilderFormatter.NodeType.CData: |
| return `CDATA: (${node.data})`; |
| case HTMLTreeBuilderFormatter.NodeType.Error: |
| return `ERROR: ${node.raw}`; |
| case HTMLTreeBuilderFormatter.NodeType.Node: { |
| let implicitCloseString = node.implicitClose ? " <implicitClose>" : ""; |
| let attributesString = node.attributes ? " " + JSON.stringify(node.attributes) : ""; |
| return `NODE: ${node.name}${attributesString}${implicitCloseString}`; |
| } |
| } |
| } |
| function visit(node, indent) { |
| lines.push(indentString.repeat(indent) + stringifyNode(node)); |
| if (node.children) { |
| for (let child of node.children) |
| visit(child, indent + 1); |
| } |
| } |
| for (let topLevelNode of treeBuilder.dom) |
| visit(topLevelNode, 0); |
| |
| debugTreeText = lines.join("\n"); |
| } catch (error) { |
| debugTreeText = "TreeBuilder error: " + JSON.stringify(error, null, 2); |
| } |
| |
| // Output the results. |
| timeOutput.innerText = (endTime - startTime) + "ms"; |
| prettyPre.innerText = formatter.formattedText; |
| debugPre.innerText = debugText; |
| debugTreePre.innerText = debugTreeText; |
| } |
| |
| setTimeout(refresh); |
| |
| // Format button. |
| document.getElementById("format").addEventListener("click", (event) => { |
| refresh(); |
| }); |
| |
| // Select output button. |
| document.getElementById("select-output").addEventListener("click", function(event) { |
| let range = document.createRange(); |
| range.selectNodeContents(prettyPre); |
| let selection = window.getSelection(); |
| selection.removeAllRanges(); |
| selection.addRange(range); |
| }); |
| |
| // Save as URL button. |
| document.getElementById("save-as-url").addEventListener("click", (event) => { |
| let content = cm.getValue(); |
| let populate = populatePicker.value; |
| let sourceType = sourceTypePicker.value; |
| window.location.search = `?content=${encodeURIComponent(content)}&populate=${encodeURIComponent(populate)}&sourceType=${encodeURIComponent(sourceType)}`; |
| }); |
| |
| const simpleHTML = `<!DOCTYPE html> |
| <html> |
| <head> |
| <title>Test</title> |
| <script src="js/script.js"></`+`script> |
| </head> |
| <body> |
| <!-- Comment --> |
| <div class="foo"><input type=text><br><p>Test</p></div> |
| <p><![CDATA[ Test ]]></p> |
| </body> |
| </html> |
| `; |
| const simpleSVG = `<?xml version="1.0"?> |
| <!-- Copyright © 2014 Apple Inc. All rights reserved. --> |
| <svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 13 14"> |
| <rect fill="none" stroke="currentColor" x="0.5" y="0.5" width="12" height="13" rx="2"/> |
| <rect fill="none" stroke="currentColor" stroke-width="1" x="3" y="6" width="7" height="2" rx="0.5"/> |
| </svg> |
| `; |
| |
| const htmlcssjs = `<!DOCTYPE html> |
| <html> |
| <head> |
| <title>Test</title> |
| <style>body,div,.foo{color:red}p{color:blue}</style> |
| <script>(function(a,b,c){let sum = a; sum += b; sum += c; return sum;})()</`+`script> |
| </head> |
| <body> |
| <!-- Comment --> |
| <div class="foo"><input type=text><br><p>Test</p></div> |
| </body> |
| </html> |
| `; |
| |
| // Populate picker |
| function updateContentFromPicker() { |
| let value = populatePicker.value; |
| let content = simpleHTML; |
| switch (value) { |
| case "html-simple": |
| content = simpleHTML; |
| break; |
| case "svg-simple": |
| content = simpleSVG; |
| break; |
| case "html-css-js": |
| content = htmlcssjs; |
| break; |
| case "self": |
| content = document.documentElement.outerHTML; |
| break; |
| } |
| cm.setValue(content); |
| } |
| |
| populatePicker.addEventListener("change", (event) => { |
| updateContentFromPicker(); |
| }); |
| |
| // Parser mode picker. |
| sourceTypePicker.addEventListener("change", (event) => { |
| cm.setOption("mode", sourceTypePicker.value); |
| refresh(); |
| }); |
| |
| // Restore better initial value from query string. |
| (function() { |
| let queryParams = {}; |
| if (window.location.search.length > 0) { |
| let searchString = window.location.search.substring(1); |
| let groups = searchString.split("&"); |
| for (let i = 0; i < groups.length; ++i) { |
| let pair = groups[i].split("="); |
| queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); |
| } |
| } |
| if (queryParams.populate) { |
| populatePicker.value = queryParams.populate; |
| updateContentFromPicker(); |
| } |
| if (queryParams.sourceType) { |
| sourceTypePicker.value = queryParams.sourceType; |
| cm.setOption("mode", sourceTypePicker.value); |
| } |
| if (queryParams.content) |
| cm.setValue(queryParams.content); |
| })(); |
| |
| if (!cm.getValue()) |
| cm.setValue(simpleHTML); |
| </script> |
| </body> |
| </html> |