blob: 580ffe540ffdb7f935665f356307235bdbc99eb3 [file] [log] [blame]
<!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>