Kratos

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
3}
4
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');
1/**
2 * A Markdown parser that converts Markdown syntax to HTML
3 * Refactored for better readability and maintainability
4 */
5class MarkdownParser {
6 /**
7 * Parse Markdown text and convert it to HTML
8 * @param {string} markdown - The Markdown text to parse
9 * @returns {string} - The resulting HTML
10 */
11 parse(markdown) {
12 const lines = markdown.split('\n');
13 let html = '';
14 let inList = false;
15
16 for (const line of lines) {
17 const result = this.parseLine(line, inList);
18 html += result.html;
19 inList = result.inList;
20 }
21
22 // Close any open list at the end
23 if (inList) {
24 html += '</ul>';
25 }
26
27 return html;
2928 }
30}
31
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
37 } else {
38 break;
29
30 /**
31 * Parse a single line of Markdown
32 * @param {string} line - The line to parse
33 * @param {boolean} inList - Whether we are currently in a list
34 * @returns {Object} - Object containing the parsed HTML and updated list state
35 */
36 parseLine(line, inList) {
37 // Try to parse as header
38 const headerResult = this.parseHeader(line, inList);
39 if (headerResult.matched) {
40 return { html: headerResult.html, inList: headerResult.inList };
41 }
42
43 // Try to parse as list item
44 const listItemResult = this.parseListItem(line, inList);
45 if (listItemResult.matched) {
46 return { html: listItemResult.html, inList: listItemResult.inList };
3947 }
48
49 // Default to paragraph
50 const paragraphResult = this.parseParagraph(line, inList);
51 return { html: paragraphResult.html, inList: paragraphResult.inList };
4052 }
41 if (count === 0 || count > 6) {
42 return [null, list];
53
54 /**
55 * Parse a header (h1-h6)
56 * @param {string} line - The line to parse
57 * @param {boolean} inList - Whether we are currently in a list
58 * @returns {Object} - Parsing result with matched flag, html, and inList state
59 */
60 parseHeader(line, inList) {
61 let count = 0;
62 for (let i = 0; i < line.length; i++) {
63 if (line[i] === '#') {
64 count += 1;
65 } else {
66 break;
67 }
68 }
69
70 // Not a header or invalid header level
71 if (count === 0 || count > 6) {
72 return { matched: false };
73 }
74
75 const headerTag = `h${count}`;
76 const content = line.substring(count + 1);
77 const headerHtml = this.wrap(content, headerTag);
78
79 // If we were in a list, close it before the header
80 const html = inList ? `</ul>${headerHtml}` : headerHtml;
81
82 return { matched: true, html, inList: false };
4383 }
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];
84
85 /**
86 * Parse a list item
87 * @param {string} line - The line to parse
88 * @param {boolean} inList - Whether we are currently in a list
89 * @returns {Object} - Parsing result with matched flag, html, and inList state
90 */
91 parseListItem(line, inList) {
92 if (!line.startsWith('*')) {
93 return { matched: false };
94 }
95
96 const content = line.substring(2);
97 const textHtml = this.parseText(content, true);
98 const listItemHtml = this.wrap(textHtml, 'li');
99
100 if (inList) {
101 return { matched: true, html: listItemHtml, inList: true };
102 } else {
103 return { matched: true, html: `<ul>${listItemHtml}`, inList: true };
104 }
50105 }
51}
52
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];
106
107 /**
108 * Parse a paragraph
109 * @param {string} line - The line to parse
110 * @param {boolean} inList - Whether we are currently in a list
111 * @returns {Object} - Parsing result with html and inList state
112 */
113 parseParagraph(line, inList) {
114 const textHtml = this.parseText(line, false);
115
116 if (inList) {
117 return { html: `</ul>${textHtml}`, inList: false };
58118 } else {
59 return [`<ul>${innerHtml}`, true];
119 return { html: textHtml, inList: false };
60120 }
61121 }
62 return [null, list];
63}
64
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
122
123 /**
124 * Parse text with emphasis (__ for strong, _ for emphasis)
125 * @param {string} text - The text to parse
126 * @param {boolean} inList - Whether we are currently in a list
127 * @returns {string} - The parsed text with HTML tags
128 */
129 parseText(text, inList) {
130 const parsedText = this.parseEmphasis(this.parseStrong(text));
131
132 if (inList) {
133 return parsedText;
134 } else {
135 return this.wrap(parsedText, 'p');
136 }
70137 }
71}
72
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
138
139 /**
140 * Parse strong emphasis (__text__)
141 * @param {string} text - The text to parse
142 * @returns {string} - The text with <strong> tags
143 */
144 parseStrong(text) {
145 return this.parseInline(text, '__', 'strong');
77146 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
147
148 /**
149 * Parse emphasis (_text_)
150 * @param {string} text - The text to parse
151 * @returns {string} - The text with <em> tags
152 */
153 parseEmphasis(text) {
154 return this.parseInline(text, '_', 'em');
80155 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
156
157 /**
158 * Parse inline elements using a delimiter
159 * @param {string} text - The text to parse
160 * @param {string} delimiter - The delimiter to look for
161 * @param {string} tag - The HTML tag to wrap with
162 * @returns {string} - The parsed text
163 */
164 parseInline(text, delimiter, tag) {
165 const pattern = new RegExp(`${delimiter}(.+?)${delimiter}`, 'g');
166 const replacement = `<${tag}>$1</${tag}>`;
167 return text.replace(pattern, replacement);
168 }
169
170 /**
171 * Wrap text with HTML tags
172 * @param {string} text - The text to wrap
173 * @param {string} tag - The HTML tag to use
174 * @returns {string} - The wrapped text
175 */
176 wrap(text, tag) {
177 return `<${tag}>${text}</${tag}>`;
83178 }
84 return [result, inListAfter];
85179}
86180
181// Create a singleton instance for backward compatibility
182const parser = new MarkdownParser();
183
87184/**
88 * @param {string} markdown
89 * @returns {string}
185 * Parse Markdown text and convert it to HTML
186 * @param {string} markdown - The Markdown text to parse
187 * @returns {string} - The resulting HTML
90188 */
91189export function parse(markdown) {
92 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;
99 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
104 }
190 return parser.parse(markdown);
105191}
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.