Skip to content

Commit ace1b72

Browse files
committed
fix: handle ordered lists bbcode
1 parent ea54ac6 commit ace1b72

5 files changed

Lines changed: 96 additions & 6 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ output.bbcode
77
output.html
88
output.md
99
output.ast
10+
input.md
1011
testme.md
1112
test2.md
1213
dist/

src/md2bbcode/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
from mistune.plugins.spoiler import spoiler
1818

1919
# local
20+
from md2bbcode.plugins.merge_lists import merge_ordered_lists
2021
from md2bbcode.renderers.bbcode import BBCodeRenderer
2122
from md2bbcode.html2bbcode import process_html
2223

2324
def convert_markdown_to_bbcode(markdown_text, domain):
2425
# Create a Markdown parser instance using the custom BBCode renderer
25-
markdown_parser = mistune.create_markdown(renderer=BBCodeRenderer(domain=domain), plugins=[strikethrough, mark, superscript, subscript, insert, table, footnotes, task_lists, def_list, abbr, spoiler, table_in_list])
26+
markdown_parser = mistune.create_markdown(renderer=BBCodeRenderer(domain=domain), plugins=[strikethrough, mark, superscript, subscript, insert, table, footnotes, task_lists, def_list, abbr, spoiler, table_in_list, merge_ordered_lists])
2627

2728
# Convert Markdown text to BBCode
2829
return markdown_parser(markdown_text)

src/md2bbcode/md2ast.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
from mistune.plugins.abbr import abbr
1111
from mistune.plugins.spoiler import spoiler
1212

13+
#local
14+
from md2bbcode.plugins.merge_lists import merge_ordered_lists
15+
1316
def convert_markdown_to_ast(input_filepath, output_filepath):
1417
# Initialize Markdown parser with no renderer to produce an AST
15-
markdown_parser = mistune.create_markdown(renderer=None, plugins=[strikethrough, mark, superscript, subscript, insert, table, footnotes, task_lists, def_list, abbr, spoiler, table_in_list])
18+
markdown_parser = mistune.create_markdown(renderer=None, plugins=[strikethrough, mark, superscript, subscript, insert, table, footnotes, task_lists, def_list, abbr, spoiler, table_in_list, merge_ordered_lists])
1619

1720
# Read the input Markdown file
1821
with open(input_filepath, 'r', encoding='utf-8') as md_file:
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from typing import Dict, Any, List
2+
3+
def merge_ordered_lists(md):
4+
"""
5+
A plugin to merge consecutive "top-level" ordered lists into one,
6+
and also attach any intervening code blocks or blank lines to the
7+
last list item so that the final BBCode appears as a single list
8+
with multiple steps.
9+
10+
This relies on a few assumptions:
11+
1) The only tokens between two ordered lists that should be merged
12+
are code blocks or blank lines (not normal paragraphs).
13+
2) We want any code block(s) right after a list item to appear in
14+
that same bullet item.
15+
"""
16+
17+
def rewrite_tokens(md, state):
18+
tokens = state.tokens
19+
merged = []
20+
i = 0
21+
22+
while i < len(tokens):
23+
token = tokens[i]
24+
25+
# Check if this token is a top-level ordered list
26+
if (
27+
token["type"] == "list"
28+
and token.get("attrs", {}).get("ordered", False)
29+
and token.get("attrs", {}).get("depth", 0) == 0
30+
):
31+
# Start new merged list
32+
current_depth = token["attrs"]["depth"]
33+
list_items = list(token["children"]) # bullet items in the first list
34+
i += 1
35+
36+
# Continue until we run into something that's not:
37+
# another top-level ordered list,
38+
# or code blocks / blank lines (which we'll attach to the last bullet).
39+
while i < len(tokens):
40+
nxt = tokens[i]
41+
42+
# If there's another ordered list at the same depth, merge its bullet items
43+
if (
44+
nxt["type"] == "list"
45+
and nxt.get("attrs", {}).get("ordered", False)
46+
and nxt.get("attrs", {}).get("depth", 0) == current_depth
47+
):
48+
list_items.extend(nxt["children"])
49+
i += 1
50+
51+
# If there's a code block or blank line, attach it to the *last* bullet item.
52+
elif nxt["type"] in ["block_code", "blank_line"]:
53+
if list_items: # attach to last bullet item, if any
54+
list_items[-1]["children"].append(nxt)
55+
i += 1
56+
57+
else:
58+
# Not a same-depth list or code block—stop merging
59+
break
60+
61+
# Create single merged list token
62+
merged.append(
63+
{
64+
"type": "list",
65+
"children": list_items,
66+
"attrs": {
67+
"ordered": True,
68+
"depth": current_depth,
69+
},
70+
}
71+
)
72+
73+
else:
74+
# If not a top-level ordered list, just keep it as-is
75+
merged.append(token)
76+
i += 1
77+
78+
# Replace the old tokens with the merged version
79+
state.tokens = merged
80+
81+
# Attach to before_render_hooks so we can manipulate tokens before rendering
82+
md.before_render_hooks.append(rewrite_tokens)
83+
return md

src/md2bbcode/renderers/bbcode.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,12 @@ def block_code(self, code: str, **attrs) -> str:
107107
# Check if the language needs special handling
108108
bbcode_lang = special_cases.get(lang, lang) # Use the special case if it exists, otherwise use lang as is
109109
if bbcode_lang:
110-
return f"[CODE={bbcode_lang}]{escape_text(code)}[/CODE]\n\n"
110+
return f"[CODE={bbcode_lang}]{escape_text(code)}[/CODE]\n"
111111
else:
112-
return f"[CODE]{escape_text(code)}[/CODE]\n\n"
112+
return f"[CODE]{escape_text(code)}[/CODE]\n"
113113
else:
114114
# No language specified, render with a generic [CODE] tag
115-
return f"[CODE]{escape_text(code)}[/CODE]\n\n"
115+
return f"[CODE]{escape_text(code)}[/CODE]\n"
116116

117117
def block_quote(self, text: str) -> str:
118118
return '[QUOTE]\n' + text + '[/QUOTE]\n'
@@ -126,7 +126,9 @@ def block_error(self, text: str) -> str:
126126
return '[color=red][icode]' + text + '[/icode][/color]\n'
127127

128128
def list(self, text: str, ordered: bool, **attrs) -> str:
129-
tag = 'list' if not ordered else 'list=1'
129+
# For ordered lists, always use [list=1] to get automatic sequential numbering
130+
# For unordered lists, use [list]
131+
tag = 'list=1' if ordered else 'list'
130132
return '[{}]'.format(tag) + text + '[/list]\n'
131133

132134
def list_item(self, text: str) -> str:

0 commit comments

Comments
 (0)