xas

Finished
1// Refactored Markdown Parser
2// This parser converts Markdown syntax to HTML
3
4/**
5 * Wraps text with HTML tags
6 * @param {string} text - The text to wrap
7 * @param {string} tag - The HTML tag to use
8 * @returns {string} The wrapped text
9 */
110function wrap(text, tag) {
211 return `<${tag}>${text}</${tag}>`;
312}
413
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
8
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
13}
14
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
22
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
14/**
15 * Parses inline formatting (bold, italic) in text
16 * @param {string} markdown - The markdown text to parse
17 * @returns {string} The parsed HTML text
18 */
19function parseInlineFormatting(markdown) {
20 // Parse bold text: **bold** or __bold__ -> <strong>bold</strong>
21 let result = markdown.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
22 result = result.replace(/__(.+?)__/g, '<strong>$1</strong>');
23
24 // Parse italic text: *italic* or _italic_ -> <em>italic</em>
25 result = result.replace(/\*(.+?)\*/g, '<em>$1</em>');
26 result = result.replace(/_(.+?)_/g, '<em>$1</em>');
27
28 return result;
3029}
3130
32function parseHeader(markdown, list) {
31/**
32 * Parses a header line
33 * @param {string} markdown - The markdown line to parse
34 * @param {boolean} inList - Whether we're currently in a list
35 * @returns {[string|null, boolean]} [HTML result, new inList state]
36 */
37function parseHeader(markdown, inList) {
38 // Count # characters at the start
3339 let count = 0;
3440 for (let i = 0; i < markdown.length; i++) {
3541 if (markdown[i] === '#') {
3844 break;
3945 }
4046 }
47
48 // Valid headers have 1-6 # characters
4149 if (count === 0 || count > 6) {
42 return [null, list];
50 return [null, inList];
4351 }
52
4453 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
54 const headerContent = markdown.substring(count + 1);
55 const headerHtml = wrap(parseInlineFormatting(headerContent), headerTag);
56
57 // Close any open list before starting header
58 if (inList) {
4759 return [`</ul>${headerHtml}`, false];
4860 } else {
4961 return [headerHtml, false];
5062 }
5163}
5264
53function parseLineItem(markdown, list) {
54 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
65/**
66 * Parses a list item line
67 * @param {string} markdown - The markdown line to parse
68 * @param {boolean} inList - Whether we're currently in a list
69 * @returns {[string|null, boolean]} [HTML result, new inList state]
70 */
71function parseLineItem(markdown, inList) {
72 if (markdown.startsWith('* ')) {
73 const itemContent = markdown.substring(2);
74 const itemHtml = wrap(parseInlineFormatting(itemContent), 'li');
75
76 if (inList) {
77 return [itemHtml, true];
5878 } else {
59 return [`<ul>${innerHtml}`, true];
79 return [`<ul>${itemHtml}`, true];
6080 }
6181 }
62 return [null, list];
82 return [null, inList];
6383}
6484
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
85/**
86 * Parses a paragraph line
87 * @param {string} markdown - The markdown line to parse
88 * @param {boolean} inList - Whether we're currently in a list
89 * @returns {[string, boolean]} [HTML result, new inList state]
90 */
91function parseParagraph(markdown, inList) {
92 const paragraphContent = parseInlineFormatting(markdown);
93
94 if (inList) {
95 return [`</ul>${wrap(paragraphContent, 'p')}`, false];
6896 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
97 return [wrap(paragraphContent, 'p'), false];
7098 }
7199}
72100
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
101/**
102 * Parses a single line of markdown
103 * @param {string} markdown - The markdown line to parse
104 * @param {boolean} inList - Whether we're currently in a list
105 * @returns {[string, boolean]} [HTML result, new inList state]
106 */
107function parseLine(markdown, inList) {
108 // Try to parse as header
109 let [result, newInList] = parseHeader(markdown, inList);
110 if (result !== null) {
111 return [result, newInList];
80112 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
113
114 // Try to parse as list item
115 [result, newInList] = parseLineItem(markdown, inList);
116 if (result !== null) {
117 return [result, newInList];
83118 }
84 return [result, inListAfter];
119
120 // Parse as paragraph
121 return parseParagraph(markdown, inList);
85122}
86123
87124/**
88 * @param {string} markdown
89 * @returns {string}
125 * Main function to parse markdown text to HTML
126 * @param {string} markdown - The markdown text to parse
127 * @returns {string} The resulting HTML
90128 */
91129export function parse(markdown) {
92130 const lines = markdown.split('\n');
93131 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
132 let inList = false;
133
134 for (const line of lines) {
135 const [lineResult, newInList] = parseLine(line, inList);
97136 result += lineResult;
98 list = newList;
137 inList = newInList;
99138 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
139
140 // Close any open list at the end
141 if (inList) {
142 result += '</ul>';
104143 }
144
145 return result;
105146}
Test NameStatus
parses normal text as a paragraph
Pass
parsing italics
Pass
parsing bold text
Pass
mixed normal, italics and bold text
Pass
with h1 header level
Pass
with h2 header level
Pass
with h3 header level
Pass
with h4 header level
Pass
with h5 header level
Pass
with h6 header level
Pass
with h7 header level
Pass
unordered lists
Pass
with a little bit of everything
Pass
with markdown symbols in the header text that should not be interpreted
Pass
with markdown symbols in the list item text that should not be interpreted
Pass
with markdown symbols in the paragraph text that should not be interpreted
Pass
unordered lists close properly with preceding and following lines
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.