2022-06-11 22:07:22 +03:00
|
|
|
import { AnyNode, Elem, Fragment, TextNode } from "../core/node.ts";
|
|
|
|
import { isNil } from "../core/utils.ts";
|
|
|
|
import { Parser } from "./types.ts";
|
|
|
|
|
2022-06-11 23:20:47 +03:00
|
|
|
const RE_NEW_LINE = /^\r?\n/;
|
|
|
|
|
2022-06-11 22:41:15 +03:00
|
|
|
const RE_OPEN_ATX_HEADING = /^\s{0,3}(#{1,6})(\s|$)/;
|
|
|
|
const RE_CLOSE_ATX_HEADING = /(^|\s+)#*\s*$/;
|
2022-06-11 22:07:22 +03:00
|
|
|
|
|
|
|
export class MarkdownParser implements Parser {
|
|
|
|
parse(input: string): AnyNode {
|
|
|
|
const ast: AstDocument = { kind: AstKind.Document, content: [] };
|
|
|
|
|
|
|
|
let readStr = input;
|
|
|
|
|
2022-06-11 23:20:47 +03:00
|
|
|
while (readStr.trim().length) {
|
|
|
|
{
|
|
|
|
// 1. clear new line character
|
|
|
|
const match = RE_NEW_LINE.exec(readStr);
|
|
|
|
if (!isNil(match)) {
|
|
|
|
readStr = readStr.slice(match[0].length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. try to find atx heading sequence
|
|
|
|
const match = RE_OPEN_ATX_HEADING.exec(readStr);
|
|
|
|
if (!isNil(match)) {
|
|
|
|
readStr = readStr.slice(match[0].length);
|
|
|
|
|
|
|
|
const atxHeading: AstAtxHeading = {
|
|
|
|
kind: AstKind.AtxHeading,
|
|
|
|
level: match[1].length as HeadingLevel,
|
|
|
|
content: [],
|
|
|
|
};
|
|
|
|
ast.content.push(atxHeading);
|
|
|
|
|
|
|
|
if (match[2].length > 0) {
|
|
|
|
const endMatch = RE_CLOSE_ATX_HEADING.exec(readStr);
|
|
|
|
|
|
|
|
const headingContent = !isNil(endMatch)
|
|
|
|
? readStr.slice(0, endMatch.index)
|
|
|
|
: readStr.includes("\n")
|
|
|
|
? readStr.slice(0, readStr.indexOf("\n") + 1)
|
|
|
|
: readStr;
|
|
|
|
readStr = readStr.slice(
|
|
|
|
headingContent.length + (endMatch?.[0].length ?? 0),
|
|
|
|
);
|
|
|
|
|
|
|
|
if (headingContent.length) {
|
|
|
|
const text: AstText = {
|
|
|
|
kind: AstKind.Text,
|
|
|
|
content: headingContent.trim(),
|
|
|
|
};
|
|
|
|
atxHeading.content.push(text);
|
|
|
|
}
|
2022-06-11 22:41:15 +03:00
|
|
|
}
|
2022-06-11 23:20:47 +03:00
|
|
|
} else {
|
|
|
|
break;
|
2022-06-11 22:07:22 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Fragment(ast.content.map(Heading));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-11 22:41:15 +03:00
|
|
|
function Heading(ast: AstAtxHeading): Elem {
|
2022-06-11 22:07:22 +03:00
|
|
|
return new Elem(`h${ast.level}`, {}, ast.content.map(Text));
|
|
|
|
}
|
|
|
|
|
|
|
|
function Text(ast: AstText): TextNode {
|
|
|
|
return new TextNode(ast.content);
|
|
|
|
}
|
|
|
|
|
|
|
|
// AST
|
|
|
|
|
|
|
|
type AstDocument = BaseAstItem<AstKind.Document, AstDocumentChild[]>;
|
2022-06-11 22:41:15 +03:00
|
|
|
type AstDocumentChild = AstAtxHeading;
|
2022-06-11 22:07:22 +03:00
|
|
|
|
2022-06-11 22:41:15 +03:00
|
|
|
interface AstAtxHeading extends BaseAstItem<AstKind.AtxHeading, AstText[]> {
|
2022-06-11 22:07:22 +03:00
|
|
|
level: HeadingLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
type AstText = BaseAstItem<AstKind.Text, string>;
|
|
|
|
|
|
|
|
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
|
|
|
|
|
|
interface BaseAstItem<K extends AstKind, Cont> {
|
|
|
|
kind: K;
|
|
|
|
content: Cont;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum AstKind {
|
|
|
|
Document,
|
2022-06-11 22:41:15 +03:00
|
|
|
AtxHeading,
|
2022-06-11 22:07:22 +03:00
|
|
|
Text,
|
|
|
|
}
|