diff --git a/tools/render_card.py b/tools/render_card.py
index 0d2412d..dad7edf 100644
--- a/tools/render_card.py
+++ b/tools/render_card.py
@@ -2542,14 +2542,104 @@ def render_tabular_html(body: str, equation_numbers: dict[str, int], figure_numb
return f'
'
+LIST_BEGIN_RE = re.compile(r"\\begin\{(?Pitemize|enumerate)\}(?:\[[^\]]*\])?")
+LIST_TOKEN_RE = re.compile(
+ r"\\begin\{(?Pitemize|enumerate)\}(?:\[[^\]]*\])?"
+ r"|\\end\{(?Pitemize|enumerate)\}"
+ r"|(?P- \\item\b)"
+)
+LIST_PLACEHOLDER_RE = re.compile(r"\x00ESC_LIST_(?P\d+)\x00")
+
+
+def list_environment_end(text: str, begin: re.Match[str]) -> tuple[int, int] | None:
+ stack: list[str] = []
+ for token in LIST_TOKEN_RE.finditer(text, begin.start()):
+ if token.group("begin"):
+ stack.append(token.group("begin"))
+ elif token.group("end"):
+ if not stack or stack[-1] != token.group("end"):
+ return None
+ stack.pop()
+ if not stack:
+ return token.start(), token.end()
+ return None
+
+
+def split_top_level_list_items(body: str) -> list[str]:
+ item_starts: list[tuple[int, int]] = []
+ depth = 0
+ for token in LIST_TOKEN_RE.finditer(body):
+ if token.group("begin"):
+ depth += 1
+ elif token.group("end"):
+ depth = max(0, depth - 1)
+ elif token.group("item") and depth == 0:
+ item_starts.append((token.start(), token.end()))
+ items: list[str] = []
+ for index, (_start, content_start) in enumerate(item_starts):
+ content_end = item_starts[index + 1][0] if index + 1 < len(item_starts) else len(body)
+ item = body[content_start:content_end].strip().rstrip(",")
+ if item:
+ items.append(item)
+ return items
+
+
+def replace_nested_lists_html(
+ text: str,
+ equation_numbers: dict[str, int],
+ figure_numbers: dict[str, int],
+) -> tuple[str, list[str]]:
+ parts: list[str] = []
+ rendered: list[str] = []
+ cursor = 0
+ while begin := LIST_BEGIN_RE.search(text, cursor):
+ bounds = list_environment_end(text, begin)
+ if bounds is None:
+ break
+ body_end, environment_end = bounds
+ parts.append(text[cursor:begin.start()])
+ body = text[begin.end():body_end]
+ renderer = render_itemize_html if begin.group("kind") == "itemize" else render_enumerate_html
+ placeholder = f"\x00ESC_LIST_{len(rendered)}\x00"
+ rendered.append(renderer(body, equation_numbers, figure_numbers))
+ parts.append(placeholder)
+ cursor = environment_end
+ parts.append(text[cursor:])
+ return "".join(parts), rendered
+
+
+def render_list_item_html(
+ item: str,
+ equation_numbers: dict[str, int],
+ figure_numbers: dict[str, int],
+) -> str:
+ content, nested = replace_nested_lists_html(item, equation_numbers, figure_numbers)
+ parts: list[str] = []
+ cursor = 0
+ for placeholder in LIST_PLACEHOLDER_RE.finditer(content):
+ inline = content[cursor:placeholder.start()].strip()
+ if inline:
+ parts.append(latex_inline_to_html(inline, equation_numbers, figure_numbers))
+ parts.append(nested[int(placeholder.group("index"))])
+ cursor = placeholder.end()
+ inline = content[cursor:].strip()
+ if inline:
+ parts.append(latex_inline_to_html(inline, equation_numbers, figure_numbers))
+ return "".join(parts)
+
+
def render_itemize_html(body: str, equation_numbers: dict[str, int], figure_numbers: dict[str, int]) -> str:
- items = [item.strip().rstrip(",") for item in re.split(r"\\item", body) if item.strip()]
- return "
" + "".join(f"- {latex_inline_to_html(item, equation_numbers, figure_numbers)}
" for item in items) + "
"
+ items = split_top_level_list_items(body)
+ return "" + "".join(
+ f"- {render_list_item_html(item, equation_numbers, figure_numbers)}
" for item in items
+ ) + "
"
def render_enumerate_html(body: str, equation_numbers: dict[str, int], figure_numbers: dict[str, int]) -> str:
- items = [item.strip().rstrip(",") for item in re.split(r"\\item", body) if item.strip()]
- return "" + "".join(f"- {latex_inline_to_html(item, equation_numbers, figure_numbers)}
" for item in items) + "
"
+ items = split_top_level_list_items(body)
+ return "" + "".join(
+ f"- {render_list_item_html(item, equation_numbers, figure_numbers)}
" for item in items
+ ) + "
"
def render_paragraphs_html(text: str, equation_numbers: dict[str, int], figure_numbers: dict[str, int]) -> list[str]:
@@ -2558,12 +2648,12 @@ def render_paragraphs_html(text: str, equation_numbers: dict[str, int], figure_n
def render_latex_fragment_html(text: str, equation_numbers: dict[str, int], figure_numbers: dict[str, int]) -> str:
+ text, nested_lists = replace_nested_lists_html(text, equation_numbers, figure_numbers)
patterns = {
"equation": r"\\begin\{equation\}(.*?)\\end\{equation\}",
"figure": r"\\begin\{figure\}\[H\](.*?)\\end\{figure\}",
"tabularx": r"\\begin\{tabularx\}.*?\n(.*?)\\end\{tabularx\}",
- "itemize": r"\\begin\{itemize\}(?:\[[^\]]+\])?(.*?)\\end\{itemize\}",
- "enumerate": r"\\begin\{enumerate\}(?:\[[^\]]+\])?(.*?)\\end\{enumerate\}",
+ "list": r"\x00ESC_LIST_\d+\x00",
"verbatim": r"\\begin\{verbatim\}(.*?)\\end\{verbatim\}",
}
combined = re.compile("|".join(f"(?P<{name}>{pattern})" for name, pattern in patterns.items()), re.S)
@@ -2582,12 +2672,10 @@ def render_latex_fragment_html(text: str, equation_numbers: dict[str, int], figu
elif kind == "tabularx":
body = re.match(patterns["tabularx"], body, re.S).group(1) # type: ignore[union-attr]
parts.append(render_tabular_html(body, equation_numbers, figure_numbers))
- elif kind == "itemize":
- body = re.match(patterns["itemize"], body, re.S).group(1) # type: ignore[union-attr]
- parts.append(render_itemize_html(body, equation_numbers, figure_numbers))
- elif kind == "enumerate":
- body = re.match(patterns["enumerate"], body, re.S).group(1) # type: ignore[union-attr]
- parts.append(render_enumerate_html(body, equation_numbers, figure_numbers))
+ elif kind == "list":
+ placeholder = LIST_PLACEHOLDER_RE.fullmatch(body)
+ if placeholder:
+ parts.append(nested_lists[int(placeholder.group("index"))])
elif kind == "verbatim":
body = re.match(patterns["verbatim"], body, re.S).group(1) # type: ignore[union-attr]
parts.append(f"{html_escape(body.strip())}
")