yahoo

Finished
1// Helper function to wrap text in HTML tags
12function wrap(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
6// Helper function to check if text starts with a specific tag
57function isTag(text, tag) {
68 return text.startsWith(`<${tag}>`);
79}
810
11// Generic parser for inline formatting (bold, italic)
912function parser(markdown, delimiter, tag) {
1013 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
1114 const replacement = `<${tag}>$1</${tag}>`;
1215 return markdown.replace(pattern, replacement);
1316}
1417
15function parse__(markdown) {
18// Parse bold text (__text__ -> <strong>text</strong>)
19function parseBold(markdown) {
1620 return parser(markdown, '__', 'strong');
1721}
1822
19function parse_(markdown) {
23// Parse italic text (_text_ -> <em>text</em>)
24function parseItalic(markdown) {
2025 return parser(markdown, '_', 'em');
2126}
2227
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
28// Parse inline formatting (bold and italic)
29function parseInlineFormatting(markdown) {
30 return parseItalic(parseBold(markdown));
31}
32
33// Parse text content, optionally wrapping in paragraph tags
34function parseText(markdown, isListItem = false) {
35 const parsedText = parseInlineFormatting(markdown);
36 if (isListItem) {
2637 return parsedText;
2738 } else {
2839 return wrap(parsedText, 'p');
2940 }
3041}
3142
32function parseHeader(markdown, list) {
33 let count = 0;
43// Parse header markdown (# Header -> <h1>Header</h1>)
44function parseHeader(markdown, currentlyInList) {
45 // Count consecutive # characters at the beginning
46 let hashCount = 0;
3447 for (let i = 0; i < markdown.length; i++) {
3548 if (markdown[i] === '#') {
36 count += 1;
49 hashCount += 1;
3750 } else {
3851 break;
3952 }
4053 }
41 if (count === 0 || count > 6) {
42 return [null, list];
54
55 // Headers must have 1-6 # characters
56 if (hashCount === 0 || hashCount > 6) {
57 return [null, currentlyInList];
4358 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
59
60 const headerTag = `h${hashCount}`;
61 const headerContent = markdown.substring(hashCount + 1);
62 const headerHtml = wrap(headerContent, headerTag);
63
64 // If we were in a list, close it before the header
65 if (currentlyInList) {
4766 return [`</ul>${headerHtml}`, false];
4867 } else {
4968 return [headerHtml, false];
5069 }
5170}
5271
53function parseLineItem(markdown, list) {
72// Parse list items (* item -> <ul><li>item</li></ul>)
73function parseLineItem(markdown, currentlyInList) {
5474 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
75 const itemContent = markdown.substring(2); // Skip '* '
76 const innerHtml = wrap(parseText(itemContent, true), 'li');
77
78 if (currentlyInList) {
79 // Continue existing list
5780 return [innerHtml, true];
5881 } else {
82 // Start new list
5983 return [`<ul>${innerHtml}`, true];
6084 }
6185 }
62 return [null, list];
86 return [null, currentlyInList];
6387}
6488
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
89// Parse paragraph content
90function parseParagraph(markdown, currentlyInList) {
91 if (currentlyInList) {
92 // Close list before starting paragraph
6993 return [`</ul>${parseText(markdown, false)}`, false];
94 } else {
95 return [parseText(markdown, false), false];
7096 }
7197}
7298
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
99// Parse a single line of markdown
100function parseLine(markdown, currentlyInList) {
101 // Try parsing as header first
102 let [result, newListState] = parseHeader(markdown, currentlyInList);
103
104 // If not a header, try parsing as list item
75105 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
106 [result, newListState] = parseLineItem(markdown, currentlyInList);
77107 }
108
109 // If neither header nor list item, parse as paragraph
78110 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
111 [result, newListState] = parseParagraph(markdown, currentlyInList);
80112 }
113
114 // This should never happen with valid input
81115 if (result === null) {
82 throw new Error('Remove this line and implement the function');
116 throw new Error('Unable to parse line: ' + markdown);
83117 }
84 return [result, inListAfter];
118
119 return [result, newListState];
85120}
86121
87122/**
88 * @param {string} markdown
89 * @returns {string}
123 * Parse Markdown text and convert it to HTML
124 * @param {string} markdown - The Markdown text to parse
125 * @returns {string} - The resulting HTML
90126 */
91127export function parse(markdown) {
92128 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
129 let htmlResult = '';
130 let isInList = false;
131
95132 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
133 let [lineHtml, newListState] = parseLine(lines[i], isInList);
134 htmlResult += lineHtml;
135 isInList = newListState;
99136 }
100 if (list) {
101 return result + '</ul>';
137
138 // Close any open list at the end
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.