xas

Finished
1// Helper function to wrap text in HTML tags
12function wrap(text, tag) {
23 return `<${tag}>${text}</${tag}>`;
34}
45
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
8
9function parser(markdown, delimiter, tag) {
6// Generic function to parse inline markdown syntax (like bold, italic)
7function parseInlineSyntax(markdown, delimiter, tag) {
108 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
119 const replacement = `<${tag}>$1</${tag}>`;
1210 return markdown.replace(pattern, replacement);
1311}
1412
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
13// Parse bold text (__text__ -> <strong>text</strong>)
14function parseBold(markdown) {
15 return parseInlineSyntax(markdown, '__', 'strong');
1716}
1817
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
18// Parse italic text (_text_ -> <em>text</em>)
19function parseItalic(markdown) {
20 return parseInlineSyntax(markdown, '_', 'em');
2121}
2222
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
23// Parse text with inline formatting and wrap in paragraph tags if needed
24function parseFormattedText(markdown, isListItem = false) {
25 // First parse bold, then italic (order matters for nested formatting)
26 const formattedText = parseItalic(parseBold(markdown));
27
28 // If this is a list item, don't wrap in <p> tags
29 if (isListItem) {
30 return formattedText;
2731 } else {
28 return wrap(parsedText, 'p');
32 return wrap(formattedText, 'p');
2933 }
3034}
3135
32function parseHeader(markdown, list) {
33 let count = 0;
36// Parse header markdown (# Header -> <h1>Header</h1>)
37function parseHeader(markdown, currentlyInList) {
38 // Count the number of # characters at the start
39 let headerLevel = 0;
3440 for (let i = 0; i < markdown.length; i++) {
3541 if (markdown[i] === '#') {
36 count += 1;
42 headerLevel += 1;
3743 } else {
3844 break;
3945 }
4046 }
41 if (count === 0 || count > 6) {
42 return [null, list];
47
48 // Valid headers are h1-h6 (1-6 # characters)
49 if (headerLevel === 0 || headerLevel > 6) {
50 return [null, currentlyInList];
4351 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
52
53 // Extract header content (skip the # characters and the following space)
54 const headerContent = markdown.substring(headerLevel + 1);
55 const headerTag = `h${headerLevel}`;
56 const headerHtml = wrap(headerContent, headerTag);
57
58 // If we were in a list, close it before the header
59 if (currentlyInList) {
4760 return [`</ul>${headerHtml}`, false];
4861 } else {
4962 return [headerHtml, false];
5063 }
5164}
5265
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];
66// Parse list item markdown (* item -> <li>item</li>)
67function parseListItem(markdown, currentlyInList) {
68 // Check if line starts with '* ' (asterisk followed by space)
69 if (markdown.startsWith('* ')) {
70 // Parse the item content with inline formatting but don't wrap in <p>
71 const itemContent = parseFormattedText(markdown.substring(2), true);
72 const listItemHtml = wrap(itemContent, 'li');
73
74 // If we're already in a list, just add the list item
75 // Otherwise, start a new list
76 if (currentlyInList) {
77 return [listItemHtml, true];
5878 } else {
59 return [`<ul>${innerHtml}`, true];
79 return [`<ul>${listItemHtml}`, true];
6080 }
6181 }
62 return [null, list];
82 return [null, currentlyInList];
6383}
6484
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
85// Parse regular paragraph
86function parseParagraph(markdown, currentlyInList) {
87 // Parse text with inline formatting and wrap in <p> tags
88 const paragraphHtml = parseFormattedText(markdown, false);
89
90 // If we were in a list, close it before the paragraph
91 if (currentlyInList) {
92 return [`</ul>${paragraphHtml}`, false];
6893 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
94 return [paragraphHtml, 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 first
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] = parseListItem(markdown, currentlyInList);
77106 }
107
108 // If not a list item, parse as regular 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 * Supports:
124 * - Headers (# Header1, ## Header2, etc.)
125 * - Bold text (__bold text__)
126 * - Italic text (_italic text_)
127 * - Unordered lists (* item)
128 * - Paragraphs
129 *
130 * @param {string} markdown - The markdown text to parse
131 * @returns {string} - The resulting HTML
90132 */
91133export function parse(markdown) {
134 // Split the markdown into individual lines
92135 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
136 let htmlResult = '';
137 let isInList = false;
138
139 // Process each line
95140 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
141 let [lineHtml, newListState] = parseLine(lines[i], isInList);
142 htmlResult += lineHtml;
143 isInList = newListState;
99144 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
145
146 // If we're still in a list at the end, close it
147 if (isInList) {
148 htmlResult += '</ul>';
104149 }
150
151 return htmlResult;
105152}
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.