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 - Text to wrap
7 * @param {string} tag - HTML tag to use
8 * @returns {string} - Wrapped HTML
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');
14/**
15 * Parses bold text (__) to <strong> tags
16 * @param {string} markdown - Markdown text
17 * @returns {string} - HTML with bold tags
18 */
19function parseBold(markdown) {
20 const pattern = new RegExp(`__([^_]+)__`); // Match __text__ but not ___text___
21 return markdown.replace(pattern, '<strong>$1</strong>');
1722}
1823
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
24/**
25 * Parses italic text (_) to <em> tags
26 * @param {string} markdown - Markdown text
27 * @returns {string} - HTML with italic tags
28 */
29function parseItalic(markdown) {
30 // Match _text_ but not __text__ or ___text___
31 const pattern = new RegExp(`_([^_]+)_`);
32 return markdown.replace(pattern, '<em>$1</em>');
2133}
2234
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
35/**
36 * Parses inline formatting (__ for bold, _ for italic)
37 * @param {string} text - Text to format
38 * @returns {string} - Formatted HTML
39 */
40function parseInlineFormatting(text) {
41 // Parse bold first, then italic to handle nesting correctly
42 let formatted = text;
43 // Handle multiple bold occurrences
44 while (formatted.includes('__')) {
45 formatted = parseBold(formatted);
46 }
47 // Handle multiple italic occurrences
48 while (formatted.includes('_')) {
49 formatted = parseItalic(formatted);
2950 }
51 return formatted;
3052}
3153
32function parseHeader(markdown, list) {
54/**
55 * Parses a header (# ## ### etc.)
56 * @param {string} line - Line to parse
57 * @param {boolean} inList - Whether we're currently in a list
58 * @returns {[string|null, boolean]} - [HTML result, new inList state]
59 */
60function parseHeader(line, inList) {
61 // Count leading # characters
3362 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
63 for (let i = 0; i < line.length; i++) {
64 if (line[i] === '#') {
3665 count += 1;
3766 } else {
3867 break;
3968 }
4069 }
70
71 // Valid headers have 1-6 # characters
4172 if (count === 0 || count > 6) {
42 return [null, list];
73 return [null, inList];
4374 }
75
76 // Extract header content (text after # characters and space)
77 const headerContent = line.substring(count + 1);
4478 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
79 const headerHtml = wrap(headerContent, headerTag);
80
81 // Close list if we were in one
82 if (inList) {
4783 return [`</ul>${headerHtml}`, false];
4884 } else {
4985 return [headerHtml, false];
5086 }
5187}
5288
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];
89/**
90 * Parses a list item (* item)
91 * @param {string} line - Line to parse
92 * @param {boolean} inList - Whether we're currently in a list
93 * @returns {[string|null, boolean]} - [HTML result, new inList state]
94 */
95function parseListItem(line, inList) {
96 if (line.startsWith('* ')) {
97 // Extract list item content (text after "* ")
98 const itemContent = line.substring(2);
99 // Parse inline formatting in list item
100 const formattedContent = parseInlineFormatting(itemContent);
101 const listItemHtml = wrap(formattedContent, 'li');
102
103 if (inList) {
104 // Continue existing list
105 return [listItemHtml, true];
58106 } else {
59 return [`<ul>${innerHtml}`, true];
107 // Start new list
108 return [`<ul>${listItemHtml}`, true];
60109 }
61110 }
62 return [null, list];
111 return [null, inList];
63112}
64113
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
114/**
115 * Parses a paragraph
116 * @param {string} line - Line to parse
117 * @param {boolean} inList - Whether we're currently in a list
118 * @returns {[string, boolean]} - [HTML result, new inList state]
119 */
120function parseParagraph(line, inList) {
121 // Parse inline formatting in paragraph
122 const formattedContent = parseInlineFormatting(line);
123 const paragraphHtml = wrap(formattedContent, 'p');
124
125 // Close list if we were in one
126 if (inList) {
127 return [`</ul>${paragraphHtml}`, false];
68128 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
129 return [paragraphHtml, false];
70130 }
71131}
72132
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);
133/**
134 * Parses a single line of Markdown
135 * @param {string} line - Line to parse
136 * @param {boolean} inList - Whether we're currently in a list
137 * @returns {[string, boolean]} - [HTML result, new inList state]
138 */
139function parseLine(line, inList) {
140 // Try to parse as header
141 let [result, newList] = parseHeader(line, inList);
142 if (result !== null) {
143 return [result, newList];
80144 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
145
146 // Try to parse as list item
147 [result, newList] = parseListItem(line, inList);
148 if (result !== null) {
149 return [result, newList];
83150 }
84 return [result, inListAfter];
151
152 // Parse as paragraph (default)
153 return parseParagraph(line, inList);
85154}
86155
87156/**
88 * @param {string} markdown
89 * @returns {string}
157 * Parses Markdown text and converts it to HTML
158 * @param {string} markdown - Markdown text to parse
159 * @returns {string} - HTML output
90160 */
91161export function parse(markdown) {
92162 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
163 let html = '';
164 let inList = false;
165
166 // Process each line
167 for (const line of lines) {
168 const [lineHtml, newList] = parseLine(line, inList);
169 html += lineHtml;
170 inList = newList;
99171 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
172
173 // Close list if we're still in one
174 if (inList) {
175 html += '</ul>';
104176 }
177
178 return html;
105179}
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.