Immune

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
1// Helper functions for wrapping content in HTML tags
2function wrapInTag(content, tag) {
3 return `<${tag}>${content}</${tag}>`;
34}
45
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
6// Helper functions for parsing inline markdown elements
7function parseBold(text) {
8 const pattern = new RegExp(`__([^_]+)__`);
9 const replacement = `<strong>$1</strong>`;
10 return text.replace(pattern, replacement);
711}
812
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
13function parseItalic(text) {
14 const pattern = new RegExp(`_([^_]+)_`);
15 const replacement = `<em>$1</em>`;
16 return text.replace(pattern, replacement);
1317}
1418
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
19function parseInlineElements(text) {
20 return parseItalic(parseBold(text));
1721}
1822
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
22
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
23function parseTextContent(text, isListItem = false) {
24 const parsedText = parseInlineElements(text);
25 if (isListItem) {
2626 return parsedText;
2727 } else {
28 return wrap(parsedText, 'p');
28 return wrapInTag(parsedText, 'p');
2929 }
3030}
3131
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
37 } else {
38 break;
32// Class to manage list state and HTML generation
33class ListManager {
34 constructor() {
35 this.isInList = false;
36 }
37
38 // Close list if currently open
39 closeListIfOpen() {
40 if (this.isInList) {
41 this.isInList = false;
42 return '</ul>';
3943 }
44 return '';
4045 }
41 if (count === 0 || count > 6) {
42 return [null, list];
46
47 // Open list if not already open
48 openListIfClosed() {
49 if (!this.isInList) {
50 this.isInList = true;
51 return '<ul>';
52 }
53 return '';
4354 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
47 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
55
56 // Get current list state
57 getListState() {
58 return this.isInList;
5059 }
5160}
5261
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];
62function parseHeader(line, listManager) {
63 let headerLevel = 0;
64 for (let i = 0; i < line.length; i++) {
65 if (line[i] === '#') {
66 headerLevel += 1;
5867 } else {
59 return [`<ul>${innerHtml}`, true];
68 break;
6069 }
6170 }
62 return [null, list];
71
72 // Not a header if no # or more than 6
73 if (headerLevel === 0 || headerLevel > 6) {
74 return null;
75 }
76
77 const headerTag = `h${headerLevel}`;
78 const headerContent = line.substring(headerLevel + 1).trim();
79 const headerHtml = wrapInTag(headerContent, headerTag);
80
81 // Close any open list before header
82 const listClosingTag = listManager.closeListIfOpen();
83 return listClosingTag + headerHtml;
6384}
6485
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
86function parseListItem(line, listManager) {
87 if (!line.startsWith('*')) {
88 return null;
7089 }
90
91 const itemContent = line.substring(2).trim();
92 const parsedContent = parseTextContent(itemContent, true);
93 const listItemHtml = wrapInTag(parsedContent, 'li');
94
95 // Open list if not already open
96 const listOpeningTag = listManager.openListIfClosed();
97 return listOpeningTag + listItemHtml;
7198}
7299
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
100function parseParagraph(line, listManager) {
101 // Close any open list before starting a paragraph
102 const listClosingTag = listManager.closeListIfOpen();
103 const paragraphHtml = parseTextContent(line, false);
104 return listClosingTag + paragraphHtml;
105}
106
107function parseLine(line, listManager) {
108 // Try to parse as header first
109 let result = parseHeader(line, listManager);
110 if (result !== null) {
111 return result;
80112 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
113
114 // Try to parse as list item
115 result = parseListItem(line, listManager);
116 if (result !== null) {
117 return result;
83118 }
84 return [result, inListAfter];
119
120 // Default to paragraph
121 return parseParagraph(line, listManager);
85122}
86123
87124/**
88 * @param {string} markdown
89 * @returns {string}
125 * Parse Markdown text and convert it to HTML
126 * @param {string} markdown - The Markdown text to parse
127 * @returns {string} - The resulting HTML
90128 */
91129export function parse(markdown) {
92130 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
131 let htmlResult = '';
132 const listManager = new ListManager();
133
95134 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
99 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
135 const lineHtml = parseLine(lines[i], listManager);
136 htmlResult += lineHtml;
104137 }
138
139 // Close any open list at the end of the document
140 const finalListClosingTag = listManager.closeListIfOpen();
141 return htmlResult + finalListClosingTag;
105142}
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.