63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
import { elementFactory, requireFile, setButtonLabel } from "./dom.ts";
|
|
import { branch, leaf } from "./tree.ts";
|
|
import type { ViewerMount } from "../types.ts";
|
|
|
|
function label(node: Element): string {
|
|
const attributes = [...node.attributes]
|
|
.map((attribute) => `${attribute.name}="${attribute.value}"`)
|
|
.join(" ");
|
|
return `<${node.tagName}${attributes ? ` ${attributes}` : ""}>`;
|
|
}
|
|
|
|
function render(doc: Document, node: Element, depth: number): HTMLElement {
|
|
const children = [...node.childNodes].filter(
|
|
(child) => child.nodeType === 1 || (child.textContent ?? "").trim(),
|
|
);
|
|
const elements = children.filter((child) => child.nodeType === 1) as Element[];
|
|
if (!elements.length)
|
|
return leaf(doc, label(node), (node.textContent ?? "").trim(), "wrn-viewer-value");
|
|
return branch(
|
|
doc,
|
|
label(node),
|
|
"wrn-viewer-key",
|
|
(into) => {
|
|
for (const child of children)
|
|
into.append(
|
|
child.nodeType === 1
|
|
? render(doc, child as Element, depth + 1)
|
|
: leaf(doc, "", (child.textContent ?? "").trim(), "wrn-viewer-value"),
|
|
);
|
|
},
|
|
depth < 2,
|
|
);
|
|
}
|
|
|
|
export const mountXml: ViewerMount = async ({ parent, file, button, signal }) => {
|
|
const doc = parent.ownerDocument;
|
|
const view = doc.defaultView;
|
|
const element = elementFactory(doc);
|
|
const text = await requireFile(file).text();
|
|
if (signal.aborted) return;
|
|
if (!view?.DOMParser) throw new Error("No XML parser available");
|
|
const parsed = new view.DOMParser().parseFromString(text, "application/xml");
|
|
const root = parsed.documentElement;
|
|
// Malformed XML throws, and the ladder falls through to the source view.
|
|
if (!root || parsed.getElementsByTagName("parsererror").length)
|
|
throw new Error("Malformed XML document");
|
|
|
|
const tree = element("div");
|
|
tree.className = "wrn-viewer-tree";
|
|
tree.append(render(doc, root, 0));
|
|
const raw = element("pre", text);
|
|
|
|
let showingRaw = false;
|
|
const toggle = button("Raw", () => {
|
|
showingRaw = !showingRaw;
|
|
setButtonLabel(toggle, showingRaw ? "Tree" : "Raw");
|
|
toggle.setAttribute("aria-pressed", String(showingRaw));
|
|
parent.replaceChildren(showingRaw ? raw : tree);
|
|
});
|
|
toggle.setAttribute("aria-pressed", "false");
|
|
parent.append(tree);
|
|
};
|