sn62

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) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
11// Generic parser for inline markdown elements (bold, italic, etc.)
12function parseInlineMarkdown(markdown, delimiter, htmlTag) {
13 const pattern = new RegExp(`${delimiter}(.+?)${delimiter}`);
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 (using __ delimiter)
19function parseBoldText(markdown) {
20 return parseInlineMarkdown(markdown, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text (using _ delimiter)
24function parseItalicText(markdown) {
25 return parseInlineMarkdown(markdown, '_', '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 inline formatting (bold and italic) within text
29function parseInlineFormatting(markdown) {
30 // Parse bold first, then italic to avoid conflicts
31 return parseItalicText(parseBoldText(markdown));
3032}
3133
32function parseHeader(markdown, list) {
33 let count = 0;
34// Parse text content with inline formatting, optionally wrapping in paragraph tags
35function parseTextContent(markdown, skipParagraphWrap = false) {
36 const parsedText = parseInlineFormatting(markdown);
37 return skipParagraphWrap ? parsedText : wrapTextInTag(parsedText, 'p');
38}
39
40// Parse header markdown (#, ##, ###, etc.) and convert to HTML headers (h1, h2, h3, etc.)
41function parseHeader(markdown, currentlyInList) {
42 // Count consecutive # characters at the beginning of the line
43 let headerLevel = 0;
3444 for (let i = 0; i < markdown.length; i++) {
3545 if (markdown[i] === '#') {
36 count += 1;
46 headerLevel += 1;
3747 } else {
3848 break;
3949 }
4050 }
41 if (count === 0 || count > 6) {
42 return [null, list];
51
52 // Valid headers have 1-6 # characters
53 if (headerLevel === 0 || headerLevel > 6) {
54 return [null, currentlyInList];
4355 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
56
57 // Extract header content (text after the # characters and space)
58 const headerContent = markdown.substring(headerLevel + 1);
59 const headerTag = `h${headerLevel}`;
60 const headerHtml = wrapTextInTag(headerContent, headerTag);
61
62 // If we were in a list, close the list before starting the header
63 if (currentlyInList) {
4764 return [`</ul>${headerHtml}`, false];
4865 } else {
4966 return [headerHtml, false];
5067 }
5168}
5269
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 }
70// Parse list items (starting with *) and handle list container logic
71function parseListItem(markdown, currentlyInList) {
72 if (!markdown.startsWith('*')) {
73 return [null, currentlyInList];
74 }
75
76 // Extract list item content (text after '* ')
77 const listItemContent = markdown.substring(2);
78 // Parse inline formatting within the list item, but don't wrap in paragraph tags
79 const parsedListItemContent = parseTextContent(listItemContent, true);
80 const listItemHtml = wrapTextInTag(parsedListItemContent, 'li');
81
82 if (currentlyInList) {
83 // Already in a list, just add the list item
84 return [listItemHtml, true];
85 } else {
86 // Not in a list, start a new list container
87 return [`<ul>${listItemHtml}`, true];
6188 }
62 return [null, list];
6389}
6490
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
91// Parse regular paragraphs (non-header, non-list content)
92function parseParagraph(markdown, currentlyInList) {
93 const paragraphHtml = parseTextContent(markdown, false);
94
95 if (currentlyInList) {
96 // Close the list before starting the paragraph
97 return [`</ul>${paragraphHtml}`, false];
6898 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
99 // Just return the paragraph
100 return [paragraphHtml, false];
70101 }
71102}
72103
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
104// Parse a single line of markdown, determining what type of element it is
105// Returns [htmlResult, stillInList] tuple
106function parseLine(markdown, currentlyInList) {
107 // Try to parse as header first (highest priority)
108 let [htmlResult, inListAfter] = parseHeader(markdown, currentlyInList);
109
110 // If not a header, try to parse as list item
111 if (htmlResult === null) {
112 [htmlResult, inListAfter] = parseListItem(markdown, currentlyInList);
77113 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
114
115 // If not a list item, parse as paragraph (fallback)
116 if (htmlResult === null) {
117 [htmlResult, inListAfter] = parseParagraph(markdown, currentlyInList);
80118 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
119
120 // This should never happen as paragraph parsing always succeeds
121 if (htmlResult === null) {
122 throw new Error('Failed to parse markdown line');
83123 }
84 return [result, inListAfter];
124
125 return [htmlResult, inListAfter];
85126}
86127
87128/**
88 * @param {string} markdown
89 * @returns {string}
129 * Main markdown parser function
130 * @param {string} markdown - The markdown string to parse
131 * @returns {string} - The equivalent HTML string
90132 */
91133export function parse(markdown) {
92134 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;
135 let htmlResult = '';
136 let currentlyInList = false;
137
138 // Process each line of the markdown
139 for (const line of lines) {
140 const [lineHtml, stillInList] = parseLine(line, currentlyInList);
141 htmlResult += lineHtml;
142 currentlyInList = stillInList;
99143 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
144
145 // If we ended while still in a list, close it
146 if (currentlyInList) {
147 htmlResult += '</ul>';
104148 }
149
150 return htmlResult;
105151}
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.