From a84908b7e8a0e2423afd6b836eccf27a420270b4 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 20 Sep 2023 19:56:52 +0900 Subject: feat(blog/nuldoc): change content format from DocBook to NulDoc --- vhosts/blog/nuldoc-src/commands/build.ts | 8 +- vhosts/blog/nuldoc-src/commands/new.ts | 72 ++--- .../blog/nuldoc-src/components/post_page_entry.ts | 2 +- vhosts/blog/nuldoc-src/docbook/document.ts | 108 ------- vhosts/blog/nuldoc-src/docbook/parse.ts | 21 -- vhosts/blog/nuldoc-src/docbook/to_html.ts | 319 --------------------- vhosts/blog/nuldoc-src/dom.ts | 6 - vhosts/blog/nuldoc-src/ndoc/document.ts | 56 ++++ vhosts/blog/nuldoc-src/ndoc/parse.ts | 47 +++ vhosts/blog/nuldoc-src/ndoc/to_html.ts | 235 +++++++++++++++ vhosts/blog/nuldoc-src/pages/post.ts | 10 +- vhosts/blog/nuldoc-src/pages/slide.ts | 2 +- vhosts/blog/nuldoc-src/renderers/html.ts | 2 + vhosts/blog/nuldoc-src/slide/parse.ts | 26 +- vhosts/blog/nuldoc-src/slide/slide.ts | 111 ++++--- vhosts/blog/nuldoc-src/xml.ts | 4 - 16 files changed, 449 insertions(+), 580 deletions(-) delete mode 100644 vhosts/blog/nuldoc-src/docbook/document.ts delete mode 100644 vhosts/blog/nuldoc-src/docbook/parse.ts delete mode 100644 vhosts/blog/nuldoc-src/docbook/to_html.ts create mode 100644 vhosts/blog/nuldoc-src/ndoc/document.ts create mode 100644 vhosts/blog/nuldoc-src/ndoc/parse.ts create mode 100644 vhosts/blog/nuldoc-src/ndoc/to_html.ts (limited to 'vhosts/blog/nuldoc-src') diff --git a/vhosts/blog/nuldoc-src/commands/build.ts b/vhosts/blog/nuldoc-src/commands/build.ts index da7e5cec..92230d7d 100644 --- a/vhosts/blog/nuldoc-src/commands/build.ts +++ b/vhosts/blog/nuldoc-src/commands/build.ts @@ -2,7 +2,7 @@ import { dirname, join, joinGlobs } from "std/path/mod.ts"; import { ensureDir } from "std/fs/mod.ts"; import { expandGlob } from "std/fs/expand_glob.ts"; import { Config } from "../config.ts"; -import { parseDocBookFile } from "../docbook/parse.ts"; +import { parseNulDocFile } from "../ndoc/parse.ts"; import { Page } from "../page.ts"; import { render } from "../render.ts"; import { dateToString } from "../revision.ts"; @@ -48,7 +48,7 @@ async function buildPostPages(config: Config): Promise { async function collectPostFiles(sourceDir: string): Promise { const filePaths = []; - const globPattern = joinGlobs([sourceDir, "**", "*.xml"]); + const globPattern = joinGlobs([sourceDir, "**", "*.ndoc"]); for await (const entry of expandGlob(globPattern)) { filePaths.push(entry.path); } @@ -62,7 +62,7 @@ async function parsePosts( const posts = []; for (const postFile of postFiles) { posts.push( - await generatePostPage(await parseDocBookFile(postFile, config), config), + await generatePostPage(await parseNulDocFile(postFile, config), config), ); } return posts; @@ -85,7 +85,7 @@ async function buildSlidePages(config: Config): Promise { async function collectSlideFiles(sourceDir: string): Promise { const filePaths = []; - const globPattern = joinGlobs([sourceDir, "**", "*.xml"]); + const globPattern = joinGlobs([sourceDir, "**", "*.toml"]); for await (const entry of expandGlob(globPattern)) { filePaths.push(entry.path); } diff --git a/vhosts/blog/nuldoc-src/commands/new.ts b/vhosts/blog/nuldoc-src/commands/new.ts index 22329972..36125755 100644 --- a/vhosts/blog/nuldoc-src/commands/new.ts +++ b/vhosts/blog/nuldoc-src/commands/new.ts @@ -21,7 +21,7 @@ export async function runNewCommand(config: Config) { config.locations.contentDir, getDirPath(type), ymd, - "TODO.xml", + "TODO.ndoc", ); await ensureDir(dirname(destFilePath)); @@ -39,54 +39,40 @@ function getDirPath(type: "post" | "slide"): string { function getTemplate(type: "post" | "slide", date: string): string { if (type === "post") { - return ` -
- - TODO - - TODO - - - TODO - - - - ${date} - 公開 - - - -
+ return `--- +[article] +title = "TODO" +description = "TODO" +tags = [ + "TODO", +] + +[[article.revisions]] +date = "${date}" +remark = "公開" +--- +
+
TODO - +

TODO - +

`; } else { - return ` - - - TODO - - TODO - - - TODO - - TODO - - TODO - - - - ${date} - 登壇 - - - - + return `[slide] +title = "TODO" +event = "TODO" +talkType = "TODO" +link = "TODO" +tags = [ + "TODO", +] + +[[slide.revisions]] +date = "${date}" +remark = "登壇" `; } } diff --git a/vhosts/blog/nuldoc-src/components/post_page_entry.ts b/vhosts/blog/nuldoc-src/components/post_page_entry.ts index 685c03a8..bed7698a 100644 --- a/vhosts/blog/nuldoc-src/components/post_page_entry.ts +++ b/vhosts/blog/nuldoc-src/components/post_page_entry.ts @@ -21,7 +21,7 @@ export function postPageEntry(post: PostPage): Element { el( "section", [["class", "entry-content"]], - el("p", [], text(post.summary)), + el("p", [], text(post.description)), ), el( "footer", diff --git a/vhosts/blog/nuldoc-src/docbook/document.ts b/vhosts/blog/nuldoc-src/docbook/document.ts deleted file mode 100644 index 677c8275..00000000 --- a/vhosts/blog/nuldoc-src/docbook/document.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { join } from "std/path/mod.ts"; -import { Config } from "../config.ts"; -import { DocBookError } from "../errors.ts"; -import { Revision, stringToDate } from "../revision.ts"; -import { - Element, - findChildElements, - findFirstChildElement, - innerText, -} from "../dom.ts"; - -export type Document = { - root: Element; - sourceFilePath: string; - link: string; - title: string; - summary: string; // TODO: should it be markup text? - tags: string[]; - revisions: Revision[]; -}; - -export function createNewDocumentFromRootElement( - root: Element, - sourceFilePath: string, - config: Config, -): Document { - const article = findFirstChildElement(root, "article"); - if (!article) { - throw new DocBookError( - `[docbook.new]
element not found`, - ); - } - const info = findFirstChildElement(article, "info"); - if (!info) { - throw new DocBookError( - `[docbook.new] element not found`, - ); - } - - const titleElement = findFirstChildElement(info, "title"); - if (!titleElement) { - throw new DocBookError( - `[docbook.new] element not found`, - ); - } - const title = innerText(titleElement).trim(); - const abstractElement = findFirstChildElement(info, "abstract"); - if (!abstractElement) { - throw new DocBookError( - `[docbook.new] <abstract> element not found`, - ); - } - const summary = innerText(abstractElement).trim(); - const keywordsetElement = findFirstChildElement(info, "keywordset"); - let tags: string[]; - if (!keywordsetElement) { - tags = []; - } else { - tags = findChildElements(keywordsetElement, "keyword").map((x) => - innerText(x).trim() - ); - } - const revhistoryElement = findFirstChildElement(info, "revhistory"); - if (!revhistoryElement) { - throw new DocBookError( - `[docbook.new] <revhistory> element not found`, - ); - } - const revisions = findChildElements(revhistoryElement, "revision").map( - (x, i) => { - const dateElement = findFirstChildElement(x, "date"); - if (!dateElement) { - throw new DocBookError( - `[docbook.new] <date> element not found`, - ); - } - const revremarkElement = findFirstChildElement(x, "revremark"); - if (!revremarkElement) { - throw new DocBookError( - `[docbook.new] <revremark> element not found`, - ); - } - return { - number: i + 1, - date: stringToDate(innerText(dateElement).trim()), - remark: innerText(revremarkElement).trim(), - }; - }, - ); - if (revisions.length === 0) { - throw new DocBookError( - `[docbook.new] <revision> element not found`, - ); - } - - const cwd = Deno.cwd(); - const contentDir = join(cwd, config.locations.contentDir); - const link = sourceFilePath.replace(contentDir, "").replace(".xml", "/"); - return { - root: root, - sourceFilePath: sourceFilePath, - link: link, - title: title, - summary: summary, - tags: tags, - revisions: revisions, - }; -} diff --git a/vhosts/blog/nuldoc-src/docbook/parse.ts b/vhosts/blog/nuldoc-src/docbook/parse.ts deleted file mode 100644 index bce317e6..00000000 --- a/vhosts/blog/nuldoc-src/docbook/parse.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Config } from "../config.ts"; -import { parseXmlFile } from "../xml.ts"; -import { DocBookError, XmlParseError } from "../errors.ts"; -import { createNewDocumentFromRootElement, Document } from "./document.ts"; -import toHtml from "./to_html.ts"; - -export async function parseDocBookFile( - filePath: string, - config: Config, -): Promise<Document> { - try { - const root = await parseXmlFile(filePath); - const doc = createNewDocumentFromRootElement(root, filePath, config); - return toHtml(doc); - } catch (e) { - if (e instanceof DocBookError || e instanceof XmlParseError) { - e.message = `${e.message} in ${filePath}`; - } - throw e; - } -} diff --git a/vhosts/blog/nuldoc-src/docbook/to_html.ts b/vhosts/blog/nuldoc-src/docbook/to_html.ts deleted file mode 100644 index 4add912c..00000000 --- a/vhosts/blog/nuldoc-src/docbook/to_html.ts +++ /dev/null @@ -1,319 +0,0 @@ -// @deno-types="../types/highlight-js.d.ts" -import hljs from "npm:highlight.js"; -import { Document } from "./document.ts"; -import { DocBookError } from "../errors.ts"; -import { - addClass, - Element, - findFirstChildElement, - forEachChild, - forEachChildRecursively, - Node, - RawHTML, - removeChildElements, - Text, -} from "../dom.ts"; - -export default function toHtml(doc: Document): Document { - removeArticleInfo(doc); - removeArticleAttributes(doc); - removeUnnecessaryTextNode(doc); - transformElementNames(doc, "emphasis", "em"); - transformElementNames(doc, "informaltable", "table"); - transformElementNames(doc, "itemizedlist", "ul"); - transformElementNames(doc, "link", "a"); - transformElementNames(doc, "listitem", "li"); - transformElementNames(doc, "literal", "code"); - transformElementNames(doc, "orderedlist", "ol"); - transformElementNames(doc, "para", "p"); - transformElementNames(doc, "superscript", "sup"); - transformAttributeNames(doc, "xml:id", "id"); - transformAttributeNames(doc, "xl:href", "href"); - transformSectionIdAttribute(doc); - setSectionTitleAnchor(doc); - transformSectionTitleElement(doc); - transformProgramListingElement(doc); - transformLiteralLayoutElement(doc); - transformNoteElement(doc); - setDefaultLangAttribute(doc); - traverseFootnotes(doc); - highlightPrograms(doc); - return doc; -} - -function removeArticleInfo(doc: Document) { - const article = findFirstChildElement(doc.root, "article"); - if (!article) { - throw new DocBookError( - `[docbook.tohtml] <article> element not found`, - ); - } - removeChildElements(article, "info"); -} - -function removeArticleAttributes(doc: Document) { - const article = findFirstChildElement(doc.root, "article"); - if (!article) { - throw new DocBookError( - `[docbook.tohtml] <article> element not found`, - ); - } - article.attributes.delete("xmlns"); - article.attributes.delete("xmlns:xl"); - article.attributes.delete("version"); -} - -function removeUnnecessaryTextNode(doc: Document) { - const g = (n: Node) => { - if (n.kind !== "element") { - return; - } - - let changed = true; - while (changed) { - changed = false; - if (n.children.length === 0) { - break; - } - const firstChild = n.children[0]; - if (firstChild.kind === "text" && firstChild.content.trim() === "") { - n.children.shift(); - changed = true; - } - if (n.children.length === 0) { - break; - } - const lastChild = n.children[n.children.length - 1]; - if (lastChild.kind === "text" && lastChild.content.trim() === "") { - n.children.pop(); - changed = true; - } - } - - forEachChild(n, g); - }; - forEachChild(doc.root, g); -} - -function transformElementNames( - doc: Document, - from: string, - to: string, -) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind === "element" && n.name === from) { - n.name = to; - } - }); -} - -function transformAttributeNames( - doc: Document, - from: string, - to: string, -) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element") { - return; - } - const value = n.attributes.get(from) as string; - if (value !== undefined) { - n.attributes.delete(from); - n.attributes.set(to, value); - } - }); -} - -function transformSectionIdAttribute(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "section") { - return; - } - - const idAttr = n.attributes.get("id"); - n.attributes.set("id", `section--${idAttr}`); - }); -} - -function setSectionTitleAnchor(doc: Document) { - const sectionStack: Element[] = []; - const g = (c: Node) => { - if (c.kind !== "element") { - return; - } - - if (c.name === "section") { - sectionStack.push(c); - } - forEachChild(c, g); - if (c.name === "section") { - sectionStack.pop(); - } - if (c.name === "title") { - const currentSection = sectionStack[sectionStack.length - 1]; - if (!currentSection) { - throw new DocBookError( - "[docbook.tohtml] <title> element must be inside <section>", - ); - } - const sectionId = currentSection.attributes.get("id"); - const aElement: Element = { - kind: "element", - name: "a", - attributes: new Map(), - children: c.children, - }; - aElement.attributes.set("href", `#${sectionId}`); - c.children = [aElement]; - } - }; - forEachChild(doc.root, g); -} - -function transformSectionTitleElement(doc: Document) { - let sectionLevel = 1; - const g = (c: Node) => { - if (c.kind !== "element") { - return; - } - - if (c.name === "section") { - sectionLevel += 1; - c.attributes.set("--section-level", sectionLevel.toString()); - } - forEachChild(c, g); - if (c.name === "section") { - sectionLevel -= 1; - } - if (c.name === "title") { - c.name = `h${sectionLevel}`; - } - }; - forEachChild(doc.root, g); -} - -function transformProgramListingElement(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "programlisting") { - return; - } - - n.name = "pre"; - addClass(n, "highlight"); - const codeElement: Element = { - kind: "element", - name: "code", - attributes: new Map(), - children: n.children, - }; - n.children = [codeElement]; - }); -} - -function transformLiteralLayoutElement(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "literallayout") { - return; - } - - n.name = "pre"; - addClass(n, "highlight"); - const codeElement: Element = { - kind: "element", - name: "code", - attributes: new Map(), - children: n.children, - }; - n.children = [codeElement]; - }); -} - -function transformNoteElement(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "note") { - return; - } - - const labelElement: Element = { - kind: "element", - name: "div", - attributes: new Map([["class", "admonition-label"]]), - children: [{ - kind: "text", - content: "NOTE", - raw: false, - }], - }; - const contentElement: Element = { - kind: "element", - name: "div", - attributes: new Map([["class", "admonition-content"]]), - children: n.children, - }; - n.name = "div"; - addClass(n, "admonition"); - n.children = [ - labelElement, - contentElement, - ]; - }); -} - -function setDefaultLangAttribute(_doc: Document) { - // TODO - // if (!e.attributes.has("lang")) { - // e.attributes.set("lang", "ja-JP"); - // } -} - -function traverseFootnotes(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "footnote") { - return; - } - - // TODO - // <footnote>x</footnote> - // - // <sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1">1</a>]</sup> - // - // <div class="footnote" id="_footnotedef_1"> - // <a href="#_footnoteref_1">1</a>. RAS syndrome - // </div> - n.name = "span"; - n.children = []; - }); -} - -function highlightPrograms(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "pre") { - return; - } - const preClass = n.attributes.get("class") || ""; - if (!preClass.includes("highlight")) { - return; - } - const codeElement = findFirstChildElement(n, "code"); - if (!codeElement) { - return; - } - const language = n.attributes.get("language"); - if (!language) { - return; - } - const sourceCodeNode = codeElement.children[0] as Text | RawHTML; - const sourceCode = sourceCodeNode.content; - - if (!hljs.getLanguage(language)) { - return; - } - - const highlighted = - hljs.highlight(sourceCode, { language: language }).value; - - sourceCodeNode.content = highlighted; - sourceCodeNode.raw = true; - codeElement.attributes.set("class", "highlight"); - }); -} diff --git a/vhosts/blog/nuldoc-src/dom.ts b/vhosts/blog/nuldoc-src/dom.ts index d8f53d76..1147f01b 100644 --- a/vhosts/blog/nuldoc-src/dom.ts +++ b/vhosts/blog/nuldoc-src/dom.ts @@ -53,12 +53,6 @@ export function findChildElements(e: Element, name: string): Element[] { return cs; } -export function removeChildElements(e: Element, name: string) { - e.children = e.children.filter((c) => - c.kind !== "element" || c.name !== name - ); -} - export function innerText(e: Element): string { let t = ""; forEachChild(e, (c) => { diff --git a/vhosts/blog/nuldoc-src/ndoc/document.ts b/vhosts/blog/nuldoc-src/ndoc/document.ts new file mode 100644 index 00000000..31bae616 --- /dev/null +++ b/vhosts/blog/nuldoc-src/ndoc/document.ts @@ -0,0 +1,56 @@ +import { join } from "std/path/mod.ts"; +import { Config } from "../config.ts"; +import { DocBookError } from "../errors.ts"; +import { Revision, stringToDate } from "../revision.ts"; +import { Element, findFirstChildElement } from "../dom.ts"; + +export type Document = { + root: Element; + sourceFilePath: string; + link: string; + title: string; + description: string; // TODO: should it be markup text? + tags: string[]; + revisions: Revision[]; +}; + +export function createNewDocumentFromRootElement( + root: Element, + meta: { + article: { + title: string; + description: string; + tags: string[]; + revisions: { + date: string; + remark: string; + }[]; + }; + }, + sourceFilePath: string, + config: Config, +): Document { + const article = findFirstChildElement(root, "article"); + if (!article) { + throw new DocBookError( + `[docbook.new] <article> element not found`, + ); + } + + const cwd = Deno.cwd(); + const contentDir = join(cwd, config.locations.contentDir); + const link = sourceFilePath.replace(contentDir, "").replace(".xml", "/"); + return { + root: root, + sourceFilePath: sourceFilePath, + link: link, + title: meta.article.title, + description: meta.article.description, + tags: meta.article.tags, + revisions: meta.article.revisions.map((r, i) => ({ + number: i, + date: stringToDate(r.date), + remark: r.remark, + })), + }; +} diff --git a/vhosts/blog/nuldoc-src/ndoc/parse.ts b/vhosts/blog/nuldoc-src/ndoc/parse.ts new file mode 100644 index 00000000..419d2630 --- /dev/null +++ b/vhosts/blog/nuldoc-src/ndoc/parse.ts @@ -0,0 +1,47 @@ +import { parse as parseToml } from "std/encoding/toml.ts"; +import { Config } from "../config.ts"; +import { parseXmlString } from "../xml.ts"; +import { createNewDocumentFromRootElement, Document } from "./document.ts"; +import toHtml from "./to_html.ts"; + +export async function parseNulDocFile( + filePath: string, + config: Config, +): Promise<Document> { + try { + const fileContent = await Deno.readTextFile(filePath); + const parts = fileContent.split(/^---$/m); + const meta = parseMetaInfo(parts[1]); + const root = parseXmlString("<?xml ?>" + parts[2]); + const doc = createNewDocumentFromRootElement(root, meta, filePath, config); + return toHtml(doc); + } catch (e) { + e.message = `${e.message} in ${filePath}`; + throw e; + } +} + +function parseMetaInfo(s: string): { + article: { + title: string; + description: string; + tags: string[]; + revisions: { + date: string; + remark: string; + }[]; + }; +} { + const root = parseToml(s) as { + article: { + title: string; + description: string; + tags: string[]; + revisions: { + date: string; + remark: string; + }[]; + }; + }; + return root; +} diff --git a/vhosts/blog/nuldoc-src/ndoc/to_html.ts b/vhosts/blog/nuldoc-src/ndoc/to_html.ts new file mode 100644 index 00000000..dc39919b --- /dev/null +++ b/vhosts/blog/nuldoc-src/ndoc/to_html.ts @@ -0,0 +1,235 @@ +// @deno-types="../types/highlight-js.d.ts" +import hljs from "npm:highlight.js"; +import { Document } from "./document.ts"; +import { DocBookError } from "../errors.ts"; +import { + addClass, + Element, + findFirstChildElement, + forEachChild, + forEachChildRecursively, + Node, + RawHTML, + Text, +} from "../dom.ts"; + +export default function toHtml(doc: Document): Document { + removeUnnecessaryTextNode(doc); + transformSectionIdAttribute(doc); + setSectionTitleAnchor(doc); + transformSectionTitleElement(doc); + transformCodeBlockElement(doc); + transformNoteElement(doc); + setDefaultLangAttribute(doc); + traverseFootnotes(doc); + highlightPrograms(doc); + return doc; +} + +function removeUnnecessaryTextNode(doc: Document) { + const g = (n: Node) => { + if (n.kind !== "element") { + return; + } + + let changed = true; + while (changed) { + changed = false; + if (n.children.length === 0) { + break; + } + const firstChild = n.children[0]; + if (firstChild.kind === "text" && firstChild.content.trim() === "") { + n.children.shift(); + changed = true; + } + if (n.children.length === 0) { + break; + } + const lastChild = n.children[n.children.length - 1]; + if (lastChild.kind === "text" && lastChild.content.trim() === "") { + n.children.pop(); + changed = true; + } + } + + forEachChild(n, g); + }; + forEachChild(doc.root, g); +} + +function transformSectionIdAttribute(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "section") { + return; + } + + const idAttr = n.attributes.get("id"); + n.attributes.set("id", `section--${idAttr}`); + }); +} + +function setSectionTitleAnchor(doc: Document) { + const sectionStack: Element[] = []; + const g = (c: Node) => { + if (c.kind !== "element") { + return; + } + + if (c.name === "section") { + sectionStack.push(c); + } + forEachChild(c, g); + if (c.name === "section") { + sectionStack.pop(); + } + if (c.name === "h") { + const currentSection = sectionStack[sectionStack.length - 1]; + if (!currentSection) { + throw new DocBookError( + "[docbook.tohtml] <h> element must be inside <section>", + ); + } + const sectionId = currentSection.attributes.get("id"); + const aElement: Element = { + kind: "element", + name: "a", + attributes: new Map(), + children: c.children, + }; + aElement.attributes.set("href", `#${sectionId}`); + c.children = [aElement]; + } + }; + forEachChild(doc.root, g); +} + +function transformSectionTitleElement(doc: Document) { + let sectionLevel = 1; + const g = (c: Node) => { + if (c.kind !== "element") { + return; + } + + if (c.name === "section") { + sectionLevel += 1; + c.attributes.set("--section-level", sectionLevel.toString()); + } + forEachChild(c, g); + if (c.name === "section") { + sectionLevel -= 1; + } + if (c.name === "h") { + c.name = `h${sectionLevel}`; + } + }; + forEachChild(doc.root, g); +} + +function transformCodeBlockElement(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "codeblock") { + return; + } + + n.name = "pre"; + addClass(n, "highlight"); + const codeElement: Element = { + kind: "element", + name: "code", + attributes: new Map(), + children: n.children, + }; + n.children = [codeElement]; + }); +} + +function transformNoteElement(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "note") { + return; + } + + const labelElement: Element = { + kind: "element", + name: "div", + attributes: new Map([["class", "admonition-label"]]), + children: [{ + kind: "text", + content: "NOTE", + raw: false, + }], + }; + const contentElement: Element = { + kind: "element", + name: "div", + attributes: new Map([["class", "admonition-content"]]), + children: n.children, + }; + n.name = "div"; + addClass(n, "admonition"); + n.children = [ + labelElement, + contentElement, + ]; + }); +} + +function setDefaultLangAttribute(_doc: Document) { + // TODO + // if (!e.attributes.has("lang")) { + // e.attributes.set("lang", "ja-JP"); + // } +} + +function traverseFootnotes(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "footnote") { + return; + } + + // TODO + // <footnote>x</footnote> + // + // <sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1">1</a>]</sup> + // + // <div class="footnote" id="_footnotedef_1"> + // <a href="#_footnoteref_1">1</a>. RAS syndrome + // </div> + n.name = "span"; + n.children = []; + }); +} + +function highlightPrograms(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "pre") { + return; + } + const preClass = n.attributes.get("class") || ""; + if (!preClass.includes("highlight")) { + return; + } + const codeElement = findFirstChildElement(n, "code"); + if (!codeElement) { + return; + } + const language = n.attributes.get("language"); + if (!language) { + return; + } + const sourceCodeNode = codeElement.children[0] as Text | RawHTML; + const sourceCode = sourceCodeNode.content; + + if (!hljs.getLanguage(language)) { + return; + } + + const highlighted = + hljs.highlight(sourceCode, { language: language }).value; + + sourceCodeNode.content = highlighted; + sourceCodeNode.raw = true; + codeElement.attributes.set("class", "highlight"); + }); +} diff --git a/vhosts/blog/nuldoc-src/pages/post.ts b/vhosts/blog/nuldoc-src/pages/post.ts index 24a6d5f5..ade0916a 100644 --- a/vhosts/blog/nuldoc-src/pages/post.ts +++ b/vhosts/blog/nuldoc-src/pages/post.ts @@ -4,13 +4,13 @@ import { globalHeader } from "../components/global_header.ts"; import { pageLayout } from "../components/page_layout.ts"; import { Config, getTagLabel } from "../config.ts"; import { el, Element, text } from "../dom.ts"; -import { Document } from "../docbook/document.ts"; +import { Document } from "../ndoc/document.ts"; import { Page } from "../page.ts"; import { Date, dateToString, Revision } from "../revision.ts"; export interface PostPage extends Page { title: string; - summary: string; + description: string; tags: string[]; revisions: Revision[]; } @@ -112,7 +112,7 @@ export async function generatePostPage( const html = await pageLayout( { metaCopyrightYear: getPostCreatedDate(doc).year, - metaDescription: doc.summary, + metaDescription: doc.description, metaKeywords: doc.tags.map((slug) => getTagLabel(config, slug)), metaTitle: `${doc.title} | ${config.blog.siteName}`, requiresSyntaxHighlight: true, @@ -124,7 +124,7 @@ export async function generatePostPage( const cwd = Deno.cwd(); const contentDir = join(cwd, config.locations.contentDir); const destFilePath = join( - doc.sourceFilePath.replace(contentDir, "").replace(".xml", ""), + doc.sourceFilePath.replace(contentDir, "").replace(".ndoc", ""), "index.html", ); return { @@ -133,7 +133,7 @@ export async function generatePostPage( destFilePath: destFilePath, href: destFilePath.replace("index.html", ""), title: doc.title, - summary: doc.summary, + description: doc.description, tags: doc.tags, revisions: doc.revisions, }; diff --git a/vhosts/blog/nuldoc-src/pages/slide.ts b/vhosts/blog/nuldoc-src/pages/slide.ts index a75aeb68..ad56fee3 100644 --- a/vhosts/blog/nuldoc-src/pages/slide.ts +++ b/vhosts/blog/nuldoc-src/pages/slide.ts @@ -122,7 +122,7 @@ export async function generateSlidePage( const cwd = Deno.cwd(); const contentDir = join(cwd, config.locations.contentDir); const destFilePath = join( - slide.sourceFilePath.replace(contentDir, "").replace(".xml", ""), + slide.sourceFilePath.replace(contentDir, "").replace(".toml", ""), "index.html", ); return { diff --git a/vhosts/blog/nuldoc-src/renderers/html.ts b/vhosts/blog/nuldoc-src/renderers/html.ts index 3b6c6ebc..d3cd9893 100644 --- a/vhosts/blog/nuldoc-src/renderers/html.ts +++ b/vhosts/blog/nuldoc-src/renderers/html.ts @@ -87,6 +87,8 @@ function getDtd(name: string): Dtd { return { type: "block" }; case "span": return { type: "inline" }; + case "strong": + return { type: "inline" }; case "sup": return { type: "inline" }; case "table": diff --git a/vhosts/blog/nuldoc-src/slide/parse.ts b/vhosts/blog/nuldoc-src/slide/parse.ts index 00ff645f..45ac6388 100644 --- a/vhosts/blog/nuldoc-src/slide/parse.ts +++ b/vhosts/blog/nuldoc-src/slide/parse.ts @@ -1,19 +1,29 @@ +import { parse as parseToml } from "std/encoding/toml.ts"; import { Config } from "../config.ts"; -import { parseXmlFile } from "../xml.ts"; -import { SlideError, XmlParseError } from "../errors.ts"; -import { createNewSlideFromRootElement, Slide } from "./slide.ts"; +import { createNewSlideFromTomlRootObject, Slide } from "./slide.ts"; export async function parseSlideFile( filePath: string, config: Config, ): Promise<Slide> { try { - const root = await parseXmlFile(filePath); - return createNewSlideFromRootElement(root, filePath, config); + // TODO runtime assertion + const root = parseToml(await Deno.readTextFile(filePath)) as { + slide: { + title: string; + event: string; + talkType: string; + link: string; + tags: string[]; + revisions: { + date: string; + remark: string; + }[]; + }; + }; + return createNewSlideFromTomlRootObject(root, filePath, config); } catch (e) { - if (e instanceof SlideError || e instanceof XmlParseError) { - e.message = `${e.message} in ${filePath}`; - } + e.message = `${e.message} in ${filePath}`; throw e; } } diff --git a/vhosts/blog/nuldoc-src/slide/slide.ts b/vhosts/blog/nuldoc-src/slide/slide.ts index a982d4f2..5d5f30eb 100644 --- a/vhosts/blog/nuldoc-src/slide/slide.ts +++ b/vhosts/blog/nuldoc-src/slide/slide.ts @@ -1,12 +1,6 @@ import { Config } from "../config.ts"; import { SlideError } from "../errors.ts"; import { Revision, stringToDate } from "../revision.ts"; -import { - Element, - findChildElements, - findFirstChildElement, - innerText, -} from "../dom.ts"; export type Slide = { sourceFilePath: string; @@ -18,105 +12,102 @@ export type Slide = { revisions: Revision[]; }; -export function createNewSlideFromRootElement( - root: Element, +type Toml = { + slide: { + title: string; + event: string; + talkType: string; + link: string; + tags: string[]; + revisions: { + date: string; + remark: string; + }[]; + }; +}; + +export function createNewSlideFromTomlRootObject( + root: Toml, sourceFilePath: string, _config: Config, ): Slide { - const slide = findFirstChildElement(root, "slide"); - if (!slide) { + const slide = root.slide ?? null; + if (root.slide === undefined) { throw new SlideError( - `[slide.new] <slide> element not found`, - ); - } - const info = findFirstChildElement(slide, "info"); - if (!info) { - throw new SlideError( - `[slide.new] <info> element not found`, + `[slide.new] 'slide' field not found`, ); } - const titleElement = findFirstChildElement(info, "title"); - if (!titleElement) { + const title = slide.title ?? null; + if (!title) { throw new SlideError( - `[slide.new] <title> element not found`, + `[slide.new] 'slide.title' field not found`, ); } - const title = innerText(titleElement).trim(); - const eventElement = findFirstChildElement(info, "event"); - if (!eventElement) { + const event = slide.event ?? null; + if (!event) { throw new SlideError( - `[slide.new] <event> element not found`, + `[slide.new] 'slide.event' field not found`, ); } - const event = innerText(eventElement).trim(); - const talkTypeElement = findFirstChildElement(info, "talktype"); - if (!talkTypeElement) { + const talkType = slide.talkType ?? null; + if (!talkType) { throw new SlideError( - `[slide.new] <talktype> element not found`, + `[slide.new] 'slide.talkType' field not found`, ); } - const talkType = innerText(talkTypeElement).trim(); - const slideLinkElement = findFirstChildElement(info, "link"); - if (!slideLinkElement) { + const link = slide.link ?? null; + if (!link) { throw new SlideError( - `[slide.new] <link> element not found`, + `[slide.new] 'slide.link' field not found`, ); } - const slideLink = innerText(slideLinkElement).trim(); - const keywordsetElement = findFirstChildElement(info, "keywordset"); - let tags: string[]; - if (!keywordsetElement) { - tags = []; - } else { - tags = findChildElements(keywordsetElement, "keyword").map((x) => - innerText(x).trim() + const tags = slide.tags ?? []; + + const revisions = slide.revisions ?? null; + if (!revisions) { + throw new SlideError( + `[slide.new] 'slide.revisions' field not found`, ); } - const revhistoryElement = findFirstChildElement(info, "revhistory"); - if (!revhistoryElement) { + if (revisions.length === 0) { throw new SlideError( - `[slide.new] <revhistory> element not found`, + `[slide.new] 'slide.revisions' field not found`, ); } - const revisions = findChildElements(revhistoryElement, "revision").map( - (x, i) => { - const dateElement = findFirstChildElement(x, "date"); - if (!dateElement) { + const revisions_ = revisions.map( + (x: { date: string; remark: string }, i: number) => { + const date = x.date ?? null; + if (!date) { throw new SlideError( - `[slide.new] <date> element not found`, + `[slide.new] 'date' field not found`, ); } - const revremarkElement = findFirstChildElement(x, "revremark"); - if (!revremarkElement) { + const remark = x.remark ?? null; + if (!remark) { throw new SlideError( - `[slide.new] <revremark> element not found`, + `[slide.new] 'remark' field not found`, ); } return { number: i + 1, - date: stringToDate(innerText(dateElement).trim()), - remark: innerText(revremarkElement).trim(), + date: stringToDate(date), + remark: remark, }; }, ); - if (revisions.length === 0) { - throw new SlideError( - `[slide.new] <revision> element not found`, - ); - } return { sourceFilePath: sourceFilePath, title: title, event: event, talkType: talkType, - slideLink: slideLink, + slideLink: link, tags: tags, - revisions: revisions, + revisions: revisions_, }; } diff --git a/vhosts/blog/nuldoc-src/xml.ts b/vhosts/blog/nuldoc-src/xml.ts index 847b5e12..9f53ef8c 100644 --- a/vhosts/blog/nuldoc-src/xml.ts +++ b/vhosts/blog/nuldoc-src/xml.ts @@ -1,10 +1,6 @@ import { Element, Node, Text } from "./dom.ts"; import { XmlParseError } from "./errors.ts"; -export async function parseXmlFile(filePath: string): Promise<Element> { - return parseXmlString(await Deno.readTextFile(filePath)); -} - export function parseXmlString(source: string): Element { return parse({ source: source, index: 0 }); } -- cgit v1.2.3-70-g09d2