feat: render nested card lists
This commit is contained in:
+100
-12
@@ -2542,14 +2542,104 @@ def render_tabular_html(body: str, equation_numbers: dict[str, int], figure_numb
|
|||||||
return f'<div class="table-wrap"><table>{"".join(html_rows)}</table></div>'
|
return f'<div class="table-wrap"><table>{"".join(html_rows)}</table></div>'
|
||||||
|
|
||||||
|
|
||||||
|
LIST_BEGIN_RE = re.compile(r"\\begin\{(?P<kind>itemize|enumerate)\}(?:\[[^\]]*\])?")
|
||||||
|
LIST_TOKEN_RE = re.compile(
|
||||||
|
r"\\begin\{(?P<begin>itemize|enumerate)\}(?:\[[^\]]*\])?"
|
||||||
|
r"|\\end\{(?P<end>itemize|enumerate)\}"
|
||||||
|
r"|(?P<item>\\item\b)"
|
||||||
|
)
|
||||||
|
LIST_PLACEHOLDER_RE = re.compile(r"\x00ESC_LIST_(?P<index>\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:
|
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()]
|
items = split_top_level_list_items(body)
|
||||||
return "<ul>" + "".join(f"<li>{latex_inline_to_html(item, equation_numbers, figure_numbers)}</li>" for item in items) + "</ul>"
|
return "<ul>" + "".join(
|
||||||
|
f"<li>{render_list_item_html(item, equation_numbers, figure_numbers)}</li>" for item in items
|
||||||
|
) + "</ul>"
|
||||||
|
|
||||||
|
|
||||||
def render_enumerate_html(body: str, equation_numbers: dict[str, int], figure_numbers: dict[str, int]) -> str:
|
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()]
|
items = split_top_level_list_items(body)
|
||||||
return "<ol>" + "".join(f"<li>{latex_inline_to_html(item, equation_numbers, figure_numbers)}</li>" for item in items) + "</ol>"
|
return "<ol>" + "".join(
|
||||||
|
f"<li>{render_list_item_html(item, equation_numbers, figure_numbers)}</li>" for item in items
|
||||||
|
) + "</ol>"
|
||||||
|
|
||||||
|
|
||||||
def render_paragraphs_html(text: str, equation_numbers: dict[str, int], figure_numbers: dict[str, int]) -> list[str]:
|
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:
|
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 = {
|
patterns = {
|
||||||
"equation": r"\\begin\{equation\}(.*?)\\end\{equation\}",
|
"equation": r"\\begin\{equation\}(.*?)\\end\{equation\}",
|
||||||
"figure": r"\\begin\{figure\}\[H\](.*?)\\end\{figure\}",
|
"figure": r"\\begin\{figure\}\[H\](.*?)\\end\{figure\}",
|
||||||
"tabularx": r"\\begin\{tabularx\}.*?\n(.*?)\\end\{tabularx\}",
|
"tabularx": r"\\begin\{tabularx\}.*?\n(.*?)\\end\{tabularx\}",
|
||||||
"itemize": r"\\begin\{itemize\}(?:\[[^\]]+\])?(.*?)\\end\{itemize\}",
|
"list": r"\x00ESC_LIST_\d+\x00",
|
||||||
"enumerate": r"\\begin\{enumerate\}(?:\[[^\]]+\])?(.*?)\\end\{enumerate\}",
|
|
||||||
"verbatim": r"\\begin\{verbatim\}(.*?)\\end\{verbatim\}",
|
"verbatim": r"\\begin\{verbatim\}(.*?)\\end\{verbatim\}",
|
||||||
}
|
}
|
||||||
combined = re.compile("|".join(f"(?P<{name}>{pattern})" for name, pattern in patterns.items()), re.S)
|
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":
|
elif kind == "tabularx":
|
||||||
body = re.match(patterns["tabularx"], body, re.S).group(1) # type: ignore[union-attr]
|
body = re.match(patterns["tabularx"], body, re.S).group(1) # type: ignore[union-attr]
|
||||||
parts.append(render_tabular_html(body, equation_numbers, figure_numbers))
|
parts.append(render_tabular_html(body, equation_numbers, figure_numbers))
|
||||||
elif kind == "itemize":
|
elif kind == "list":
|
||||||
body = re.match(patterns["itemize"], body, re.S).group(1) # type: ignore[union-attr]
|
placeholder = LIST_PLACEHOLDER_RE.fullmatch(body)
|
||||||
parts.append(render_itemize_html(body, equation_numbers, figure_numbers))
|
if placeholder:
|
||||||
elif kind == "enumerate":
|
parts.append(nested_lists[int(placeholder.group("index"))])
|
||||||
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 == "verbatim":
|
elif kind == "verbatim":
|
||||||
body = re.match(patterns["verbatim"], body, re.S).group(1) # type: ignore[union-attr]
|
body = re.match(patterns["verbatim"], body, re.S).group(1) # type: ignore[union-attr]
|
||||||
parts.append(f"<pre><code>{html_escape(body.strip())}</code></pre>")
|
parts.append(f"<pre><code>{html_escape(body.strip())}</code></pre>")
|
||||||
|
|||||||
Reference in New Issue
Block a user