joseph

Finished
1function wrap(text, tag) {
1// Helper function to wrap text in HTML tags
2function wrapTextInTag(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) {
11// Generic parser for markdown delimiters (like *text*, __text__)
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 (surrounded by __)
19function parseBoldText(text) {
20 return parseMarkdownDelimiter(text, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text (surrounded by _)
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');
29 }
28// Parse inline formatting (bold and italic) in text
29function parseInlineFormatting(text) {
30 // Apply bold parsing first, then italic to handle nested cases like ___text___
31 return parseItalicText(parseBoldText(text));
32}
33
34// Process text content with inline formatting and optional paragraph wrapping
35function processTextContent(text, skipParagraphWrap = false) {
36 const formattedText = parseInlineFormatting(text);
37 return skipParagraphWrap ? formattedText : wrapTextInTag(formattedText, 'p');
3038}
3139
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
40// Parse header markdown (# Header, ## Header, etc.)
41function parseHeader(text, currentlyInList) {
42 // Count consecutive # characters at the start of the line
43 let headerLevel = 0;
44 for (let i = 0; i < text.length; i++) {
45 if (text[i] === '#') {
46 headerLevel += 1;
3747 } else {
3848 break;
3949 }
4050 }
41 if (count === 0 || count > 6) {
42 return [null, list];
51
52 // Valid headers have 1-6 # characters
53 if (headerLevel === 0 || headerLevel > 6) {
54 return [null, currentlyInList];
4355 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
56
57 // Extract header content (text after # characters and space)
58 const headerContent = text.substring(headerLevel + 1);
59 const headerTag = `h${headerLevel}`;
60 const headerHtml = wrapTextInTag(headerContent, headerTag);
61
62 // If we were in a list, close it before starting the header
63 if (currentlyInList) {
4764 return [`</ul>${headerHtml}`, false];
4865 } else {
4966 return [headerHtml, false];
5067 }
5168}
5269
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];
70// Parse list item markdown (* item)
71function parseListItem(text, currentlyInList) {
72 if (text.startsWith('*')) {
73 // Extract list item content (text after '* ')
74 const itemContent = processTextContent(text.substring(2), true);
75 const listItemHtml = wrapTextInTag(itemContent, 'li');
76
77 // If we weren't in a list, start one; otherwise continue the existing list
78 if (currentlyInList) {
79 return [listItemHtml, true];
5880 } else {
59 return [`<ul>${innerHtml}`, true];
81 return [`<ul>${listItemHtml}`, true];
6082 }
6183 }
62 return [null, list];
84 return [null, currentlyInList];
6385}
6486
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
87// Parse regular paragraph text
88function parseParagraph(text, currentlyInList) {
89 // If we were in a list, close it before starting the paragraph
90 if (currentlyInList) {
91 return [`</ul>${processTextContent(text, false)}`, false];
6892 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
93 return [processTextContent(text, false), false];
7094 }
7195}
7296
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
97// Parse a single line of markdown, determining its type and converting to HTML
98function parseLine(text, currentlyInList) {
99 // Try to parse as header first
100 let [result, inListAfter] = parseHeader(text, currentlyInList);
101
102 // If not a header, try to parse as list item
75103 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
104 [result, inListAfter] = parseListItem(text, currentlyInList);
77105 }
106
107 // If not a list item, parse as paragraph
78108 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
109 [result, inListAfter] = parseParagraph(text, currentlyInList);
80110 }
111
112 // This should never happen as one of the parsers should always succeed
81113 if (result === null) {
82 throw new Error('Remove this line and implement the function');
114 throw new Error('Unable to parse markdown line');
83115 }
116
84117 return [result, inListAfter];
85118}
86119
87120/**
88 * @param {string} markdown
89 * @returns {string}
121 * Parse markdown text and convert it to HTML
122 * @param {string} markdown - The markdown text to parse
123 * @returns {string} The corresponding HTML
90124 */
91125export function parse(markdown) {
126 // Split input into individual lines
92127 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
128 let htmlResult = '';
129 let currentlyInList = false;
130
131 // Process each line, maintaining list state across lines
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, stillInList] = parseLine(lines[i], currentlyInList);
134 htmlResult += lineHtml;
135 currentlyInList = stillInList;
99136 }
100 if (list) {
101 return result + '</ul>';
137
138 // If we ended while still in a list, close it
139 if (currentlyInList) {
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.