tobe

Finished
1/**
2 * Refactored Markdown Parser
3 *
4 * This refactored version improves readability and maintainability while preserving
5 * all existing functionality. Key improvements include:
6 * - Better organization of parsing logic with clear separation of concerns
7 * - More descriptive function and variable names
8 * - Consolidated helper functions
9 * - Cleaner state management for list handling
10 * - Reduced coupling between functions
11 */
12
13// Helper function to wrap text in HTML tags
114function wrap(text, tag) {
215 return `<${tag}>${text}</${tag}>`;
316}
417
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 }
18// Helper function to parse inline formatting (__ for strong, _ for emphasis)
19// Note: Strong formatting (__text__) must be parsed before emphasis (_text_) to avoid conflicts
20function parseInlineFormatting(text) {
21 // Parse strong formatting (__text__) first - use non-greedy matching
22 let result = text.replace(/__(.+?)__/g, '<strong>$1</strong>');
23 // Then parse emphasis formatting (_text_) - use non-greedy matching
24 result = result.replace(/_(.+?)_/g, '<em>$1</em>');
25 return result;
3026}
3127
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
28// Parse header elements (h1-h6)
29function parseHeader(markdownLine) {
30 // Count leading # characters
31 let headerLevel = 0;
32 for (let i = 0; i < markdownLine.length; i++) {
33 if (markdownLine[i] === '#') {
34 headerLevel++;
3735 } else {
3836 break;
3937 }
4038 }
41 if (count === 0 || count > 6) {
42 return [null, list];
43 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
47 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
39
40 // Valid headers have 1-6 # characters
41 if (headerLevel === 0 || headerLevel > 6) {
42 return null;
5043 }
44
45 // Extract header content (text after # characters and space)
46 const headerContent = markdownLine.substring(headerLevel + 1);
47 const headerTag = `h${headerLevel}`;
48
49 return wrap(parseInlineFormatting(headerContent), headerTag);
5150}
5251
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 }
52// Parse list items
53function parseListItem(markdownLine) {
54 if (!markdownLine.startsWith('* ')) {
55 return null;
6156 }
62 return [null, list];
57
58 // Extract list item content (text after '* ')
59 const itemContent = markdownLine.substring(2);
60 return wrap(parseInlineFormatting(itemContent), 'li');
6361}
6462
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
63// Parse paragraph elements
64function parseParagraph(markdownLine) {
65 return wrap(parseInlineFormatting(markdownLine), 'p');
7166}
7267
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);
68// Parse a single line of markdown
69function parseLine(markdownLine) {
70 // Try parsing as header
71 const headerResult = parseHeader(markdownLine);
72 if (headerResult !== null) {
73 return headerResult;
8074 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
75
76 // Try parsing as list item
77 const listItemResult = parseListItem(markdownLine);
78 if (listItemResult !== null) {
79 return listItemResult;
8380 }
84 return [result, inListAfter];
81
82 // Default to paragraph
83 return parseParagraph(markdownLine);
8584}
8685
8786/**
88 * @param {string} markdown
89 * @returns {string}
87 * Parse markdown text and convert to HTML
88 * @param {string} markdown - The markdown text to parse
89 * @returns {string} - The resulting HTML
9090 */
9191export function parse(markdown) {
9292 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;
93 let htmlResult = '';
94 let inList = false;
95
96 for (const line of lines) {
97 // Try parsing as header
98 const headerHtml = parseHeader(line);
99 if (headerHtml !== null) {
100 // Close list if we were in one
101 if (inList) {
102 htmlResult += '</ul>';
103 inList = false;
104 }
105 htmlResult += headerHtml;
106 continue;
107 }
108
109 // Try parsing as list item
110 const listItemHtml = parseListItem(line);
111 if (listItemHtml !== null) {
112 // Open list if we weren't already in one
113 if (!inList) {
114 htmlResult += '<ul>';
115 inList = true;
116 }
117 htmlResult += listItemHtml;
118 continue;
119 }
120
121 // Close list if we were in one
122 if (inList) {
123 htmlResult += '</ul>';
124 inList = false;
125 }
126
127 // Parse as paragraph
128 htmlResult += parseParagraph(line);
99129 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
130
131 // Close any open list at the end
132 if (inList) {
133 htmlResult += '</ul>';
104134 }
135
136 return htmlResult;
105137}
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.