Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion docling/backend/latex/utils/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@
pass # guarded by LatexDocumentBackend.__init__


# A {...} group, allowing one level of nested braces.
_BRACED_GROUP = r"\{(?:[^{}]|\{[^{}]*\})*\}"
# A [...] optional argument whose braced values may contain brackets,
# as in [caption={A [b] c}].
_OPTIONAL_ARGUMENT = rf"\[(?:[^\[\]{{}}]|{_BRACED_GROUP})*\]"


def iter_child_nodelists(node: Any) -> Iterator[list]:
"""Yield the node lists of ``node`` and of its group arguments."""
if isinstance(node, (LatexGroupNode, LatexEnvironmentNode, LatexMathNode)):
Expand Down Expand Up @@ -125,7 +132,13 @@ def _process_group_node(
self._process_nodes(node.nodelist, doc, parent, formatting, text_label)

def _extract_verbatim_content(self, latex_str: str, env_name: str) -> str:
pattern = rf"\\begin\{{{re.escape(env_name)}\}}(?:\[.*?\])?(.*?)\\end\{{{re.escape(env_name)}\}}"
name = re.escape(env_name)
# minted takes the code's language as a mandatory {argument}.
language = _BRACED_GROUP if env_name == "minted" else ""
pattern = (
rf"\\begin\{{{name}\}}(?:{_OPTIONAL_ARGUMENT})?{language}"
rf"(.*?)\\end\{{{name}\}}"
)
match = re.search(pattern, latex_str, re.DOTALL)
if match:
return re.sub(r"\A(?:[ \t]*\r?\n)+", "", match.group(1)).rstrip()
Expand Down
32 changes: 32 additions & 0 deletions tests/test_latex/test_environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,38 @@ def test_latex_code_environment_keeps_first_line_indentation(env_name, lead):
assert code_texts == [" key: 1\n sub:\n x: 2"]


@pytest.mark.parametrize(
"opening",
[
"\\begin{minted}{python}",
"\\begin{minted}[linenos, frame=lines]{python}",
"\\begin{lstlisting}[language=Python, caption={A [nested] cap}]",
],
)
def test_latex_code_environment_skips_its_arguments(opening):
"""Environment arguments are markup, not the first line of the code."""
env_name = opening[len("\\begin{") : opening.index("}")]
latex_content = (
"\\documentclass{article}\n"
"\\begin{document}\n"
f"{opening}\n"
" x = 1\n"
f"\\end{{{env_name}}}\n"
"\\end{document}\n"
).encode()
in_doc = InputDocument(
path_or_stream=BytesIO(latex_content),
format=InputFormat.LATEX,
backend=LatexDocumentBackend,
filename="test.tex",
)
backend = LatexDocumentBackend(in_doc=in_doc, path_or_stream=BytesIO(latex_content))
doc = backend.convert()

code_texts = [t.text for t in doc.texts if t.label == DocItemLabel.CODE]
assert code_texts == [" x = 1"]


def test_latex_subequations_environment():
"""Test subequations wrapper environment passes through inner equations."""
latex_content = rb"""
Expand Down
Loading