Archived
1
0
Fork 0

Compare commits

..

No commits in common. "328343ca76d892955bd4732c32aff9e72c319a64" and "ef1baea81bb18dba5de6b55d59c0bd0b233c4481" have entirely different histories.

2 changed files with 30 additions and 95 deletions

View file

@ -5,17 +5,7 @@ import { MarkdownParser } from "./md.ts";
const ren = new HtmlStrRenderer(); const ren = new HtmlStrRenderer();
Deno.test({ Deno.test({
name: "should skip new line character", name: "should parse header",
fn: () => {
const par = new MarkdownParser();
assertEquals(ren.render(par.parse("\n")), "");
assertEquals(ren.render(par.parse("\r\n")), "");
assertEquals(ren.render(par.parse("\n\r\n")), "");
},
});
Deno.test({
name: "should parse empty ATX header",
fn: () => { fn: () => {
const par = new MarkdownParser(); const par = new MarkdownParser();
const res = par.parse("#"); const res = par.parse("#");
@ -24,16 +14,16 @@ Deno.test({
}); });
Deno.test({ Deno.test({
name: "should parse ATX header with text", name: "should parse header with text",
fn: () => { fn: () => {
const par = new MarkdownParser(); const par = new MarkdownParser();
assertEquals(ren.render(par.parse("# hello")), "<h1>hello</h1>"); const res = par.parse("# hello");
assertEquals(ren.render(par.parse("# hello#")), "<h1>hello#</h1>"); assertEquals(ren.render(res), "<h1>hello</h1>");
}, },
}); });
Deno.test({ Deno.test({
name: "should parse ATX header with specific level", name: "should parse header with specific level",
fn: () => { fn: () => {
const par = new MarkdownParser(); const par = new MarkdownParser();
assertEquals(ren.render(par.parse("# hello")), "<h1>hello</h1>"); assertEquals(ren.render(par.parse("# hello")), "<h1>hello</h1>");
@ -46,43 +36,11 @@ Deno.test({
}); });
Deno.test({ Deno.test({
name: "should parse ATX header if line contains additional spaces", name: "should parse header if line contains additional spaces",
fn: () => { fn: () => {
const par = new MarkdownParser(); const par = new MarkdownParser();
assertEquals(ren.render(par.parse(" # hello")), "<h1>hello</h1>"); assertEquals(ren.render(par.parse(" # hello")), "<h1>hello</h1>");
assertEquals(ren.render(par.parse(" # hello")), "<h1>hello</h1>"); assertEquals(ren.render(par.parse(" # hello")), "<h1>hello</h1>");
assertEquals(ren.render(par.parse(" # hello")), "<h1>hello</h1>"); assertEquals(ren.render(par.parse(" # hello")), "<h1>hello</h1>");
assertEquals(ren.render(par.parse("\n # hello")), "<h1>hello</h1>");
assertEquals(ren.render(par.parse("\r\n # hello")), "<h1>hello</h1>");
},
});
Deno.test({
name: "should parse ATX header with closing sequence",
fn: () => {
const par = new MarkdownParser();
assertEquals(ren.render(par.parse("# #")), "<h1></h1>");
assertEquals(ren.render(par.parse("# hello #")), "<h1>hello</h1>");
assertEquals(ren.render(par.parse("# hello #########")), "<h1>hello</h1>");
assertEquals(ren.render(par.parse("# hello # ")), "<h1>hello</h1>");
assertEquals(ren.render(par.parse("###### hello #")), "<h6>hello</h6>");
},
});
Deno.test({
name: "should parse many headers with text",
fn: () => {
const par = new MarkdownParser();
const input = `\
# hello
## world
### this is
#### my world!`;
assertEquals(
ren.render(par.parse(input)),
"<h1>hello</h1><h2>world</h2><h3>this is</h3><h4>my world!</h4>",
);
}, },
}); });

View file

@ -2,10 +2,7 @@ import { AnyNode, Elem, Fragment, TextNode } from "../core/node.ts";
import { isNil } from "../core/utils.ts"; import { isNil } from "../core/utils.ts";
import { Parser } from "./types.ts"; import { Parser } from "./types.ts";
const RE_NEW_LINE = /^\r?\n/; const RE_OPEN_HEADING = /^\s{0,3}(#{1,6})(\s|$)/;
const RE_OPEN_ATX_HEADING = /^\s{0,3}(#{1,6})(\s|$)/;
const RE_CLOSE_ATX_HEADING = /(^|\s+)#*\s*$/;
export class MarkdownParser implements Parser { export class MarkdownParser implements Parser {
parse(input: string): AnyNode { parse(input: string): AnyNode {
@ -13,49 +10,29 @@ export class MarkdownParser implements Parser {
let readStr = input; let readStr = input;
while (readStr.trim().length) { const match = RE_OPEN_HEADING.exec(readStr);
{
// 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)) { if (!isNil(match)) {
readStr = readStr.slice(match[0].length); readStr = readStr.slice(match[0].length);
const atxHeading: AstAtxHeading = { console.log({ match });
kind: AstKind.AtxHeading,
const heading: AstHeading = {
kind: AstKind.Heading,
level: match[1].length as HeadingLevel, level: match[1].length as HeadingLevel,
content: [], content: [],
}; };
ast.content.push(atxHeading); ast.content.push(heading);
if (match[2].length > 0) { if (match[2].length > 0) {
const endMatch = RE_CLOSE_ATX_HEADING.exec(readStr); const textContent = readStr.split("\n", 1)[0];
readStr = readStr.slice(textContent.length);
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 = { const text: AstText = {
kind: AstKind.Text, kind: AstKind.Text,
content: headingContent.trim(), content: textContent,
}; };
atxHeading.content.push(text);
} heading.content.push(text);
}
} else {
break;
} }
} }
@ -63,7 +40,7 @@ export class MarkdownParser implements Parser {
} }
} }
function Heading(ast: AstAtxHeading): Elem { function Heading(ast: AstHeading): Elem {
return new Elem(`h${ast.level}`, {}, ast.content.map(Text)); return new Elem(`h${ast.level}`, {}, ast.content.map(Text));
} }
@ -74,9 +51,9 @@ function Text(ast: AstText): TextNode {
// AST // AST
type AstDocument = BaseAstItem<AstKind.Document, AstDocumentChild[]>; type AstDocument = BaseAstItem<AstKind.Document, AstDocumentChild[]>;
type AstDocumentChild = AstAtxHeading; type AstDocumentChild = AstHeading;
interface AstAtxHeading extends BaseAstItem<AstKind.AtxHeading, AstText[]> { interface AstHeading extends BaseAstItem<AstKind.Heading, AstText[]> {
level: HeadingLevel; level: HeadingLevel;
} }
@ -91,6 +68,6 @@ interface BaseAstItem<K extends AstKind, Cont> {
enum AstKind { enum AstKind {
Document, Document,
AtxHeading, Heading,
Text, Text,
} }