ismism/src/doc/mdh.ts
2024-08-19 13:49:13 +08:00

214 lines
4.5 KiB
TypeScript

type Ln = {
l: string[],
n: number,
}
export type Block = {
tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
htm: string,
} | {
tag: "hr",
} | {
tag: "blockquote",
htm: string,
} | {
tag: "ol" | "ul",
li: string[],
} | {
tag: "table",
th: string[],
td: string[][],
} | {
tag: "p" | "pre" | "section",
htm: string,
}
function pln(
md: string
): Ln {
return { l: md.split("\n"), n: 0 }
}
function pl(
ln: Ln
): string {
return ln.l[ln.n]
}
function pn(
ln: Ln,
n: "check" | "back" | "line" | "block" = "line",
): boolean {
if (n == "back" && ln.n > 0) --ln.n
else if (n == "line") ++ln.n
else if (n == "block") while (ln.n < ln.l.length && ln.l[ln.n].trim() == "") ++ln.n
return ln.n < ln.l.length
}
function pb(
ln: Ln
): Block[] {
if (!pn(ln, "check")) return []
const lb: Block[] = []
while (pn(ln, "block")) {
let b: null | Block = null
for (const p of [ph, phr, pbq, pli, pt, pp])
if (b = p(ln)) { lb.push(b); break }
}
return lb
}
function pbi(
ln: Ln
): string {
const l = []
do {
if (pl(ln) == "") {
if (l.length > 0) l.push("")
continue
}
const m = pl(ln).match(/^( {4}|\t)(.*)/)
if (m) l.push(m[2])
else break
} while (pn(ln))
return l.length > 0 ? htm(pb({ l, n: 0 })) : ""
}
export function pmd(
md: string
): Block[] {
return pb(pln(md))
}
export function htm(
lb: Block[]
): string {
return lb.map(b => {
switch (b.tag) {
case "h1": case "h2": case "h3": case "h4": case "h5": case "h6":
case "blockquote": case "p": case "pre": case "section":
return `<${b.tag}>${b.htm}</${b.tag}>`
case "hr": return "<hr>"
case "ol": case "ul":
return `<${b.tag}>${b.li.map(li => `<li>${li}</li>`).join("\n")}</${b.tag}>`
case "table": {
const tr = (t: "th" | "td", c: string[]) =>
`<tr>${c.map(c => `<${t}>${c}</${t}>`).join("")}</tr>`
const [th, td] = [
(h: string[]) => tr("th", h),
(d: string[]) => tr("td", d),
]
return `<${b.tag}>${[th(b.th), ...b.td.map(td)].join("\n")}</${b.tag}>`
}
}
}).join("\n")
}
export function mdh(
md: string
): string {
return htm(pmd(md))
}
function ph(
ln: Ln
): null | Block & { tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" } {
const m = pl(ln).match(/(^#{1,6}) (.*$)/) //@ts-ignore
if (m) { pn(ln); return { tag: `h${m[1].length}`, htm: phtm(m[2]) } }
return null
}
function phr(
ln: Ln
): null | Block & { tag: "hr" } {
if (["***", "---", "___"].some(h => pl(ln).startsWith(h))) { pn(ln); return { tag: "hr" } }
return null
}
function pbq(
ln: Ln
): null | Block & { tag: "blockquote" } {
const l = []
do {
const m = pl(ln).match(/^> ?(.*$)/)
if (m) l.push(pure(m[1]))
else break
} while (pn(ln))
return l.length > 0 ? { tag: "blockquote", htm: htm(pb({ l, n: 0 })) } : null
}
function pli(
ln: Ln
): null | Block & { tag: "ol" | "ul" } {
let tag: null | "ol" | "ul" = null
const li: string[] = []
do {
const m = pl(ln).match(/^([*+-]|^\d+\.) (.*)/)
if (m == null && li.length > 0) {
const bi = pbi(ln)
if (bi == "") break
li[li.length - 1] += bi
pn(ln, "back"); continue
}
if (m == null) break
const t = m[1].endsWith(".") ? "ol" : "ul"
if (tag == null) tag = t
else if (tag != t) break
li.push(phtm(m[2]))
} while (pn(ln))
return tag ? { tag, li } : null
}
function pt(
ln: Ln
): null | Block & { tag: "table" } {
const [th, td]: [string[], string[][]] = [[], []]
let tag: null | "table" = null
do {
if (tag == null) {
if (pl(ln) == "```csv") { tag = "table"; continue }
else return null
} else if (pl(ln) == "```") { pn(ln); break }
if (th.length == 0) th.push(...pl(ln).split(","))
else td.push(pl(ln).split(","))
} while (pn(ln))
return { tag, th, td }
}
function pp(
ln: Ln
): Block & { tag: "p" | "pre" } {
const l = []
let tag: "p" | "pre" = "p"
do {
if (tag == "p") {
if (pl(ln) == "") break
if (pl(ln).startsWith("```")) { tag = "pre"; continue }
} else if (pl(ln) == "```") { pn(ln); break }
l.push(tag == "p" ? phtm(pl(ln)) : pl(ln))
} while (pn(ln))
return { tag, htm: l.join("\n") }
}
function pure(
t: string
): string {
return t.replaceAll("<", "﹤")
.replaceAll(">", "﹥")
.replaceAll("'", "\"")
}
function phtm(
t: string
): string {
return pure(t)
.replace(/\*{3,}([^\*]+)\*{3,}/g, "<b><em>$1</em></b>")
.replace(/\*{2}([^\*]+)\*{2}/g, "<b>$1</b>")
.replace(/\*+([^\*]+)\*+/g, "<i>$1</i>")
.replace(/`+([^`]+)`+/g, "<code>$1</code>")
.replace(/={2,}([^=]+)={2,}/g, "<mark>$1</mark>")
.replace(/!\[(.*?)\]\((.*?)\)/g, "<img alt='$1' src='$2'/>")
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2'>$1</a>")
.replace(/ {2,}$/g, "<br>")
}