diff --git a/par/md.test.ts b/par/md.test.ts index 0325f83..2349623 100644 --- a/par/md.test.ts +++ b/par/md.test.ts @@ -4,6 +4,16 @@ import { MarkdownParser } from "./md.ts"; const ren = new HtmlStrRenderer(); +Deno.test({ + name: "should skip new line character", + 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: () => { @@ -42,6 +52,8 @@ Deno.test({ assertEquals(ren.render(par.parse(" # hello")), "

hello

"); assertEquals(ren.render(par.parse(" # hello")), "

hello

"); assertEquals(ren.render(par.parse(" # hello")), "

hello

"); + assertEquals(ren.render(par.parse("\n # hello")), "

hello

"); + assertEquals(ren.render(par.parse("\r\n # hello")), "

hello

"); }, }); @@ -56,3 +68,21 @@ Deno.test({ assertEquals(ren.render(par.parse("###### hello #")), "
hello
"); }, }); + +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)), + "

hello

world

this is

my world!

", + ); + }, +}); diff --git a/par/md.ts b/par/md.ts index b41bcd3..c660f13 100644 --- a/par/md.ts +++ b/par/md.ts @@ -2,6 +2,8 @@ import { AnyNode, Elem, Fragment, TextNode } from "../core/node.ts"; import { isNil } from "../core/utils.ts"; import { Parser } from "./types.ts"; +const RE_NEW_LINE = /^\r?\n/; + const RE_OPEN_ATX_HEADING = /^\s{0,3}(#{1,6})(\s|$)/; const RE_CLOSE_ATX_HEADING = /(^|\s+)#*\s*$/; @@ -11,33 +13,50 @@ export class MarkdownParser implements Parser { let readStr = input; - 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.split("\n", 1)[0] - : readStr.slice(0, endMatch.index); - readStr = readStr.slice(headingContent.length); - - if (headingContent.trim().length) { - const text: AstText = { - kind: AstKind.Text, - content: headingContent.trim(), - }; - atxHeading.content.push(text); + 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); + } + } + } else { + break; + } } return new Fragment(ast.content.map(Heading));