<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> textarea { width: 100%; height: 250px; } </style> </head> <body> <form onsubmit="convert(event)"> <fieldset> <legend>Data</legend> <label for="input">Input</label> <br /> <textarea name="input" id="input" required></textarea> </fieldset> <fieldset> <legend>Output</legend> <label for="output">Output</label> <br /> <textarea name="output" id="output" disabled></textarea> <br /> <button type="button" onclick="window.navigator.clipboard.writeText(document.getElementById('output').value)" > Copy </button> </fieldset> <br /> <button>Submit</button> </form> <script> const INDENT_SPACES = 4; function get_indent_str(indent) { return " ".repeat(indent > 0 ? indent * INDENT_SPACES : 0); } function gen_lisp( element, indent = 0, add_n_after_elements = false, ) { const indent_str = get_indent_str(indent); let output = ""; for (const node of element.childNodes) { if (node.nodeName === "#text") { const data = node.data.trim(); if (!data) { continue; } if (data.startsWith("{")) { // templating let text = ""; for (const line of data .replaceAll('"', '\\"') .split("\n")) { text += ` ${line.trim()}`; } output += `${indent_str}(text "${text.trim().replaceAll('"', '\\"').replaceAll('\\\\"', '\\"').replaceAll("}} {{", "}}\n{{")}")\n`; } else { // normal text output += `${indent_str}(text "${data.replaceAll('"', '\\"')}")\n`; } } else if (node.localName) { output += `${indent_str}(${node.localName}\n`; // add attributes for (const attr of node.attributes || []) { output += `${get_indent_str(indent + 1)}("${attr.name}" "${attr.value.replaceAll('"', '\\\\\\"')}")\n`; } // add children output += gen_lisp(node, indent + 1); // close block output = output.slice(0, output.length - 1); // remove last character (new line) output += add_n_after_elements ? ")\n\n" : ")\n"; } else if (node.nodeName === "#comment") { let text = ""; for (const line of node.textContent.split("\n")) { if (line.trim() === "prettier-ignore") { continue; } text += `${indent_str};${line.trimEnd()}\n`; } output += text; } else { console.warn( "skipped element with no local name:", node, ); } } return output; } function convert(e) { e.preventDefault(); const element_document = new DOMParser().parseFromString( e.target.input.value, "text/html", ); const start = performance.now(); e.target.output.value = gen_lisp( element_document.body, 0, true, ); console.log( `processed ${e.target.input.value.length} characters in ${performance.now() - start}ms`, ); } </script> </body> </html>