Skip to content

Commit

Permalink
👌 Improve parsing of nested amsmath
Browse files Browse the repository at this point in the history
the previous logic was problematic for amsmath blocks nested in other blocs (such as blockquotes)

the parsing code now principally follows the logic in markdown_it/rules_block/fence.py,
except that:
(a) it allows for closing tag on same line as opening tag
(b) it does not allow for opening tag without closing tag (i.e. no auto-closing)
  • Loading branch information
chrisjsewell committed Sep 6, 2024
1 parent 637f7e7 commit b11ddd7
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 33 deletions.
71 changes: 42 additions & 29 deletions mdit_py_plugins/amsmath/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
# whose total width is the actual width of the contents;
# thus they can be used as a component in a containing expression

RE_OPEN = re.compile(r"\\begin\{(" + "|".join(ENVIRONMENTS) + r")([\*]?)\}")
RE_OPEN = r"\\begin\{(" + "|".join(ENVIRONMENTS) + r")([\*]?)\}"


def amsmath_plugin(
Expand Down Expand Up @@ -95,47 +95,60 @@ def render_amsmath_block(
md.add_render_rule("amsmath", render_amsmath_block)


def match_environment(string: str) -> None | tuple[str, str, int]:
match_open = RE_OPEN.match(string)
if not match_open:
return None
environment = match_open.group(1)
numbered = match_open.group(2)
match_close = re.search(
r"\\end\{" + environment + numbered.replace("*", r"\*") + "\\}", string
)
if not match_close:
return None
return (environment, numbered, match_close.end())


def amsmath_block(
state: StateBlock, startLine: int, endLine: int, silent: bool
) -> bool:
# note the code principally follows the logic in markdown_it/rules_block/fence.py,
# except that:
# (a) it allows for closing tag on same line as opening tag
# (b) it does not allow for opening tag without closing tag (i.e. no auto-closing)

if is_code_block(state, startLine):
return False

begin = state.bMarks[startLine] + state.tShift[startLine]
# does the first line contain the beginning of an amsmath environment
first_start = state.bMarks[startLine] + state.tShift[startLine]
first_end = state.eMarks[startLine]
first_text = state.src[first_start:first_end]

outcome = match_environment(state.src[begin:])
if not outcome:
if not (match_open := re.match(RE_OPEN, first_text)):
return False
environment, numbered, endpos = outcome
endpos += begin

line = startLine
while line < endLine:
if endpos >= state.bMarks[line] and endpos <= state.eMarks[line]:
# line for end of block math found ...
state.line = line + 1

# construct the closing tag
environment = match_open.group(1)
numbered = match_open.group(2)
closing = rf"\end{{{match_open.group(1)}{match_open.group(2)}}}"

# start looking for the closing tag, including the current line
nextLine = startLine - 1

while True:
nextLine += 1
if nextLine >= endLine:
# reached the end of the block without finding the closing tag
return False

Check warning on line 129 in mdit_py_plugins/amsmath/__init__.py

View check run for this annotation

Codecov / codecov/patch

mdit_py_plugins/amsmath/__init__.py#L129

Added line #L129 was not covered by tests

next_start = state.bMarks[nextLine] + state.tShift[nextLine]
next_end = state.eMarks[nextLine]
if next_start < first_end and state.sCount[nextLine] < state.blkIndent:
# non-empty line with negative indent should stop the list:
# - \begin{align}
# test
return False

Check warning on line 137 in mdit_py_plugins/amsmath/__init__.py

View check run for this annotation

Codecov / codecov/patch

mdit_py_plugins/amsmath/__init__.py#L137

Added line #L137 was not covered by tests

if state.src[next_start:next_end].rstrip().endswith(closing):
# found the closing tag
break
line += 1

state.line = nextLine + 1

if not silent:
token = state.push("amsmath", "math", 0)
token.block = True
token.content = state.src[begin:endpos]
token.content = state.getLines(
startLine, state.line, state.sCount[startLine], False
)
token.meta = {"environment": environment, "numbered": numbered}
token.map = [startLine, line]
token.map = [startLine, nextLine]

return True
36 changes: 32 additions & 4 deletions tests/fixtures/amsmath.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ a = 1
</div>
.

equation environment on one line:
.
\begin{equation}a = 1\end{equation}
.
<div class="math amsmath">
\begin{equation}a = 1\end{equation}
</div>
.

equation* environment:
.
\begin{equation*}
Expand Down Expand Up @@ -181,13 +190,32 @@ equation environment, in list:
<li>
<div class="math amsmath">
\begin{equation}
a = 1
\end{equation}
a = 1
\end{equation}
</div>
</li>
</ul>
.

equation environment, in block quote:
.
> \begin{matrix}
> -0.707 & 0.408 & 0.577 \\
> -0.707 & -0.408 & -0.577 \\
> -0. & -0.816 & 0.577
> \end{matrix}
.
<blockquote>
<div class="math amsmath">
\begin{matrix}
-0.707 &amp; 0.408 &amp; 0.577 \\
-0.707 &amp; -0.408 &amp; -0.577 \\
-0. &amp; -0.816 &amp; 0.577
\end{matrix}
</div>
</blockquote>
.

`alignat` environment and HTML escaping
.
\begin{alignat}{3}
Expand Down Expand Up @@ -242,7 +270,7 @@ Indented by 4 spaces, DISABLE-CODEBLOCKS
.
<div class="math amsmath">
\begin{equation}
a = 1
\end{equation}
a = 1
\end{equation}
</div>
.

0 comments on commit b11ddd7

Please sign in to comment.