diff options
Diffstat (limited to 'vhosts/blog/nuldoc-src/renderers')
| -rw-r--r-- | vhosts/blog/nuldoc-src/renderers/html.ts | 310 | ||||
| -rw-r--r-- | vhosts/blog/nuldoc-src/renderers/xml.ts | 130 |
2 files changed, 0 insertions, 440 deletions
diff --git a/vhosts/blog/nuldoc-src/renderers/html.ts b/vhosts/blog/nuldoc-src/renderers/html.ts deleted file mode 100644 index 84b3ebaa..00000000 --- a/vhosts/blog/nuldoc-src/renderers/html.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { Element, forEachChild, Node, Text } from "../dom.ts"; -import { NuldocError } from "../errors.ts"; - -export function renderHtml(root: Node): string { - return `<!DOCTYPE html>\n` + nodeToHtmlText(root, { - indentLevel: 0, - isInPre: false, - }); -} - -type Context = { - indentLevel: number; - isInPre: boolean; -}; - -type Dtd = { type: "block" | "inline"; self_closing?: boolean }; - -function getDtd(name: string): Dtd { - switch (name) { - case "a": - return { type: "inline" }; - case "article": - return { type: "block" }; - case "blockquote": - return { type: "block" }; - case "body": - return { type: "block" }; - case "br": - return { type: "block", self_closing: true }; - case "button": - return { type: "block" }; - case "canvas": - return { type: "block" }; - case "caption": - return { type: "block" }; - case "code": - return { type: "inline" }; - case "del": - return { type: "block" }; - case "div": - return { type: "block" }; - case "em": - return { type: "inline" }; - case "footer": - return { type: "block" }; - case "h1": - return { type: "inline" }; - case "h2": - return { type: "inline" }; - case "h3": - return { type: "inline" }; - case "h4": - return { type: "inline" }; - case "h5": - return { type: "inline" }; - case "h6": - return { type: "inline" }; - case "head": - return { type: "block" }; - case "header": - return { type: "block" }; - case "hr": - return { type: "block", self_closing: true }; - case "html": - return { type: "block" }; - case "i": - return { type: "inline" }; - case "li": - return { type: "block" }; - case "link": - return { type: "block", self_closing: true }; - case "img": - return { type: "inline", self_closing: true }; - case "ins": - return { type: "block" }; - case "main": - return { type: "block" }; - case "mark": - return { type: "inline" }; - case "meta": - return { type: "block", self_closing: true }; - case "nav": - return { type: "block" }; - case "noscript": - return { type: "block" }; - case "ol": - return { type: "block" }; - case "p": - return { type: "block" }; - case "pre": - return { type: "block" }; - case "script": - return { type: "block" }; - case "section": - return { type: "block" }; - case "span": - return { type: "inline" }; - case "strong": - return { type: "inline" }; - case "sub": - return { type: "inline" }; - case "sup": - return { type: "inline" }; - case "table": - return { type: "block" }; - case "tbody": - return { type: "block" }; - case "td": // TODO - return { type: "block" }; - case "tfoot": - return { type: "block" }; - case "th": - return { type: "block" }; - case "thead": - return { type: "block" }; - case "time": - return { type: "inline" }; - case "title": // TODO - return { type: "inline" }; - case "tr": - return { type: "block" }; - case "ul": - return { type: "block" }; - default: - throw new NuldocError(`[html.write] Unknown element name: ${name}`); - } -} - -function isInlineNode(n: Node): boolean { - if (n.kind === "text") { - return true; - } - if (n.name !== "a") { - return getDtd(n.name).type === "inline"; - } - - // a tag: check if all children are inline elements. - let allInline = true; - forEachChild(n, (c) => allInline &&= isInlineNode(c)); - return allInline; -} - -function isBlockNode(n: Node): boolean { - return !isInlineNode(n); -} - -function nodeToHtmlText(n: Node, ctx: Context): string { - if (n.kind === "text") { - if (n.raw) { - return n.content; - } else { - return textNodeToHtmlText(n, ctx); - } - } else { - return elementNodeToHtmlText(n, ctx); - } -} - -function textNodeToHtmlText(t: Text, ctx: Context): string { - const s = encodeSpecialCharacters(t.content); - if (ctx.isInPre) return s; - - return s.replaceAll(/\n */g, (_match, offset, subject) => { - const last_char = subject[offset - 1]; - if (last_char === "。" || last_char === "、") { - // 日本語で改行するときはスペースを入れない - return ""; - } else { - return " "; - } - }); -} - -function encodeSpecialCharacters(s: string): string { - return s.replaceAll(/&(?!\w+;)/g, "&") - .replaceAll(/</g, "<") - .replaceAll(/>/g, ">") - .replaceAll(/'/g, "'") - .replaceAll(/"/g, """); -} - -function elementNodeToHtmlText(e: Element, ctx: Context): string { - const dtd = getDtd(e.name); - - let s = ""; - - if (isBlockNode(e)) { - s += indent(ctx); - } - s += `<${e.name}`; - const attributes = getElementAttributes(e); - if (attributes.length > 0) { - s += " "; - for (let i = 0; i < attributes.length; i++) { - const [name, value] = attributes[i]; - if (name === "defer" && value === "true") { - // TODO - s += "defer"; - } else { - s += `${name === "className" ? "class" : name}="${ - encodeSpecialCharacters(value) - }"`; - } - if (i !== attributes.length - 1) { - s += " "; - } - } - } - s += ">"; - if (isBlockNode(e) && e.name !== "pre") { - s += "\n"; - } - - ctx.indentLevel += 1; - - let prevChild: Node | null = null; - if (e.name === "pre") { - ctx.isInPre = true; - } - forEachChild(e, (c) => { - if (isBlockNode(e) && !ctx.isInPre) { - if (isInlineNode(c)) { - if (needsIndent(prevChild)) { - s += indent(ctx); - } - } else { - if (needsLineBreak(prevChild)) { - s += "\n"; - } - } - } - s += nodeToHtmlText(c, ctx); - prevChild = c; - }); - if (e.name === "pre") { - ctx.isInPre = false; - } - - ctx.indentLevel -= 1; - if (!dtd.self_closing) { - if (e.name !== "pre") { - if (isBlockNode(e)) { - if (needsLineBreak(prevChild)) { - s += "\n"; - } - s += indent(ctx); - } - } - s += `</${e.name}>`; - if (isBlockNode(e)) { - s += "\n"; - } - } - return s; -} - -function indent(ctx: Context): string { - return " ".repeat(ctx.indentLevel); -} - -function getElementAttributes(e: Element): [string, string][] { - return [...e.attributes.entries()] - .filter((a) => !a[0].startsWith("--")) - .filter((a) => a[1] !== undefined) - .sort( - (a, b) => { - // Special rules: - if (e.name === "meta") { - if (a[0] === "content" && b[0] === "name") { - return 1; - } - if (a[0] === "name" && b[0] === "content") { - return -1; - } - if (a[0] === "content" && b[0] === "property") { - return 1; - } - if (a[0] === "property" && b[0] === "content") { - return -1; - } - } - if (e.name === "link") { - if (a[0] === "href" && b[0] === "rel") { - return 1; - } - if (a[0] === "rel" && b[0] === "href") { - return -1; - } - if (a[0] === "href" && b[0] === "type") { - return 1; - } - if (a[0] === "type" && b[0] === "href") { - return -1; - } - } - // General rules: - if (a[0] > b[0]) return 1; - else if (a[0] < b[0]) return -1; - else return 0; - }, - ); -} - -function needsIndent(prevChild: Node | null): boolean { - return !prevChild || isBlockNode(prevChild); -} - -function needsLineBreak(prevChild: Node | null): boolean { - return !needsIndent(prevChild); -} diff --git a/vhosts/blog/nuldoc-src/renderers/xml.ts b/vhosts/blog/nuldoc-src/renderers/xml.ts deleted file mode 100644 index 77cc1574..00000000 --- a/vhosts/blog/nuldoc-src/renderers/xml.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { Element, forEachChild, Node, Text } from "../dom.ts"; - -export function renderXml(root: Node): string { - return `<?xml version="1.0" encoding="utf-8"?>\n` + nodeToXmlText(root, { - indentLevel: 0, - }); -} - -type Context = { - indentLevel: number; -}; - -type Dtd = { type: "block" | "inline" }; - -function getDtd(name: string): Dtd { - switch (name) { - case "feed": - case "entry": - case "author": - return { type: "block" }; - default: - return { type: "inline" }; - } -} - -function isInlineNode(n: Node): boolean { - if (n.kind === "text") { - return true; - } - return getDtd(n.name).type === "inline"; -} - -function isBlockNode(n: Node): boolean { - return !isInlineNode(n); -} - -function nodeToXmlText(n: Node, ctx: Context): string { - if (n.kind === "text") { - if (n.raw) { - return n.content; - } else { - return textNodeToXmlText(n); - } - } else { - return elementNodeToXmlText(n, ctx); - } -} - -function textNodeToXmlText(t: Text): string { - const s = encodeSpecialCharacters(t.content); - - // TODO: 日本語で改行するときはスペースを入れない - return s.replaceAll(/\n */g, " "); -} - -function encodeSpecialCharacters(s: string): string { - return s.replaceAll(/&(?!\w+;)/g, "&") - .replaceAll(/</g, "<") - .replaceAll(/>/g, ">") - .replaceAll(/'/g, "'") - .replaceAll(/"/g, """); -} - -function elementNodeToXmlText(e: Element, ctx: Context): string { - let s = ""; - - s += indent(ctx); - s += `<${e.name}`; - const attributes = getElementAttributes(e); - if (attributes.length > 0) { - s += " "; - for (let i = 0; i < attributes.length; i++) { - const [name, value] = attributes[i]; - s += `${name}="${encodeSpecialCharacters(value)}"`; - if (i !== attributes.length - 1) { - s += " "; - } - } - } - s += ">"; - if (isBlockNode(e)) { - s += "\n"; - } - ctx.indentLevel += 1; - - forEachChild(e, (c) => { - s += nodeToXmlText(c, ctx); - }); - - ctx.indentLevel -= 1; - if (isBlockNode(e)) { - s += indent(ctx); - } - s += `</${e.name}>`; - s += "\n"; - - return s; -} - -function indent(ctx: Context): string { - return " ".repeat(ctx.indentLevel); -} - -function getElementAttributes(e: Element): [string, string][] { - return [...e.attributes.entries()] - .filter((a) => !a[0].startsWith("--")) - .sort( - (a, b) => { - // Special rules: - if (e.name === "link") { - if (a[0] === "href" && b[0] === "rel") { - return 1; - } - if (a[0] === "rel" && b[0] === "href") { - return -1; - } - if (a[0] === "href" && b[0] === "type") { - return 1; - } - if (a[0] === "type" && b[0] === "href") { - return -1; - } - } - // General rules: - if (a[0] > b[0]) return 1; - else if (a[0] < b[0]) return -1; - else return 0; - }, - ); -} |
