Finished
11import re
22
33
4def _apply_text_formatting(text):
5 """Apply bold and italic formatting to text."""
6 # Apply bold formatting (__text__)
7 bold_match = re.match('(.*)__(.*)__(.*)', text)
8 if bold_match:
9 text = bold_match.group(1) + '<strong>' + bold_match.group(2) + '</strong>' + bold_match.group(3)
10
11 # Apply italic formatting (_text_)
12 italic_match = re.match('(.*)_(.*)_(.*)', text)
13 if italic_match:
14 text = italic_match.group(1) + '<em>' + italic_match.group(2) + '</em>' + italic_match.group(3)
15
16 return text
17
18
19def _parse_header(line):
20 """Parse markdown headers and return HTML header tag or None if not a header."""
21 header_patterns = [
22 (r'###### (.*)', '<h6>', '</h6>', 7),
23 (r'##### (.*)', '<h5>', '</h5>', 6),
24 (r'#### (.*)', '<h4>', '</h4>', 5),
25 (r'### (.*)', '<h3>', '</h3>', 4),
26 (r'## (.*)', '<h2>', '</h2>', 3),
27 (r'# (.*)', '<h1>', '</h1>', 2),
28 ]
29
30 for pattern, open_tag, close_tag, prefix_length in header_patterns:
31 match = re.match(pattern, line)
32 if match:
33 content = line[prefix_length:]
34 return open_tag + content + close_tag
35
36 return None
37
38
39def _parse_list_item(line):
40 """Parse markdown list items and return the content or None if not a list item."""
41 match = re.match(r'\* (.*)', line)
42 if match:
43 return match.group(1)
44 return None
45
46
47def _wrap_in_paragraph_if_needed(line):
48 """Wrap line in paragraph tags if it is not already an HTML element."""
49 if not re.match('<h|<ul|<p|<li', line):
50 return '<p>' + line + '</p>'
51 return line
52
53
454def parse(markdown):
55 """Parse markdown text and return HTML."""
556 lines = markdown.split('\n')
6 res = ''
57 result = ''
758 in_list = False
8 in_list_append = False
9 for i in lines:
10 if re.match('###### (.*)', i) is not None:
11 i = '<h6>' + i[7:] + '</h6>'
12 elif re.match('##### (.*)', i) is not None:
13 i = '<h5>' + i[6:] + '</h5>'
14 elif re.match('#### (.*)', i) is not None:
15 i = '<h4>' + i[5:] + '</h4>'
16 elif re.match('### (.*)', i) is not None:
17 i = '<h3>' + i[4:] + '</h3>'
18 elif re.match('## (.*)', i) is not None:
19 i = '<h2>' + i[3:] + '</h2>'
20 elif re.match('# (.*)', i) is not None:
21 i = '<h1>' + i[2:] + '</h1>'
22 m = re.match(r'\* (.*)', i)
23 if m:
24 if not in_list:
25 in_list = True
26 is_bold = False
27 is_italic = False
28 curr = m.group(1)
29 m1 = re.match('(.*)__(.*)__(.*)', curr)
30 if m1:
31 curr = m1.group(1) + '<strong>' + \
32 m1.group(2) + '</strong>' + m1.group(3)
33 is_bold = True
34 m1 = re.match('(.*)_(.*)_(.*)', curr)
35 if m1:
36 curr = m1.group(1) + '<em>' + m1.group(2) + \
37 '</em>' + m1.group(3)
38 is_italic = True
39 i = '<ul><li>' + curr + '</li>'
40 else:
41 is_bold = False
42 is_italic = False
43 curr = m.group(1)
44 m1 = re.match('(.*)__(.*)__(.*)', curr)
45 if m1:
46 is_bold = True
47 m1 = re.match('(.*)_(.*)_(.*)', curr)
48 if m1:
49 is_italic = True
50 if is_bold:
51 curr = m1.group(1) + '<strong>' + \
52 m1.group(2) + '</strong>' + m1.group(3)
53 if is_italic:
54 curr = m1.group(1) + '<em>' + m1.group(2) + \
55 '</em>' + m1.group(3)
56 i = '<li>' + curr + '</li>'
59 needs_list_close = False
60
61 for line in lines:
62 # Parse headers
63 header_html = _parse_header(line)
64 if header_html:
65 line = header_html
5766 else:
58 if in_list:
59 in_list_append = True
60 in_list = False
61
62 m = re.match('<h|<ul|<p|<li', i)
63 if not m:
64 i = '<p>' + i + '</p>'
65 m = re.match('(.*)__(.*)__(.*)', i)
66 if m:
67 i = m.group(1) + '<strong>' + m.group(2) + '</strong>' + m.group(3)
68 m = re.match('(.*)_(.*)_(.*)', i)
69 if m:
70 i = m.group(1) + '<em>' + m.group(2) + '</em>' + m.group(3)
71 if in_list_append:
72 i = '</ul>' + i
73 in_list_append = False
74 res += i
67 # Parse list items
68 list_content = _parse_list_item(line)
69 if list_content:
70 formatted_content = _apply_text_formatting(list_content)
71
72 if not in_list:
73 # Start a new list
74 line = '<ul><li>' + formatted_content + '</li>'
75 in_list = True
76 else:
77 # Continue existing list
78 line = '<li>' + formatted_content + '</li>'
79 else:
80 # Not a list item - close list if we were in one
81 if in_list:
82 needs_list_close = True
83 in_list = False
84
85 # Apply paragraph wrapping if needed
86 line = _wrap_in_paragraph_if_needed(line)
87
88 # Apply text formatting (bold/italic) to non-list content
89 if not _parse_list_item(line):
90 line = _apply_text_formatting(line)
91
92 # Close list if needed
93 if needs_list_close:
94 line = '</ul>' + line
95 needs_list_close = False
96
97 result += line
98
99 # Close any remaining open list
75100 if in_list:
76 res += '</ul>'
77 return res
101 result += '</ul>'
102
103 return result
Test NameStatus
test_h7_header_level_is_a_paragraph
Pass
test_mixed_normal_italics_and_bold_text
Pass
test_parses_normal_text_as_a_paragraph
Pass
test_parsing_bold_text
Pass
test_parsing_italics
Pass
test_unordered_lists
Pass
test_unordered_lists_close_properly_with_preceding_and_following_lines
Pass
test_with_a_little_bit_of_everything
Pass
test_with_h1_header_level
Pass
test_with_h2_header_level
Pass
test_with_h3_header_level
Pass
test_with_h4_header_level
Pass
test_with_h5_header_level
Pass
test_with_h6_header_level
Pass
test_with_markdown_symbols_in_the_header_text_that_should_not_be_interpreted
Pass
test_with_markdown_symbols_in_the_list_item_text_that_should_not_be_interpreted
Pass
test_with_markdown_symbols_in_the_paragraph_text_that_should_not_be_interpreted
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.