joseph

Finished
1// Refactored Markdown Parser
2// This implementation maintains the exact same functionality as the original
3// but with improved readability and structure.
4
5/**
6 * Wraps text with HTML tags
7 * @param {string} text - The text to wrap
8 * @param {string} tag - The HTML tag to use
9 * @returns {string} The wrapped text
10 */
111function wrap(text, tag) {
212 return `<${tag}>${text}</${tag}>`;
313}
414
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}>`;
15/**
16 * Parses bold text (__) and wraps it with <strong> tags
17 * @param {string} markdown - The markdown text to parse
18 * @returns {string} The parsed text with <strong> tags
19 */
20function parseBold(markdown) {
21 const pattern = new RegExp(`__(.+)__`);
22 const replacement = `<strong>$1</strong>`;
1223 return markdown.replace(pattern, replacement);
1324}
1425
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
26/**
27 * Parses italic text (_) and wraps it with <em> tags
28 * @param {string} markdown - The markdown text to parse
29 * @returns {string} The parsed text with <em> tags
30 */
31function parseItalic(markdown) {
32 const pattern = new RegExp(`_(.+)_`);
33 const replacement = `<em>$1</em>`;
34 return markdown.replace(pattern, replacement);
2135}
2236
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
37/**
38 * Parses inline formatting (__ for bold, _ for italic) for paragraphs and list items
39 * @param {string} markdown - The markdown text to parse
40 * @param {boolean} inList - Whether we're currently in a list
41 * @returns {string} The parsed text with appropriate HTML tags
42 */
43function parseInlineFormatting(markdown, inList) {
44 // Parse bold first, then italic
45 // Note: The original regex is greedy, so "__bold__ and __also__" becomes "<strong>bold__ and __also</strong>"
46 const boldParsed = parseBold(markdown);
47 const italicParsed = parseItalic(boldParsed);
48
49 // If we're in a list, don't wrap in <p> tags
50 if (inList) {
51 return italicParsed;
2752 } else {
28 return wrap(parsedText, 'p');
53 return wrap(italicParsed, 'p');
2954 }
3055}
3156
32function parseHeader(markdown, list) {
33 let count = 0;
57/**
58 * Parses a header (# ## ### etc.) and converts it to appropriate HTML
59 * Headers do NOT parse inline formatting (__ and _ are treated as literal text)
60 * @param {string} markdown - The markdown line to parse
61 * @param {boolean} currentlyInList - Whether we're currently in a list
62 * @returns {Object|null} Object with html and newListState, or null if not a header
63 */
64function parseHeader(markdown, currentlyInList) {
65 // Count the number of # at the beginning
66 let hashCount = 0;
3467 for (let i = 0; i < markdown.length; i++) {
3568 if (markdown[i] === '#') {
36 count += 1;
69 hashCount++;
3770 } else {
3871 break;
3972 }
4073 }
41 if (count === 0 || count > 6) {
42 return [null, list];
74
75 // Headers must have 1-6 # characters
76 if (hashCount === 0 || hashCount > 6) {
77 return null;
4378 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
47 return [`</ul>${headerHtml}`, false];
79
80 // Extract the header text (skip the # and the following space)
81 // Headers do NOT parse inline formatting
82 const headerText = markdown.substring(hashCount + 1);
83 const headerTag = `h${hashCount}`;
84 const headerHtml = wrap(headerText, headerTag);
85
86 // If we were in a list, we need to close it before the header
87 if (currentlyInList) {
88 return {
89 html: `</ul>${headerHtml}`,
90 newListState: false
91 };
4892 } else {
49 return [headerHtml, false];
93 return {
94 html: headerHtml,
95 newListState: false
96 };
5097 }
5198}
5299
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];
58 } else {
59 return [`<ul>${innerHtml}`, true];
60 }
100/**
101 * Parses a list item (*) and converts it to appropriate HTML
102 * @param {string} markdown - The markdown line to parse
103 * @param {boolean} currentlyInList - Whether we're currently in a list
104 * @returns {Object|null} Object with html and newListState, or null if not a list item
105 */
106function parseListItem(markdown, currentlyInList) {
107 // Must start with "*" (asterisk) - the original implementation doesn't require a space
108 if (!markdown.startsWith('*')) {
109 return null;
110 }
111
112 // Extract the list item text (skip the "*")
113 const itemText = markdown.substring(1);
114
115 // Parse inline formatting but don't wrap in <p> since we're in a list
116 const formattedText = parseInlineFormatting(itemText, true);
117 const listItemHtml = wrap(formattedText, 'li');
118
119 // If we're already in a list, just add the list item
120 // Otherwise, start a new list
121 if (currentlyInList) {
122 return {
123 html: listItemHtml,
124 newListState: true
125 };
126 } else {
127 return {
128 html: `<ul>${listItemHtml}`,
129 newListState: true
130 };
61131 }
62 return [null, list];
63132}
64133
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
134/**
135 * Parses a paragraph and converts it to appropriate HTML
136 * @param {string} markdown - The markdown line to parse
137 * @param {boolean} currentlyInList - Whether we're currently in a list
138 * @returns {Object} Object with html and newListState
139 */
140function parseParagraph(markdown, currentlyInList) {
141 // Parse inline formatting and wrap in <p> tags (unless we're closing a list)
142 const formattedText = parseInlineFormatting(markdown, false);
143
144 // If we were in a list, we need to close it before the paragraph
145 if (currentlyInList) {
146 return {
147 html: `</ul>${formattedText}`,
148 newListState: false
149 };
68150 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
151 return {
152 html: formattedText,
153 newListState: false
154 };
70155 }
71156}
72157
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);
158/**
159 * Parses a single line of markdown
160 * @param {string} markdownLine - The markdown line to parse
161 * @param {boolean} currentlyInList - Whether we're currently in a list
162 * @returns {Object} Object with html and newListState
163 */
164function parseLine(markdownLine, currentlyInList) {
165 // Try to parse as header first
166 const headerResult = parseHeader(markdownLine, currentlyInList);
167 if (headerResult !== null) {
168 return headerResult;
80169 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
170
171 // Try to parse as list item
172 const listItemResult = parseListItem(markdownLine, currentlyInList);
173 if (listItemResult !== null) {
174 return listItemResult;
83175 }
84 return [result, inListAfter];
176
177 // Default to paragraph
178 return parseParagraph(markdownLine, currentlyInList);
85179}
86180
87181/**
88 * @param {string} markdown
89 * @returns {string}
182 * Parses markdown text and converts it to HTML
183 * @param {string} markdown - The markdown text to parse
184 * @returns {string} The resulting HTML
90185 */
91186export function parse(markdown) {
92187 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;
188 let htmlResult = '';
189 let inList = false;
190
191 // Process each line
192 for (const line of lines) {
193 const lineResult = parseLine(line, inList);
194 htmlResult += lineResult.html;
195 inList = lineResult.newListState;
99196 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
197
198 // Close any open list at the end
199 if (inList) {
200 htmlResult += '</ul>';
104201 }
202
203 return htmlResult;
105204}
Test NameStatus
unordered lists
Fail
with a little bit of everything
Fail
with markdown symbols in the list item text that should not be interpreted
Fail
unordered lists close properly with preceding and following lines
Fail
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
with markdown symbols in the header text that should not be interpreted
Pass
with markdown symbols in the paragraph text that should not be interpreted
Pass

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