joseph

Finished
1function wrap(text, tag) {
1// Helper function to wrap text in HTML tags
2function wrapTextInTag(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
5function isTag(text, tag) {
6// Helper function to check if text starts with a specific HTML tag
7function startsWithTag(text, tag) {
68 return text.startsWith(`<${tag}>`);
79}
810
9function parser(markdown, delimiter, tag) {
11// Generic parser for inline markdown elements (bold, italic, etc.)
12function parseInlineMarkdown(markdown, delimiter, htmlTag) {
1013 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
14 const replacement = `<${htmlTag}>$1</${htmlTag}>`;
1215 return markdown.replace(pattern, replacement);
1316}
1417
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
18// Parse bold text (__ delimiter)
19function parseBoldText(markdown) {
20 return parseInlineMarkdown(markdown, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text (_ delimiter)
24function parseItalicText(markdown) {
25 return parseInlineMarkdown(markdown, '_', 'em');
2126}
2227
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
28// Parse inline formatting (bold and italic) within text
29function parseInlineFormatting(markdown, isListItem) {
30 const parsedText = parseItalicText(parseBoldText(markdown));
31 if (isListItem) {
2632 return parsedText;
2733 } else {
28 return wrap(parsedText, 'p');
34 return wrapTextInTag(parsedText, 'p');
2935 }
3036}
3137
32function parseHeader(markdown, list) {
33 let count = 0;
38// Parse header markdown (#, ##, ###, etc.)
39function parseHeader(markdown, isInList) {
40 // Count consecutive # characters at the beginning
41 let headerLevel = 0;
3442 for (let i = 0; i < markdown.length; i++) {
3543 if (markdown[i] === '#') {
36 count += 1;
44 headerLevel += 1;
3745 } else {
3846 break;
3947 }
4048 }
41 if (count === 0 || count > 6) {
42 return [null, list];
49
50 // Valid headers have 1-6 # characters
51 if (headerLevel === 0 || headerLevel > 6) {
52 return [null, isInList];
4353 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
54
55 // Create the header tag (h1, h2, h3, etc.)
56 const headerTag = `h${headerLevel}`;
57 const headerContent = markdown.substring(headerLevel + 1);
58 const headerHtml = wrapTextInTag(headerContent, headerTag);
59
60 // If we were in a list, close it before starting the header
61 if (isInList) {
4762 return [`</ul>${headerHtml}`, false];
4863 } else {
4964 return [headerHtml, false];
5065 }
5166}
5267
53function parseLineItem(markdown, list) {
68// Parse list items (starting with *)
69function parseListItem(markdown, isInList) {
5470 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
71 // Extract the content after the '* ' prefix
72 const listItemContent = markdown.substring(2);
73 const formattedContent = parseInlineFormatting(listItemContent, true);
74 const listItemHtml = wrapTextInTag(formattedContent, 'li');
75
76 if (isInList) {
77 // Continue existing list
78 return [listItemHtml, true];
5879 } else {
59 return [`<ul>${innerHtml}`, true];
80 // Start a new list
81 return [`<ul>${listItemHtml}`, true];
6082 }
6183 }
62 return [null, list];
84 return [null, isInList];
6385}
6486
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
87// Parse regular paragraphs (default case when no other markdown patterns match)
88function parseParagraph(markdown, isInList) {
89 const paragraphHtml = parseInlineFormatting(markdown, false);
90
91 if (isInList) {
92 // Close the list and start a paragraph
93 return [`</ul>${paragraphHtml}`, false];
6894 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
95 // Just return the paragraph
96 return [paragraphHtml, false];
7097 }
7198}
7299
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
100// Parse a single line of markdown, determining its type and converting to HTML
101function parseLine(markdown, isInList) {
102 // Try to parse as header first
103 let [result, newInListState] = parseHeader(markdown, isInList);
104
105 // If not a header, try to parse as list item
75106 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
107 [result, newInListState] = parseListItem(markdown, isInList);
77108 }
109
110 // If not a list item, parse as paragraph
78111 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
112 [result, newInListState] = parseParagraph(markdown, isInList);
80113 }
114
115 // This should never happen as one of the parsers should always match
81116 if (result === null) {
82 throw new Error('Remove this line and implement the function');
117 throw new Error('Unable to parse markdown line');
83118 }
84 return [result, inListAfter];
119
120 return [result, newInListState];
85121}
86122
87123/**
88 * @param {string} markdown
89 * @returns {string}
124 * Main markdown parser function
125 * @param {string} markdown - The markdown string to parse
126 * @returns {string} - The resulting HTML
90127 */
91128export function parse(markdown) {
92129 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
130 let htmlResult = '';
131 let isInList = false;
132
133 // Process each line of markdown
95134 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
135 const [lineHtml, newInListState] = parseLine(lines[i], isInList);
136 htmlResult += lineHtml;
137 isInList = newInListState;
99138 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
139
140 // Close any unclosed list at the end
141 if (isInList) {
142 htmlResult += '</ul>';
104143 }
144
145 return htmlResult;
105146}
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.