unicode

Finished
1function wrap(text, tag) {
1// Utility function to wrap text in HTML tags
2function wrapTextInTag(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
5function isTag(text, tag) {
6// 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 delimiter-based markdown (bold, italic, etc.)
12function parseDelimitedText(markdown, delimiter, htmlTag) {
1013 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
14 const replacement = `<${htmlTag}>$1</${htmlTag}>`;
1215 return markdown.replace(pattern, replacement);
1316}
1417
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
18// Parse bold text (using __ delimiter)
19function parseBoldText(markdown) {
20 return parseDelimitedText(markdown, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text (using _ delimiter)
24function parseItalicText(markdown) {
25 return parseDelimitedText(markdown, '_', 'em');
2126}
2227
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
28// Parse inline markdown elements (bold, italic) and wrap in paragraph tags if not in a list
29function parseInlineAndWrap(markdown, inList) {
30 // First parse bold, then italic (order matters for nested elements)
31 const withBold = parseBoldText(markdown);
32 const withItalic = parseItalicText(withBold);
33
34 // If we're in a list, don't wrap in paragraph tags
35 if (inList) {
36 return withItalic;
2737 } else {
28 return wrap(parsedText, 'p');
38 return wrapTextInTag(withItalic, 'p');
2939 }
3040}
3141
32function parseHeader(markdown, list) {
33 let count = 0;
42// Parse header markdown (# ## ### etc.) and handle list state transitions
43function parseHeaderLine(markdown, inList) {
44 // Count consecutive # characters at the beginning
45 let hashCount = 0;
3446 for (let i = 0; i < markdown.length; i++) {
3547 if (markdown[i] === '#') {
36 count += 1;
48 hashCount += 1;
3749 } else {
3850 break;
3951 }
4052 }
41 if (count === 0 || count > 6) {
42 return [null, list];
53
54 // Valid headers have 1-6 # characters
55 if (hashCount === 0 || hashCount > 6) {
56 return [null, inList];
4357 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
58
59 // Extract header content (skip # characters and the following space)
60 const headerContent = markdown.substring(hashCount + 1);
61 const headerTag = `h${hashCount}`;
62 const headerHtml = wrapTextInTag(headerContent, headerTag);
63
64 // If we were in a list, close it before starting the header
65 if (inList) {
4766 return [`</ul>${headerHtml}`, false];
4867 } else {
4968 return [headerHtml, false];
5069 }
5170}
5271
53function parseLineItem(markdown, list) {
72// Parse list item markdown (* item) and handle list state transitions
73function parseListItemLine(markdown, inList) {
5474 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
75 // Extract list item content (skip '* ' prefix)
76 const itemContent = markdown.substring(2);
77 const parsedContent = parseInlineAndWrap(itemContent, true);
78 const listItemHtml = wrapTextInTag(parsedContent, 'li');
79
80 // If we weren't already in a list, start one
81 if (inList) {
82 return [listItemHtml, true];
5883 } else {
59 return [`<ul>${innerHtml}`, true];
84 return [`<ul>${listItemHtml}`, true];
6085 }
6186 }
62 return [null, list];
87 return [null, inList];
6388}
6489
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
90// Parse paragraph markdown (default case) and handle list state transitions
91function parseParagraphLine(markdown, inList) {
92 // If we were in a list, close it before starting the paragraph
93 if (inList) {
94 const paragraphHtml = parseInlineAndWrap(markdown, false);
95 return [`</ul>${paragraphHtml}`, false];
6896 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
97 const paragraphHtml = parseInlineAndWrap(markdown, false);
98 return [paragraphHtml, false];
7099 }
71100}
72101
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
102// Parse a single line of markdown by trying different parsers in order
103function parseLineOfType(markdown, inList) {
104 // Try parsing as header first
105 let [result, newListState] = parseHeaderLine(markdown, inList);
106
107 // If not a header, try parsing as list item
75108 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
109 [result, newListState] = parseListItemLine(markdown, inList);
77110 }
111
112 // If neither header nor list item, parse as paragraph
78113 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
114 [result, newListState] = parseParagraphLine(markdown, inList);
80115 }
116
117 // This should never happen with valid input
81118 if (result === null) {
82 throw new Error('Remove this line and implement the function');
119 throw new Error('Unable to parse markdown line: ' + markdown);
83120 }
84 return [result, inListAfter];
121
122 return [result, newListState];
85123}
86124
87125/**
88 * @param {string} markdown
89 * @returns {string}
126 * Parse markdown text and convert it to HTML
127 * @param {string} markdown - The markdown text to parse
128 * @returns {string} The resulting HTML
90129 */
91130export function parse(markdown) {
92131 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
132 let htmlResult = '';
133 let inList = false;
134
135 // Process each line of markdown
95136 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
137 const [lineHtml, newInListState] = parseLineOfType(lines[i], inList);
138 htmlResult += lineHtml;
139 inList = newInListState;
99140 }
100 if (list) {
101 return result + '</ul>';
141
142 // If we ended while still in a list, close it
143 if (inList) {
144 return htmlResult + '</ul>';
102145 } else {
103 return result;
146 return htmlResult;
104147 }
105148}
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.