Compare commits
No commits in common. "328343ca76d892955bd4732c32aff9e72c319a64" and "ef1baea81bb18dba5de6b55d59c0bd0b233c4481" have entirely different histories.
328343ca76
...
ef1baea81b
2 changed files with 30 additions and 95 deletions
|
@ -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>",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
71
par/md.ts
71
par/md.ts
|
@ -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);
|
||||||
{
|
if (!isNil(match)) {
|
||||||
// 1. clear new line character
|
readStr = readStr.slice(match[0].length);
|
||||||
const match = RE_NEW_LINE.exec(readStr);
|
|
||||||
if (!isNil(match)) {
|
|
||||||
readStr = readStr.slice(match[0].length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. try to find atx heading sequence
|
console.log({ match });
|
||||||
const match = RE_OPEN_ATX_HEADING.exec(readStr);
|
|
||||||
if (!isNil(match)) {
|
|
||||||
readStr = readStr.slice(match[0].length);
|
|
||||||
|
|
||||||
const atxHeading: AstAtxHeading = {
|
const heading: AstHeading = {
|
||||||
kind: AstKind.AtxHeading,
|
kind: AstKind.Heading,
|
||||||
level: match[1].length as HeadingLevel,
|
level: match[1].length as HeadingLevel,
|
||||||
content: [],
|
content: [],
|
||||||
|
};
|
||||||
|
ast.content.push(heading);
|
||||||
|
|
||||||
|
if (match[2].length > 0) {
|
||||||
|
const textContent = readStr.split("\n", 1)[0];
|
||||||
|
readStr = readStr.slice(textContent.length);
|
||||||
|
|
||||||
|
const text: AstText = {
|
||||||
|
kind: AstKind.Text,
|
||||||
|
content: textContent,
|
||||||
};
|
};
|
||||||
ast.content.push(atxHeading);
|
|
||||||
|
|
||||||
if (match[2].length > 0) {
|
heading.content.push(text);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue