chris

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
3}
4
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
1// Refactored Markdown Parser
2// This parser converts Markdown syntax to HTML while maintaining the exact same behavior as the original implementation.
83
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
4// Utility function to wrap text in HTML tags
5function wrapInTag(text, tag) {
6 return `<${tag}>${text}</${tag}>`;
137}
148
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
9// Parse bold text using __ delimiters
10// Note: Uses greedy regex pattern that only matches first and last occurrence (preserving original behavior)
11function parseBoldText(markdown) {
12 const boldPattern = new RegExp(`__([^]*)__`); // Greedy pattern - matches first to last __
13 return markdown.replace(boldPattern, '<strong>$1</strong>');
1714}
1815
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
16// Parse italic text using _ delimiters
17// Note: Uses greedy regex pattern that only matches first and last occurrence (preserving original behavior)
18function parseItalicText(markdown) {
19 const italicPattern = new RegExp(`_([^_]*)_`); // Greedy pattern - matches first to last _
20 return markdown.replace(italicPattern, '<em>$1</em>');
2121}
2222
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
23// Parse text with both bold and italic formatting
24// When in a list, text is not wrapped in <p> tags
25function parseFormattedText(markdown, inList) {
26 // Apply bold and italic formatting
27 const formattedText = parseItalicText(parseBoldText(markdown));
28
29 // If we're in a list, don't wrap in paragraph tags
30 if (inList) {
31 return formattedText;
2732 } else {
28 return wrap(parsedText, 'p');
33 return wrapInTag(formattedText, 'p');
2934 }
3035}
3136
32function parseHeader(markdown, list) {
33 let count = 0;
37// Parse header markdown (# ## ### etc.)
38function parseHeaderLine(markdown, currentlyInList) {
39 // Count consecutive # characters at the beginning
40 let headerLevel = 0;
3441 for (let i = 0; i < markdown.length; i++) {
3542 if (markdown[i] === '#') {
36 count += 1;
43 headerLevel += 1;
3744 } else {
3845 break;
3946 }
4047 }
41 if (count === 0 || count > 6) {
42 return [null, list];
48
49 // Headers must be between 1 and 6 # characters
50 if (headerLevel === 0 || headerLevel > 6) {
51 return [null, currentlyInList]; // Not a header
4352 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
53
54 // Create the appropriate header tag (h1, h2, etc.)
55 const headerTag = `h${headerLevel}`;
56 const headerContent = markdown.substring(headerLevel + 1); // Skip # characters and space
57 const headerHtml = wrapInTag(headerContent, headerTag);
58
59 // If we were in a list, close it before the header
60 if (currentlyInList) {
4761 return [`</ul>${headerHtml}`, false];
4862 } else {
4963 return [headerHtml, false];
5064 }
5165}
5266
53function parseLineItem(markdown, list) {
67// Parse list item markdown (* item)
68function parseListItemLine(markdown, currentlyInList) {
69 // Check if line starts with *
5470 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
71 // Extract the list item content (skip "* ")
72 const itemContent = markdown.substring(2);
73
74 // Parse formatting in the list item content
75 const formattedContent = parseFormattedText(itemContent, true); // true = in list
76
77 // Wrap in <li> tags
78 const listItemHtml = wrapInTag(formattedContent, 'li');
79
80 // If we're already in a list, just add the item
81 // Otherwise, start a new list
82 if (currentlyInList) {
83 return [listItemHtml, true];
5884 } else {
59 return [`<ul>${innerHtml}`, true];
85 return [`<ul>${listItemHtml}`, true];
6086 }
6187 }
62 return [null, list];
88
89 // Not a list item
90 return [null, currentlyInList];
6391}
6492
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
93// Parse paragraph (default case when not header or list item)
94function parseParagraphLine(markdown, currentlyInList) {
95 // Parse formatting in the paragraph
96 const formattedParagraph = parseFormattedText(markdown, false); // false = not in list
97
98 // If we were in a list, close it before the paragraph
99 if (currentlyInList) {
100 return [`</ul>${formattedParagraph}`, false];
68101 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
102 return [formattedParagraph, false];
70103 }
71104}
72105
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
106// Parse a single line of markdown
107function parseLineOfMarkdown(markdown, currentlyInList) {
108 // Try to parse as header first
109 let [result, newListState] = parseHeaderLine(markdown, currentlyInList);
110
111 // If not a header, try to parse as list item
75112 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
113 [result, newListState] = parseListItemLine(markdown, currentlyInList);
77114 }
115
116 // If not a list item, parse as paragraph
78117 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
118 [result, newListState] = parseParagraphLine(markdown, currentlyInList);
80119 }
120
121 // This should never happen with valid input
81122 if (result === null) {
82 throw new Error('Remove this line and implement the function');
123 throw new Error('Invalid markdown line');
83124 }
84 return [result, inListAfter];
125
126 return [result, newListState];
85127}
86128
87129/**
88 * @param {string} markdown
89 * @returns {string}
130 * Parse markdown text and convert it to HTML
131 * @param {string} markdown - The markdown text to parse
132 * @returns {string} - The resulting HTML
90133 */
91134export function parse(markdown) {
135 // Split the markdown into individual lines
92136 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;
137
138 // Build the result HTML
139 let htmlResult = '';
140
141 // Track whether we're currently inside a list
142 let inList = false;
143
144 // Process each line
145 for (const line of lines) {
146 // Parse the line and get the HTML result plus updated list state
147 const [lineHtml, newListState] = parseLineOfMarkdown(line, inList);
148
149 // Add the line's HTML to our result
150 htmlResult += lineHtml;
151
152 // Update our list state
153 inList = newListState;
99154 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
155
156 // If we're still in a list at the end, close it
157 if (inList) {
158 htmlResult += '</ul>';
104159 }
160
161 return htmlResult;
105162}
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.