harrison

Finished
1// Utility function to wrap text in HTML tags
12function wrap(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
8
9function parser(markdown, delimiter, tag) {
6// Generic parser for inline markdown formatting (bold, italic, etc.)
7function parseInlineFormatting(markdown, delimiter, tag) {
108 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
119 const replacement = `<${tag}>$1</${tag}>`;
1210 return markdown.replace(pattern, replacement);
1311}
1412
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
13// Parse bold text using __ delimiter
14function parseBold(markdown) {
15 return parseInlineFormatting(markdown, '__', 'strong');
1716}
1817
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
18// Parse italic text using _ delimiter
19function parseItalic(markdown) {
20 return parseInlineFormatting(markdown, '_', 'em');
2121}
2222
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
23// Parse inline formatting (bold and italic) within text
24function parseInlineText(markdown, isListItem = false) {
25 const parsedText = parseItalic(parseBold(markdown));
26 return isListItem ? parsedText : wrap(parsedText, 'p');
3027}
3128
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
29// Parse markdown headers (# to ######)
30function parseHeader(markdown, isInList) {
31 // Count leading # characters
32 let headerLevel = 0;
33 for (const char of markdown) {
34 if (char === '#') {
35 headerLevel++;
3736 } else {
3837 break;
3938 }
4039 }
41 if (count === 0 || count > 6) {
42 return [null, list];
40
41 // Valid headers are 1-6 # characters
42 if (headerLevel === 0 || headerLevel > 6) {
43 return [null, isInList];
4344 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
45
46 const headerTag = `h${headerLevel}`;
47 const headerContent = markdown.substring(headerLevel + 1).trim();
48 const headerHtml = wrap(headerContent, headerTag);
49
50 // Close any open list before starting header
51 if (isInList) {
4752 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
5053 }
54
55 return [headerHtml, false];
5156}
5257
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 }
58// Parse list items starting with *
59function parseListItem(markdown, isInList) {
60 if (!markdown.startsWith('*')) {
61 return [null, isInList];
62 }
63
64 const itemContent = markdown.substring(2); // Remove '* '
65 const parsedItemContent = parseInlineText(itemContent, true);
66 const listItemHtml = wrap(parsedItemContent, 'li');
67
68 if (isInList) {
69 return [listItemHtml, true];
6170 }
62 return [null, list];
71
72 // Start a new list
73 return [`<ul>${listItemHtml}`, true];
6374}
6475
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
76// Parse regular paragraphs
77function parseParagraph(markdown, isInList) {
78 const paragraphHtml = parseInlineText(markdown, false);
79
80 if (isInList) {
81 // Close the list before starting paragraph
82 return [`</ul>${paragraphHtml}`, false];
7083 }
84
85 return [paragraphHtml, false];
7186}
7287
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
88// Parse a single line of markdown, determining its type and converting to HTML
89function parseLine(markdown, isInList) {
90 // Try to parse as header first
91 let [html, stillInList] = parseHeader(markdown, isInList);
92
93 // If not a header, try to parse as list item
94 if (html === null) {
95 [html, stillInList] = parseListItem(markdown, isInList);
7796 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
97
98 // If not a list item, parse as paragraph
99 if (html === null) {
100 [html, stillInList] = parseParagraph(markdown, isInList);
80101 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
102
103 // This should never happen as one of the parsers should always match
104 if (html === null) {
105 throw new Error('Unable to parse markdown line');
83106 }
84 return [result, inListAfter];
107
108 return [html, stillInList];
85109}
86110
87111/**
88 * @param {string} markdown
89 * @returns {string}
112 * Parse markdown text and convert it to HTML
113 * @param {string} markdown - The markdown text to parse
114 * @returns {string} The equivalent HTML
90115 */
91116export function parse(markdown) {
92117 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;
118 let htmlResult = '';
119 let isInList = false;
120
121 // Process each line of markdown
122 for (const line of lines) {
123 const [lineHtml, stillInList] = parseLine(line, isInList);
124 htmlResult += lineHtml;
125 isInList = stillInList;
99126 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
127
128 // Close any unclosed list at the end
129 if (isInList) {
130 htmlResult += '</ul>';
104131 }
132
133 return htmlResult;
105134}
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.