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// Utility 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 (bold, italic, etc.)
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 marked with __text__
19function parseBoldText(text) {
20 return parseMarkdownDelimiter(text, '__', 'strong');
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
23// Parse italic text marked with _text_
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;
28// Parse regular text, applying bold and italic formatting
29function parseRegularText(text, isListItem = false) {
30 // Apply bold and italic formatting
31 const formattedText = parseItalicText(parseBoldText(text));
32
33 // If this is part of a list item, don't wrap in paragraph tags
34 if (isListItem) {
35 return formattedText;
2736 } else {
28 return wrap(parsedText, 'p');
37 return wrapTextInTag(formattedText, 'p');
2938 }
3039}
3140
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
41// Parse markdown headers (h1-h6) marked with # symbols
42function parseHeader(line, currentlyInList) {
43 // Count the number of # symbols at the beginning
44 let headerLevel = 0;
45 for (let i = 0; i < line.length; i++) {
46 if (line[i] === '#') {
47 headerLevel += 1;
3748 } else {
3849 break;
3950 }
4051 }
41 if (count === 0 || count > 6) {
42 return [null, list];
52
53 // If no # symbols or too many (more than 6), this is not a header
54 if (headerLevel === 0 || headerLevel > 6) {
55 return [null, currentlyInList];
4356 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
57
58 // Extract the header content (after the # symbols and space)
59 const headerContent = line.substring(headerLevel + 1);
60 const headerTag = `h${headerLevel}`;
61 const headerHtml = wrapTextInTag(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) {
54 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
71// Parse list items marked with * symbol
72function parseListItem(line, currentlyInList) {
73 if (line.startsWith('*')) {
74 // Extract the list item content (after * and space)
75 const itemContent = line.substring(2);
76 // Parse the content as list item text (no paragraph tags)
77 const parsedContent = parseRegularText(itemContent, true);
78 // Wrap in list item tags
79 const listItemHtml = wrapTextInTag(parsedContent, 'li');
80
81 if (currentlyInList) {
82 // Already in a list, just add the item
83 return [listItemHtml, true];
5884 } else {
59 return [`<ul>${innerHtml}`, true];
85 // Starting a new list
86 return [`<ul>${listItemHtml}`, true];
6087 }
6188 }
62 return [null, list];
89
90 // Not a list item
91 return [null, currentlyInList];
6392}
6493
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
94// Parse regular paragraphs (fallback when not header or list item)
95function parseParagraph(line, currentlyInList) {
96 const paragraphHtml = parseRegularText(line, false);
97
98 if (currentlyInList) {
99 // Close the list before starting a paragraph
100 return [`</ul>${paragraphHtml}`, false];
68101 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
102 // Just a regular paragraph
103 return [paragraphHtml, false];
70104 }
71105}
72106
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
107// Main line parser - determines what type of markdown line this is
108// // and dispatches to the appropriate parser
109function parseMarkdownLine(line, currentlyInList) {
110 // Try to parse as header first
111 let [htmlResult, stillInList] = parseHeader(line, currentlyInList);
112
113 if (htmlResult === null) {
114 // Try to parse as list item
115 [htmlResult, stillInList] = parseListItem(line, currentlyInList);
77116 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
117
118 if (htmlResult === null) {
119 // Try to parse as paragraph (fallback)
120 [htmlResult, stillInList] = parseParagraph(line, currentlyInList);
80121 }
81 if (result === null) {
122
123 if (htmlResult === null) {
124 // This should never happen since one of the parsers should always work
82125 throw new Error('Remove this line and implement the function');
83126 }
84 return [result, inListAfter];
127
128 return [htmlResult, stillInList];
85129}
86130
87131/**
88 * @param {string} markdown
89 * @returns {string}
132 * Parse markdown text and convert it to HTML
133 * @param {string} markdown - The markdown text to parse
134 * @returns {string} - The resulting HTML
90135 */
91136export function parse(markdown) {
92137 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
138 let htmlResult = '';
139 let currentlyInList = false;
140
141 // Process each line of the markdown text
95142 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
143 const [lineHtml, newListState] = parseMarkdownLine(lines[i], currentlyInList);
144 htmlResult += lineHtml;
145 currentlyInList = newListState;
99146 }
100 if (list) {
101 return result + '</ul>';
147
148 // If we're still in a list at the end, close it
149 if (currentlyInList) {
150 return htmlResult + '</ul>';
102151 } else {
103 return result;
152 return htmlResult;
104153 }
105154}
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.