future

Finished
1function wrap(text, tag) {
1// Helper function to wrap text in HTML tags
2function wrapText(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}>`;
12 return markdown.replace(pattern, replacement);
11// Generic parser for markdown delimiters (bold, italic, etc.)
12function parseMarkdownDelimiter(text, delimiter, htmlTag) {
13 // Use non-greedy matching to handle multiple occurrences correctly
14 const pattern = new RegExp(`${delimiter}([^${delimiter}]+?)${delimiter}`, 'g');
15 return text.replace(pattern, `<${htmlTag}>$1</${htmlTag}>`);
1316}
1417
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
18// Parse bold text (wrapped in __)
19function parseBoldText(text) {
20 return parseMarkdownDelimiter(text, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text (wrapped in _)
24function parseItalicText(text) {
25 return parseMarkdownDelimiter(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');
28// Parse inline formatting (bold and italic) and optionally wrap in paragraph tags
29function parseInlineFormatting(text, isListItem = false) {
30 // Parse bold text first, then italic to avoid conflicts
31 let parsedText = parseBoldText(text);
32 parsedText = parseItalicText(parsedText);
33
34 // Only wrap in paragraph tags if this is not a list item
35 if (!isListItem) {
36 return wrapText(parsedText, 'p');
2937 }
38 return parsedText;
3039}
3140
32function parseHeader(markdown, list) {
33 let count = 0;
41// Parse header markdown (# Header 1, ## Header 2, etc.)
42function parseHeader(markdown, isInList) {
43 // Count consecutive # characters at the beginning of the line
44 let headerLevel = 0;
3445 for (let i = 0; i < markdown.length; i++) {
3546 if (markdown[i] === '#') {
36 count += 1;
47 headerLevel += 1;
3748 } else {
3849 break;
3950 }
4051 }
41 if (count === 0 || count > 6) {
42 return [null, list];
52
53 // Valid headers have 1-6 # characters
54 if (headerLevel === 0 || headerLevel > 6) {
55 return [null, isInList];
4356 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
57
58 // Extract header text (skip # characters and the following space)
59 const headerText = markdown.substring(headerLevel + 1);
60 const headerTag = `h${headerLevel}`;
61 const headerHtml = wrapText(headerText, headerTag);
62
63 // If we were in a list, close it before starting the header
64 if (isInList) {
4765 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
5066 }
67
68 return [headerHtml, false];
5169}
5270
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 }
71// Parse list item markdown (* List item)
72function parseListItem(markdown, isInList) {
73 // Check if this line starts a list item
74 if (!markdown.startsWith('*')) {
75 return [null, isInList];
76 }
77
78 // Extract the list item content (skip '* ')
79 const itemContent = markdown.substring(2);
80 // Parse inline formatting within the list item
81 const parsedContent = parseInlineFormatting(itemContent, true);
82 // Wrap in list item tags
83 const listItemHtml = wrapText(parsedContent, 'li');
84
85 // If we're already in a list, just add the item
86 if (isInList) {
87 return [listItemHtml, true];
6188 }
62 return [null, list];
89
90 // If we're not in a list, start a new unordered list
91 return [`<ul>${listItemHtml}`, true];
6392}
6493
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
94// Parse paragraph markdown (plain text)
95function parseParagraph(markdown, isInList) {
96 // Parse inline formatting within the paragraph
97 const parsedContent = parseInlineFormatting(markdown, false);
98
99 // If we were in a list, close it before starting the paragraph
100 if (isInList) {
101 return [`</ul>${parsedContent}`, false];
70102 }
103
104 return [parsedContent, false];
71105}
72106
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
107// Parse a single line of markdown, determining its type and converting to HTML
108function parseLine(markdown, isInList) {
109 // Try to parse as header first
110 let [result, newInList] = parseHeader(markdown, isInList);
111
112 // If not a header, try to parse as list item
78113 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
114 [result, newInList] = parseListItem(markdown, isInList);
80115 }
116
117 // If not a list item, parse as paragraph (default case)
81118 if (result === null) {
82 throw new Error('Remove this line and implement the function');
119 [result, newInList] = parseParagraph(markdown, isInList);
83120 }
84 return [result, inListAfter];
121
122 // At this point, result should never be null as parseParagraph always returns a value
123 return [result, newInList];
85124}
86125
87126/**
88 * @param {string} markdown
89 * @returns {string}
127 * Parse markdown text and convert it to HTML
128 * @param {string} markdown - The markdown text to parse
129 * @returns {string} - The resulting HTML
90130 */
91131export function parse(markdown) {
132 // Split the markdown into individual lines
92133 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;
134 let htmlResult = '';
135 let isInList = false;
136
137 // Process each line of markdown
138 for (const line of lines) {
139 let [lineHtml, newListState] = parseLine(line, isInList);
140 htmlResult += lineHtml;
141 isInList = newListState;
99142 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
143
144 // If we finished processing while still in a list, close the list
145 if (isInList) {
146 htmlResult += '</ul>';
104147 }
148
149 return htmlResult;
105150}
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.