sn62

Finished
1function wrap(text, tag) {
1// Utility function to wrap text in HTML tags
2function wrapTextInTag(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
5function isTag(text, tag) {
6// 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 markdown delimiters (bold, italic, etc.)
12function parseMarkdownDelimiter(text, delimiter, htmlTag) {
1013 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
14 const replacement = `<${htmlTag}>$1</${htmlTag}>`;
15 return text.replace(pattern, replacement);
1316}
1417
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
18// Parse bold text (double underscores)
19function parseBoldText(text) {
20 return parseMarkdownDelimiter(text, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text (single underscore)
24function parseItalicText(text) {
25 return parseMarkdownDelimiter(text, '_', 'em');
2126}
2227
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
28// Parse inline markdown elements (bold, italic) and wrap in paragraph if not in a list
29function parseInlineMarkdownAndWrap(text, isInList) {
30 // First parse bold, then italic
31 const parsedText = parseItalicText(parseBoldText(text));
32
33 // If we're in a list, don't wrap in paragraph tags
34 if (isInList) {
2635 return parsedText;
2736 } else {
28 return wrap(parsedText, 'p');
37 return wrapTextInTag(parsedText, 'p');
2938 }
3039}
3140
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
41// Maximum number of header levels supported
42const MAX_HEADER_LEVEL = 6;
43
44// Parse markdown headers (#, ##, ###, etc.)
45function parseHeader(markdownLine, isInUnorderedList) {
46 // Count the number of consecutive # characters at the start
47 let headerLevel = 0;
48 for (let i = 0; i < markdownLine.length; i++) {
49 if (markdownLine[i] === '#') {
50 headerLevel++;
3751 } else {
3852 break;
3953 }
4054 }
41 if (count === 0 || count > 6) {
42 return [null, list];
55
56 // Valid headers have 1-6 # characters
57 if (headerLevel === 0 || headerLevel > MAX_HEADER_LEVEL) {
58 return [null, isInUnorderedList];
4359 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
60
61 // Extract header content (text after # characters and space)
62 const headerContent = markdownLine.substring(headerLevel + 1);
63 const headerTag = `h${headerLevel}`;
64 const headerHtml = wrapTextInTag(headerContent, headerTag);
65
66 // If we were in a list, close it before starting the header
67 if (isInUnorderedList) {
4768 return [`</ul>${headerHtml}`, false];
4869 } else {
4970 return [headerHtml, false];
5071 }
5172}
5273
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];
74// Parse unordered list items (starting with *)
75function parseUnorderedListItem(markdownLine, isInUnorderedList) {
76 const LIST_ITEM_MARKER = '*';
77
78 if (markdownLine.startsWith(LIST_ITEM_MARKER)) {
79 // Extract the list item content (remove "* " prefix)
80 const listItemContent = markdownLine.substring(2);
81 // Parse inline markdown and wrap in list item tags
82 const listItemHtml = wrapTextInTag(parseInlineMarkdownAndWrap(listItemContent, true), 'li');
83
84 if (isInUnorderedList) {
85 // Already in a list, just add the list item
86 return [listItemHtml, true];
5887 } else {
59 return [`<ul>${innerHtml}`, true];
88 // Start a new unordered list
89 return [`<ul>${listItemHtml}`, true];
6090 }
6191 }
62 return [null, list];
92
93 // Not a list item
94 return [null, isInUnorderedList];
6395}
6496
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
97// Parse regular paragraph text
98function parseParagraph(markdownLine, isInUnorderedList) {
99 if (!isInUnorderedList) {
100 // Not in a list, parse as regular paragraph
101 return [parseInlineMarkdownAndWrap(markdownLine, false), false];
68102 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
103 // We were in a list, close it and start a paragraph
104 return [`</ul>${parseInlineMarkdownAndWrap(markdownLine, false)}`, false];
70105 }
71106}
72107
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
108// Parse a single line of markdown by trying different parsing strategies
109function parseMarkdownLine(markdownLine, isInUnorderedList) {
110 // Try to parse as header first
111 let [result, newInListState] = parseHeader(markdownLine, isInUnorderedList);
112
113 // If not a header, try to parse as list item
75114 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
115 [result, newInListState] = parseUnorderedListItem(markdownLine, isInUnorderedList);
77116 }
117
118 // If not a list item, parse as paragraph
78119 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
120 [result, newInListState] = parseParagraph(markdownLine, isInUnorderedList);
80121 }
122
123 // This should never happen as one of the parsers should always succeed
81124 if (result === null) {
82 throw new Error('Remove this line and implement the function');
125 throw new Error('Unable to parse markdown line: No parser matched');
83126 }
84 return [result, inListAfter];
127
128 return [result, newInListState];
85129}
86130
87131/**
88 * @param {string} markdown
89 * @returns {string}
132 * Parse markdown text and convert it to HTML
133 * @param {string} markdown - The markdown text to parse
134 * @returns {string} - The resulting HTML
90135 */
91136export function parse(markdown) {
137 // Split the markdown into individual lines
92138 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
139 let htmlResult = '';
140 let isInUnorderedList = false;
141
142 // Process each line of markdown
95143 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
144 // Parse the current line and get the HTML result and updated list state
145 let [lineHtml, newListState] = parseMarkdownLine(lines[i], isInUnorderedList);
146 htmlResult += lineHtml;
147 isInUnorderedList = newListState;
99148 }
100 if (list) {
101 return result + '</ul>';
149
150 // If we're still in a list at the end, close it
151 if (isInUnorderedList) {
152 return htmlResult + '</ul>';
102153 } else {
103 return result;
154 return htmlResult;
104155 }
105156}
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.