Skip to content

Commit 4bd0c7a

Browse files
committed
- some markdown parse errors fixed
1 parent 9e6cb92 commit 4bd0c7a

File tree

2 files changed

+257
-210
lines changed

2 files changed

+257
-210
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
import { Injectable } from '@angular/core';
2+
import { MarkdownBlockParser } from './models/markdown-block-parser';
3+
import { MarkdownInlineParser } from './models/markdown-inline-parser';
4+
import { MarkdownHeader1, markdownHeader1Type, MarkdownHeader2, markdownHeader2Type, MarkdownHeader3, markdownHeader3Type, MarkdownHeader4, markdownHeader4Type, MarkdownHeader5, markdownHeader5Type, MarkdownHeader6, markdownHeader6Type } from './models/markdown-header';
5+
import { MarkdownCodeBlock, markdownCodeBlockType } from './models/markdown-code-block';
6+
import { MarkdownSeparator, markdownSeparatorType } from './models/markdown-separator';
7+
import { MarkdownElement } from './models/markdown-element';
8+
import { cast } from '../../helpers/cast';
9+
import { MarkdownCode, markdownCodeType } from './models/markdown-code';
10+
import { MarkdownImage, markdownImageType } from './models/markdown-image';
11+
import { MarkdownLink, markdownLinkType } from './models/markdown-link';
12+
import { MarkdownItalic, markdownItalicType } from './models/markdown-italic';
13+
import { MarkdownBold, markdownBoldType } from './models/markdown-bold';
14+
import { MarkdownStrikethrough, markdownStrikethroughType } from './models/markdown-strikethrough';
15+
import { MarkdownParagraph } from './models/markdown-paragraph';
16+
import { MarkdownText, markdownTextType } from './models/markdown-text';
17+
18+
@Injectable({
19+
providedIn: 'root'
20+
})
21+
export class MarkdownParser {
22+
private readonly blockParsers: MarkdownBlockParser[] = [];
23+
private readonly inlineParsers: MarkdownInlineParser[] = [];
24+
25+
public constructor() {
26+
this.registerBlockParser({
27+
regex: /^\s*#(?<inline>[^#].*)$/,
28+
factory: (result: RegExpExecArray): MarkdownHeader1 => ({
29+
type: markdownHeader1Type,
30+
elements: this.parseInline(result.groups?.['inline']?.trim()),
31+
isBlock: true
32+
})
33+
});
34+
this.registerBlockParser({
35+
regex: /^\s*#{2}(?<inline>[^#].*)$/,
36+
factory: (result: RegExpExecArray): MarkdownHeader2 => ({
37+
type: markdownHeader2Type,
38+
elements: this.parseInline(result.groups?.['inline']?.trim()),
39+
isBlock: true
40+
})
41+
});
42+
this.registerBlockParser({
43+
regex: /^\s*#{3}(?<inline>[^#].*)$/,
44+
factory: (result: RegExpExecArray): MarkdownHeader3 => ({
45+
type: markdownHeader3Type,
46+
elements: this.parseInline(result.groups?.['inline']?.trim()),
47+
isBlock: true
48+
})
49+
});
50+
this.registerBlockParser({
51+
regex: /^\s*#{4}(?<inline>[^#].*)$/,
52+
factory: (result: RegExpExecArray): MarkdownHeader4 => ({
53+
type: markdownHeader4Type,
54+
elements: this.parseInline(result.groups?.['inline']?.trim()),
55+
isBlock: true
56+
})
57+
});
58+
this.registerBlockParser({
59+
regex: /^\s*#{5}(?<inline>[^#].*)$/,
60+
factory: (result: RegExpExecArray): MarkdownHeader5 => ({
61+
type: markdownHeader5Type,
62+
elements: this.parseInline(result.groups?.['inline']?.trim()),
63+
isBlock: true
64+
})
65+
});
66+
this.registerBlockParser({
67+
regex: /^\s*#{6}(?<inline>[^#].*)$/,
68+
factory: (result: RegExpExecArray): MarkdownHeader6 => ({
69+
type: markdownHeader6Type,
70+
elements: this.parseInline(result.groups?.['inline']?.trim()),
71+
isBlock: true
72+
})
73+
});
74+
this.registerBlockParser({
75+
regex: /^\s*`{3}(?<language>.*?)\r?\n(?<code>.*?)`{3}$/g,
76+
factory: (result: RegExpExecArray): MarkdownCodeBlock => ({
77+
type: markdownCodeBlockType,
78+
language: result.groups?.['language'],
79+
code: result.groups?.['code'],
80+
isBlock: true
81+
})
82+
});
83+
this.registerBlockParser({
84+
regex: /^\s*-{3}\s*$/,
85+
factory: (): MarkdownSeparator => ({
86+
type: markdownSeparatorType,
87+
isBlock: true
88+
})
89+
});
90+
91+
this.registerInlineParser({
92+
regex: /`(?<code>[^`].*?)`(?<post>.*)/,
93+
factory: (result: RegExpExecArray): MarkdownElement[] => [
94+
...this.parseInline(result.input.substring(0, result.index)),
95+
cast<MarkdownCode>({
96+
type: markdownCodeType,
97+
code: result.groups?.['code']
98+
}),
99+
...this.parseInline(result.groups?.['post'])
100+
]
101+
});
102+
this.registerInlineParser({
103+
regex: /!\[(?<alt>.*)]\((?<url>.*)\)(?<post>.*)/,
104+
factory: (result: RegExpExecArray): MarkdownElement[] => [
105+
...this.parseInline(result.input.substring(0, result.index)),
106+
cast<MarkdownImage>({
107+
type: markdownImageType,
108+
url: result.groups?.['url'] ?? '',
109+
alt: result.groups?.['alt'] ?? ''
110+
}),
111+
...this.parseInline(result.groups?.['post'])
112+
]
113+
});
114+
this.registerInlineParser({
115+
regex: /\[(?<inline>.*)]\((?<url>.*)\)(?<post>.*)/,
116+
factory: (result: RegExpExecArray): MarkdownElement[] => [
117+
...this.parseInline(result.input.substring(0, result.index)),
118+
cast<MarkdownLink>({
119+
type: markdownLinkType,
120+
url: result.groups?.['url'] ?? '',
121+
elements: this.parseInline(result.groups?.['inline'])
122+
}),
123+
...this.parseInline(result.groups?.['post'])
124+
]
125+
});
126+
this.registerInlineParser({
127+
regex: /\*(?<inline>.*?)\*(?<post>.*)/,
128+
factory: (result: RegExpExecArray): MarkdownElement[] => [
129+
...this.parseInline(result.input.substring(0, result.index)),
130+
cast<MarkdownItalic>({
131+
type: markdownItalicType,
132+
elements: this.parseInline(result.groups?.['inline'])
133+
}),
134+
...this.parseInline(result.groups?.['post'])
135+
]
136+
});
137+
this.registerInlineParser({
138+
regex: /_(?<inline>.*?)_(?<post>.*)/,
139+
factory: (result: RegExpExecArray): MarkdownElement[] => [
140+
...this.parseInline(result.input.substring(0, result.index)),
141+
cast<MarkdownItalic>({
142+
type: markdownItalicType,
143+
elements: this.parseInline(result.groups?.['inline'])
144+
}),
145+
...this.parseInline(result.groups?.['post'])
146+
]
147+
});
148+
this.registerInlineParser({
149+
regex: /\*\*(?<inline>.*?)\*\*(?<post>.*)/,
150+
factory: (result: RegExpExecArray): MarkdownElement[] => [
151+
...this.parseInline(result.input.substring(0, result.index)),
152+
cast<MarkdownBold>({
153+
type: markdownBoldType,
154+
elements: this.parseInline(result.groups?.['inline'])
155+
}),
156+
...this.parseInline(result.groups?.['post'])
157+
]
158+
});
159+
this.registerInlineParser({
160+
regex: /__(?<inline>.*?)__(?<post>.*)/,
161+
factory: (result: RegExpExecArray): MarkdownElement[] => [
162+
...this.parseInline(result.input.substring(0, result.index)),
163+
cast<MarkdownBold>({
164+
type: markdownBoldType,
165+
elements: this.parseInline(result.groups?.['inline'])
166+
}),
167+
...this.parseInline(result.groups?.['post'])
168+
]
169+
});
170+
this.registerInlineParser({
171+
regex: /~~(?<inline>.*?)~~(?<post>.*)/,
172+
factory: (result: RegExpExecArray): MarkdownElement[] => [
173+
...this.parseInline(result.input.substring(0, result.index)),
174+
cast<MarkdownStrikethrough>({
175+
type: markdownStrikethroughType,
176+
elements: this.parseInline(result.groups?.['inline'])
177+
}),
178+
...this.parseInline(result.groups?.['post'])
179+
]
180+
});
181+
182+
}
183+
184+
public registerBlockParser(parser: MarkdownBlockParser): void {
185+
this.blockParsers.push(parser);
186+
}
187+
188+
public registerInlineParser(parser: MarkdownInlineParser): void {
189+
this.inlineParsers.push(parser);
190+
}
191+
192+
public parse(value: string): MarkdownElement[] {
193+
const elements: MarkdownElement[] = [];
194+
if (value.includes('\n\n')) {
195+
const paragraphs = value.split('\n\n');
196+
for (const paragraph of paragraphs) {
197+
elements.push(cast<MarkdownParagraph>({
198+
type: 'paragraph',
199+
elements: this.parseBlock(paragraph),
200+
isBlock: true
201+
}));
202+
}
203+
} else {
204+
this.parseBlock(value);
205+
}
206+
return elements;
207+
}
208+
209+
private parseBlock(text: string): MarkdownElement[] {
210+
const lines = text.split('\n');
211+
const elements: MarkdownElement[] = [];
212+
for (const line of lines) {
213+
let parsed = false;
214+
for (const parser of this.blockParsers) {
215+
const regexResult = parser.regex.exec(line);
216+
if (regexResult) {
217+
elements.push(parser.factory(regexResult));
218+
parsed = true;
219+
break;
220+
}
221+
}
222+
if (!parsed) {
223+
elements.push(...this.parseInline(line));
224+
}
225+
elements[elements.length - 1].breakLine = true;
226+
}
227+
return elements;
228+
}
229+
230+
private parseInline(text: string | undefined): MarkdownElement[] {
231+
if (!text) {
232+
return [];
233+
}
234+
const elements: MarkdownElement[] = [];
235+
let firstResult: RegExpExecArray | undefined;
236+
let firstParser: MarkdownInlineParser | undefined;
237+
for (const parser of this.inlineParsers) {
238+
const regexResult = parser.regex.exec(text);
239+
if (regexResult && (!firstResult || regexResult.index <= firstResult.index)) {
240+
firstResult = regexResult;
241+
firstParser = parser;
242+
}
243+
}
244+
if (firstResult && firstParser) {
245+
elements.push(...firstParser.factory(firstResult));
246+
} else {
247+
elements.push(cast<MarkdownText>({ type: markdownTextType, text }));
248+
}
249+
return elements;
250+
}
251+
}

0 commit comments

Comments
 (0)