xas

Finished
1/**
2 * A Markdown parser that converts Markdown syntax to HTML.
3 *
4 * Features supported:
5 * - Headers (# Header 1, ## Header 2, etc.)
6 * - Paragraphs (separated by blank lines)
7 * - Emphasis (_italic_, __bold__)
8 * - Unordered lists (* item)
9 */
10
11/**
12 * Wraps text with HTML tags
13 * @param {string} text - The text to wrap
14 * @param {string} tag - The HTML tag to use
15 * @returns {string} The wrapped text
16 */
117function wrap(text, tag) {
218 return `<${tag}>${text}</${tag}>`;
319}
420
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}>`;
21/**
22 * Parses bold text (__text__ -> <strong>text</strong>)
23 * @param {string} markdown - The markdown text to parse
24 * @returns {string} The parsed HTML
25 */
26function parseBold(markdown) {
27 const pattern = new RegExp('__(.+)__');
28 const replacement = '<strong>$1</strong>';
1229 return markdown.replace(pattern, replacement);
1330}
1431
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
32/**
33 * Parses italic text (_text_ -> <em>text</em>)
34 * @param {string} markdown - The markdown text to parse
35 * @returns {string} The parsed HTML
36 */
37function parseItalic(markdown) {
38 const pattern = new RegExp('_(.+)_');
39 const replacement = '<em>$1</em>';
40 return markdown.replace(pattern, replacement);
2141}
2242
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
43/**
44 * Parses text with emphasis formatting and wraps in paragraph tags if needed
45 * @param {string} markdown - The markdown text to parse
46 * @param {boolean} inList - Whether we're currently inside a list
47 * @returns {string} The parsed HTML
48 */
49function parseText(markdown, inList) {
50 // First parse bold, then italic (nested parsing)
51 const parsedText = parseItalic(parseBold(markdown));
52
53 // If we're in a list, don't wrap in paragraph tags
54 if (inList) {
2655 return parsedText;
2756 } else {
2857 return wrap(parsedText, 'p');
2958 }
3059}
3160
32function parseHeader(markdown, list) {
61/**
62 * Parses a header line
63 * @param {string} markdown - The markdown line to parse
64 * @param {boolean} inList - Whether we're currently inside a list
65 * @returns {[string|null, boolean]} [parsed HTML or null if not a header, new list state]
66 */
67function parseHeader(markdown, inList) {
68 // Count the number of # characters at the start
3369 let count = 0;
3470 for (let i = 0; i < markdown.length; i++) {
3571 if (markdown[i] === '#') {
3874 break;
3975 }
4076 }
77
78 // Valid headers have 1-6 # characters
4179 if (count === 0 || count > 6) {
42 return [null, list];
80 return [null, inList];
4381 }
82
83 // Create the appropriate header tag (h1, h2, etc.)
4484 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
85 const headerContent = markdown.substring(count + 1); // Skip # characters and space
86 const headerHtml = wrap(headerContent, headerTag);
87
88 // If we were in a list, close it before the header
89 if (inList) {
4790 return [`</ul>${headerHtml}`, false];
4891 } else {
4992 return [headerHtml, false];
5093 }
5194}
5295
53function parseLineItem(markdown, list) {
96/**
97 * Parses a list item line
98 * @param {string} markdown - The markdown line to parse
99 * @param {boolean} inList - Whether we're currently inside a list
100 * @returns {[string|null, boolean]} [parsed HTML or null if not a list item, new list state]
101 */
102function parseLineItem(markdown, inList) {
103 // Check if this line starts with a list marker
54104 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
105 const itemContent = markdown.substring(2); // Skip "* "
106 const innerHtml = wrap(parseText(itemContent, true), 'li');
107
108 // If we're already in a list, just add the item
109 if (inList) {
57110 return [innerHtml, true];
58111 } else {
112 // Otherwise, start a new list
59113 return [`<ul>${innerHtml}`, true];
60114 }
61115 }
62 return [null, list];
116
117 return [null, inList];
63118}
64119
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
120/**
121 * Parses a paragraph line
122 * @param {string} markdown - The markdown line to parse
123 * @param {boolean} inList - Whether we're currently inside a list
124 * @returns {[string, boolean]} [parsed HTML, new list state]
125 */
126function parseParagraph(markdown, inList) {
127 // If we were in a list, close it before the paragraph
128 if (inList) {
69129 return [`</ul>${parseText(markdown, false)}`, false];
130 } else {
131 return [parseText(markdown, false), false];
70132 }
71133}
72134
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);
135/**
136 * Parses a single line of markdown
137 * @param {string} markdownLine - The markdown line to parse
138 * @param {boolean} inList - Whether we're currently inside a list
139 * @returns {[string, boolean]} [parsed HTML, new list state]
140 */
141function parseLine(markdownLine, inList) {
142 // Try to parse as header first
143 let [result, newListState] = parseHeader(markdownLine, inList);
144 if (result !== null) {
145 return [result, newListState];
80146 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
147
148 // Try to parse as list item
149 [result, newListState] = parseLineItem(markdownLine, inList);
150 if (result !== null) {
151 return [result, newListState];
83152 }
84 return [result, inListAfter];
153
154 // Default to paragraph
155 return parseParagraph(markdownLine, inList);
85156}
86157
87158/**
88 * @param {string} markdown
89 * @returns {string}
159 * Parses a markdown string and converts it to HTML
160 * @param {string} markdown - The markdown text to parse
161 * @returns {string} The resulting HTML
90162 */
91163export function parse(markdown) {
92164 const lines = markdown.split('\n');
93165 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
166 let inList = false;
167
168 for (const line of lines) {
169 const [lineResult, newListState] = parseLine(line, inList);
97170 result += lineResult;
98 list = newList;
171 inList = newListState;
99172 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
173
174 // Close any open list at the end
175 if (inList) {
176 result += '</ul>';
104177 }
178
179 return result;
105180}
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.