sn62

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 elements
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 with appropriate wrapper 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) {
43// Parse header elements (# Header -> <h1>Header</h1>)
44function parseHeader(markdown, currentlyInList) {
3345 let count = 0;
3446 for (let i = 0; i < markdown.length; i++) {
3547 if (markdown[i] === '#') {
3850 break;
3951 }
4052 }
53
54 // Headers must be between 1 and 6 # characters
4155 if (count === 0 || count > 6) {
42 return [null, list];
56 return [null, currentlyInList];
4357 }
58
4459 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
60 const headerContent = markdown.substring(count + 1);
61 const headerHtml = wrap(headerContent, headerTag);
62
63 // If we were in a list, close it before the header
64 if (currentlyInList) {
4765 return [`</ul>${headerHtml}`, false];
4866 } else {
4967 return [headerHtml, false];
5068 }
5169}
5270
53function parseLineItem(markdown, list) {
71// Parse list items (* item -> <ul><li>item</li></ul>)
72function parseLineItem(markdown, currentlyInList) {
5473 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
74 const itemContent = markdown.substring(2); // Skip '* ' prefix
75 const innerHtml = wrap(parseText(itemContent, true), 'li');
76
77 if (currentlyInList) {
78 // Continue existing list
5779 return [innerHtml, true];
5880 } else {
81 // Start new list
5982 return [`<ul>${innerHtml}`, true];
6083 }
6184 }
62 return [null, list];
85 return [null, currentlyInList];
6386}
6487
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
88// Parse paragraph elements
89function parseParagraph(markdown, currentlyInList) {
90 if (currentlyInList) {
91 // Close list before starting paragraph
6992 return [`</ul>${parseText(markdown, false)}`, false];
93 } else {
94 return [parseText(markdown, false), false];
7095 }
7196}
7297
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
98// Parse a single line of markdown
99function parseLine(markdown, currentlyInList) {
100 // Try to parse as header
101 let [result, newListState] = parseHeader(markdown, currentlyInList);
102
103 // If not a header, try to parse as list item
75104 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
105 [result, newListState] = parseLineItem(markdown, currentlyInList);
77106 }
107
108 // If not a list item, parse as paragraph
78109 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
110 [result, newListState] = parseParagraph(markdown, currentlyInList);
80111 }
112
113 // This should never happen with valid input
81114 if (result === null) {
82 throw new Error('Remove this line and implement the function');
115 throw new Error('Unable to parse line: ' + markdown);
83116 }
84 return [result, inListAfter];
117
118 return [result, newListState];
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 resulting HTML
90125 */
91126export function parse(markdown) {
92127 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;
128 let htmlResult = '';
129 let inList = false;
130
131 for (const line of lines) {
132 let [lineHtml, newListState] = parseLine(line, inList);
133 htmlResult += lineHtml;
134 inList = newListState;
99135 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
136
137 // Close any open list at the end
138 if (inList) {
139 htmlResult += '</ul>';
104140 }
141
142 return htmlResult;
105143}
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.