285 lines
10 KiB
HTML
285 lines
10 KiB
HTML
<!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>
|