Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mdit_py_plugins/amsmath/__init__.py: 48%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

52 statements  

1"""An extension to capture amsmath latex environments.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Callable, Sequence 

6import re 

7from typing import TYPE_CHECKING 

8 

9from markdown_it import MarkdownIt 

10from markdown_it.common.utils import escapeHtml 

11from markdown_it.rules_block import StateBlock 

12 

13from mdit_py_plugins.utils import is_code_block 

14 

15if TYPE_CHECKING: 

16 from markdown_it.renderer import RendererProtocol 

17 from markdown_it.token import Token 

18 from markdown_it.utils import EnvType, OptionsDict 

19 

20# Taken from amsmath version 2.1 

21# http://anorien.csc.warwick.ac.uk/mirrors/CTAN/macros/latex/required/amsmath/amsldoc.pdf 

22ENVIRONMENTS = [ 

23 # 3.2 single equation with an automatically gen-erated number 

24 "equation", 

25 # 3.3 variation equation, used for equations that dont fit on a single line 

26 "multline", 

27 # 3.5 a group of consecutive equations when there is no alignment desired among them 

28 "gather", 

29 # 3.6 Used for two or more equations when vertical alignment is desired 

30 "align", 

31 # allows the horizontal space between equationsto be explicitly specified. 

32 "alignat", 

33 # stretches the space betweenthe equation columns to the maximum possible width 

34 "flalign", 

35 # 4.1 The pmatrix, bmatrix, Bmatrix, vmatrix and Vmatrix have (respectively) 

36 # (),[],{},||,and ‖‖ delimiters built in. 

37 "matrix", 

38 "pmatrix", 

39 "bmatrix", 

40 "Bmatrix", 

41 "vmatrix", 

42 "Vmatrix", 

43 # eqnarray is another math environment, it is not part of amsmath, 

44 # and note that it is better to use align or equation+split instead 

45 "eqnarray", 

46] 

47# other "non-top-level" environments: 

48 

49# 3.4 the split environment is for single equations that are too long to fit on one line 

50# and hence must be split into multiple lines, 

51# it is intended for use only inside some other displayed equation structure, 

52# usually an equation, align, or gather environment 

53 

54# 3.7 variants gathered, aligned,and alignedat are provided 

55# whose total width is the actual width of the contents; 

56# thus they can be used as a component in a containing expression 

57 

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

59 

60 

61def amsmath_plugin( 

62 md: MarkdownIt, *, renderer: Callable[[str], str] | None = None 

63) -> None: 

64 """Parses TeX math equations, without any surrounding delimiters, 

65 only for top-level `amsmath <https://ctan.org/pkg/amsmath>`__ environments: 

66 

67 .. code-block:: latex 

68 

69 \\begin{gather*} 

70 a_1=b_1+c_1\\\\ 

71 a_2=b_2+c_2-d_2+e_2 

72 \\end{gather*} 

73 

74 :param renderer: Function to render content, by default escapes HTML 

75 

76 """ 

77 md.block.ruler.before( 

78 "blockquote", 

79 "amsmath", 

80 amsmath_block, 

81 {"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]}, 

82 ) 

83 

84 _renderer = (lambda content: escapeHtml(content)) if renderer is None else renderer 

85 

86 def render_amsmath_block( 

87 self: RendererProtocol, 

88 tokens: Sequence[Token], 

89 idx: int, 

90 options: OptionsDict, 

91 env: EnvType, 

92 ) -> str: 

93 content = _renderer(str(tokens[idx].content)) 

94 return f'<div class="math amsmath">\n{content}\n</div>\n' 

95 

96 md.add_render_rule("amsmath", render_amsmath_block) 

97 

98 

99def amsmath_block( 

100 state: StateBlock, startLine: int, endLine: int, silent: bool 

101) -> bool: 

102 # note the code principally follows the logic in markdown_it/rules_block/fence.py, 

103 # except that: 

104 # (a) it allows for closing tag on same line as opening tag 

105 # (b) it does not allow for opening tag without closing tag (i.e. no auto-closing) 

106 

107 if is_code_block(state, startLine): 

108 return False 

109 

110 # does the first line contain the beginning of an amsmath environment 

111 first_start = state.bMarks[startLine] + state.tShift[startLine] 

112 first_end = state.eMarks[startLine] 

113 first_text = state.src[first_start:first_end] 

114 

115 if not (match_open := re.match(RE_OPEN, first_text)): 

116 return False 

117 

118 # construct the closing tag 

119 environment = match_open.group(1) 

120 numbered = match_open.group(2) 

121 closing = rf"\end{{{match_open.group(1)}{match_open.group(2)}}}" 

122 

123 # start looking for the closing tag, including the current line 

124 nextLine = startLine - 1 

125 

126 while True: 

127 nextLine += 1 

128 if nextLine >= endLine: 

129 # reached the end of the block without finding the closing tag 

130 return False 

131 

132 next_start = state.bMarks[nextLine] + state.tShift[nextLine] 

133 next_end = state.eMarks[nextLine] 

134 if next_start < first_end and state.sCount[nextLine] < state.blkIndent: 

135 # non-empty line with negative indent should stop the list: 

136 # - \begin{align} 

137 # test 

138 return False 

139 

140 if state.src[next_start:next_end].rstrip().endswith(closing): 

141 # found the closing tag 

142 break 

143 

144 state.line = nextLine + 1 

145 

146 if not silent: 

147 token = state.push("amsmath", "math", 0) 

148 token.block = True 

149 token.content = state.getLines( 

150 startLine, state.line, state.sCount[startLine], False 

151 ) 

152 token.meta = {"environment": environment, "numbered": numbered} 

153 token.map = [startLine, nextLine] 

154 

155 return True