couple

Finished
1function wrap(text, tag) {
1// Utility function to wrap text in HTML tags
2function wrapText(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
5function isTag(text, tag) {
6// Utility 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 text formatting (bold, italic, etc.)
12function parseTextFormatting(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 using __ delimiter
19function parseBoldText(text) {
20 return parseTextFormatting(text, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text using _ delimiter
24function parseItalicText(text) {
25 return parseTextFormatting(text, '_', '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) in text
29function parseInlineFormatting(text) {
30 return parseItalicText(parseBoldText(text));
31}
32
33// Process text content with inline formatting
34function processTextContent(text, isListItem = false) {
35 const formattedText = parseInlineFormatting(text);
36 return isListItem ? formattedText : wrapText(formattedText, 'p');
3037}
3138
32function parseHeader(markdown, list) {
33 let count = 0;
39// Parse markdown headers (#, ##, ###, etc.)
40function parseHeader(markdown, isInList) {
41 // Count consecutive # characters at the beginning
42 let headerLevel = 0;
3443 for (let i = 0; i < markdown.length; i++) {
3544 if (markdown[i] === '#') {
36 count += 1;
45 headerLevel += 1;
3746 } else {
3847 break;
3948 }
4049 }
41 if (count === 0 || count > 6) {
42 return [null, list];
50
51 // Valid headers have 1-6 # characters
52 if (headerLevel === 0 || headerLevel > 6) {
53 return [null, isInList];
4354 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
55
56 // Create the appropriate header tag (h1, h2, etc.)
57 const headerTag = `h${headerLevel}`;
58 const headerContent = markdown.substring(headerLevel + 1);
59 const headerHtml = wrapText(headerContent, headerTag);
60
61 // If we were in a list, close it before starting the header
62 if (isInList) {
4763 return [`</ul>${headerHtml}`, false];
4864 } else {
4965 return [headerHtml, false];
5066 }
5167}
5268
53function parseLineItem(markdown, list) {
69// Parse list items (* item)
70function parseListItem(markdown, isInList) {
5471 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
72 const itemContent = markdown.substring(2); // Remove '* ' prefix
73 const formattedContent = processTextContent(itemContent, true);
74 const listItemHtml = wrapText(formattedContent, 'li');
75
76 if (isInList) {
77 // Continue existing list
78 return [listItemHtml, true];
5879 } else {
59 return [`<ul>${innerHtml}`, true];
80 // Start 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
88function parseParagraph(markdown, isInList) {
89 if (!isInList) {
90 // Not in a list, create a simple paragraph
91 return [processTextContent(markdown, false), false];
6892 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
93 // Was in a list, close it and create a paragraph
94 return [`</ul>${processTextContent(markdown, false)}`, false];
7095 }
7196}
7297
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
98// Parse a single line of markdown, determining its type and converting to HTML
99function parseLine(markdown, isInList) {
100 // Try to parse as header first
101 let [result, newInList] = parseHeader(markdown, isInList);
102
103 // If not a header, try to parse as list item
75104 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
105 [result, newInList] = parseListItem(markdown, isInList);
77106 }
107
108 // If not a list item, parse as paragraph
78109 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
110 [result, newInList] = parseParagraph(markdown, isInList);
80111 }
112
113 // This should never happen as one of the parsers should always match
81114 if (result === null) {
82 throw new Error('Remove this line and implement the function');
115 throw new Error('Unable to parse markdown line');
83116 }
84 return [result, inListAfter];
117
118 return [result, newInList];
85119}
86120
87121/**
88 * @param {string} markdown
89 * @returns {string}
122 * Parse markdown text and convert it to HTML
123 * @param {string} markdown - The markdown text to parse
124 * @returns {string} - The equivalent HTML
90125 */
91126export function parse(markdown) {
92127 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
128 let htmlResult = '';
129 let isInList = false;
130
131 // Process each line of markdown
95132 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
133 const [lineHtml, newInListState] = parseLine(lines[i], isInList);
134 htmlResult += lineHtml;
135 isInList = newInListState;
99136 }
100 if (list) {
101 return result + '</ul>';
137
138 // If we ended while still in a list, close it
139 if (isInList) {
140 return htmlResult + '</ul>';
102141 } else {
103 return result;
142 return htmlResult;
104143 }
105144}
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.