uv

Finished
1/**
2 * Parses Markdown text and converts it to HTML.
3 * Supports headers (# ## ### #### ##### ######),
4 * bold text (__text__),
5 * italic text (_text_),
6 * and unordered lists (* item).
7 *
8 * Refactored for better readability and maintainability.
9 */
10
11// Helper function to wrap text in HTML tags
112function wrap(text, tag) {
213 return `<${tag}>${text}</${tag}>`;
314}
415
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');
16// Parse bold text (__text__ -> <strong>text</strong>)
17function parseBold(markdown) {
18 const pattern = /__(.+)__/;
19 return markdown.replace(pattern, '<strong>$1</strong>');
1720}
1821
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
22// Parse italic text (_text_ -> <em>text</em>)
23function parseItalic(markdown) {
24 const pattern = /_(.+)_/;
25 return markdown.replace(pattern, '<em>$1</em>');
2126}
2227
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
28// Parse text with inline formatting (bold and italic)
29function parseInlineFormatting(text) {
30 return parseItalic(parseBold(text));
3031}
3132
32function parseHeader(markdown, list) {
33// Parse header (# Header -> <h1>Header</h1>)
34function parseHeader(line) {
3335 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
36
37 // Count consecutive # characters at the beginning
38 for (let i = 0; i < line.length; i++) {
39 if (line[i] === '#') {
40 count++;
3741 } else {
3842 break;
3943 }
4044 }
45
46 // Valid headers have 1-6 # characters
4147 if (count === 0 || count > 6) {
42 return [null, list];
48 return null;
4349 }
50
4451 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];
50 }
52 const headerText = line.substring(count + 1); // Skip # characters and space
53 const formattedText = parseInlineFormatting(headerText); // Apply inline formatting to headers
54 return wrap(formattedText, headerTag);
5155}
5256
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 }
57// Parse list item (* item -> <li>item</li>)
58function parseListItem(line) {
59 if (!line.startsWith('*')) {
60 return null;
6161 }
62 return [null, list];
62
63 const itemText = line.substring(2); // Skip "* " prefix
64 const formattedText = parseInlineFormatting(itemText);
65 return wrap(formattedText, 'li');
6366}
6467
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
68// Parse paragraph (plain text -> <p>text</p>)
69function parseParagraph(line) {
70 const formattedText = parseInlineFormatting(line);
71 return wrap(formattedText, 'p');
7172}
7273
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
74// Process a single line based on its type
75function processLine(line) {
76 // Try to parse as header
77 const header = parseHeader(line);
78 if (header !== null) {
79 return { type: 'header', content: header };
7780 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
81
82 // Try to parse as list item
83 const listItem = parseListItem(line);
84 if (listItem !== null) {
85 return { type: 'listItem', content: listItem };
8086 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
83 }
84 return [result, inListAfter];
87
88 // Default to paragraph
89 return { type: 'paragraph', content: parseParagraph(line) };
8590}
8691
8792/**
9095 */
9196export function parse(markdown) {
9297 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;
98 let html = '';
99 let inList = false;
100
101 for (const line of lines) {
102 const result = processLine(line);
103
104 // Handle list state transitions
105 if (result.type === 'listItem' && !inList) {
106 // Starting a new list
107 html += '<ul>' + result.content;
108 inList = true;
109 } else if (result.type !== 'listItem' && inList) {
110 // Ending a list
111 html += '</ul>' + result.content;
112 inList = false;
113 } else {
114 // No list state change
115 html += result.content;
116 }
99117 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
118
119 // Close any open list at the end
120 if (inList) {
121 html += '</ul>';
104122 }
123
124 return html;
105125}
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.