Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mistune/plugins/table.py: 93%

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

104 statements  

1import re 

2from typing import ( 

3 TYPE_CHECKING, 

4 Any, 

5 Dict, 

6 List, 

7 Match, 

8 Optional, 

9 Tuple, 

10 Union, 

11) 

12 

13from ..helpers import PREVENT_BACKSLASH 

14 

15if TYPE_CHECKING: 

16 from ..block_parser import BlockParser 

17 from ..core import BaseRenderer, BlockState 

18 from ..markdown import Markdown 

19 

20# https://michelf.ca/projects/php-markdown/extra/#table 

21 

22__all__ = ["table", "table_in_quote", "table_in_list"] 

23 

24 

25TABLE_PATTERN = ( 

26 r"^ {0,3}\|(?P<table_head>.+)\|[ \t]*\n" 

27 r" {0,3}\|(?P<table_align> *[-:]+[-| :]*)\|[ \t]*\n" 

28 r"(?P<table_body>(?: {0,3}\|.*\|[ \t]*(?:\n|$))*)\n*" 

29) 

30NP_TABLE_PATTERN = ( 

31 r"^ {0,3}(?P<nptable_head>\S.*\|.*)\n" 

32 r" {0,3}(?P<nptable_align>[-:]+ *\|[-| :]*)\n" 

33 r"(?P<nptable_body>(?:.*\|.*(?:\n|$))*)\n*" 

34) 

35 

36TABLE_CELL = re.compile(r"^ {0,3}\|(.+)\|[ \t]*$") 

37CELL_SPLIT = re.compile(r" *" + PREVENT_BACKSLASH + r"\| *") 

38ALIGN_CENTER = re.compile(r"^ *:-+: *$") 

39ALIGN_LEFT = re.compile(r"^ *:-+ *$") 

40ALIGN_RIGHT = re.compile(r"^ *-+: *$") 

41 

42 

43def parse_table(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]: 

44 pos = m.end() 

45 header = m.group("table_head") 

46 align = m.group("table_align") 

47 thead, aligns = _process_thead(header, align) 

48 if not thead: 

49 return None 

50 assert aligns is not None 

51 

52 rows = [] 

53 body = m.group("table_body") 

54 for text in body.splitlines(): 

55 m2 = TABLE_CELL.match(text) 

56 if not m2: # pragma: no cover 

57 return None 

58 row = _process_row(m2.group(1), aligns) 

59 if not row: 

60 return None 

61 rows.append(row) 

62 

63 children = [thead, {"type": "table_body", "children": rows}] 

64 state.append_token({"type": "table", "children": children}) 

65 return pos 

66 

67 

68def parse_nptable(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]: 

69 header = m.group("nptable_head") 

70 align = m.group("nptable_align") 

71 thead, aligns = _process_thead(header, align) 

72 if not thead: 

73 return None 

74 assert aligns is not None 

75 

76 rows = [] 

77 body = m.group("nptable_body") 

78 for text in body.splitlines(): 

79 row = _process_row(text, aligns) 

80 if not row: 

81 return None 

82 rows.append(row) 

83 

84 children = [thead, {"type": "table_body", "children": rows}] 

85 state.append_token({"type": "table", "children": children}) 

86 return m.end() 

87 

88 

89def _process_thead(header: str, align: str) -> Union[Tuple[None, None], Tuple[Dict[str, Any], List[str]]]: 

90 headers = CELL_SPLIT.split(header) 

91 aligns = CELL_SPLIT.split(align) 

92 if len(headers) != len(aligns): 

93 return None, None 

94 

95 for i, v in enumerate(aligns): 

96 if ALIGN_CENTER.match(v): 

97 aligns[i] = "center" 

98 elif ALIGN_LEFT.match(v): 

99 aligns[i] = "left" 

100 elif ALIGN_RIGHT.match(v): 

101 aligns[i] = "right" 

102 else: 

103 aligns[i] = None 

104 

105 children = [ 

106 {"type": "table_cell", "text": text.strip(), "attrs": {"align": aligns[i], "head": True}} 

107 for i, text in enumerate(headers) 

108 ] 

109 thead = {"type": "table_head", "children": children} 

110 return thead, aligns 

111 

112 

113def _process_row(text: str, aligns: List[str]) -> Optional[Dict[str, Any]]: 

114 cells = CELL_SPLIT.split(text) 

115 if len(cells) != len(aligns): 

116 return None 

117 

118 children = [ 

119 {"type": "table_cell", "text": text.strip(), "attrs": {"align": aligns[i], "head": False}} 

120 for i, text in enumerate(cells) 

121 ] 

122 return {"type": "table_row", "children": children} 

123 

124 

125def render_table(renderer: "BaseRenderer", text: str) -> str: 

126 return "<table>\n" + text + "</table>\n" 

127 

128 

129def render_table_head(renderer: "BaseRenderer", text: str) -> str: 

130 return "<thead>\n<tr>\n" + text + "</tr>\n</thead>\n" 

131 

132 

133def render_table_body(renderer: "BaseRenderer", text: str) -> str: 

134 return "<tbody>\n" + text + "</tbody>\n" 

135 

136 

137def render_table_row(renderer: "BaseRenderer", text: str) -> str: 

138 return "<tr>\n" + text + "</tr>\n" 

139 

140 

141def render_table_cell(renderer: "BaseRenderer", text: str, align: Optional[str] = None, head: bool = False) -> str: 

142 if head: 

143 tag = "th" 

144 else: 

145 tag = "td" 

146 

147 html = " <" + tag 

148 if align: 

149 html += ' style="text-align:' + align + '"' 

150 

151 return html + ">" + text + "</" + tag + ">\n" 

152 

153 

154def table(md: "Markdown") -> None: 

155 """A mistune plugin to support table, spec defined at 

156 https://michelf.ca/projects/php-markdown/extra/#table 

157 

158 Here is an example: 

159 

160 .. code-block:: text 

161 

162 First Header | Second Header 

163 ------------- | ------------- 

164 Content Cell | Content Cell 

165 Content Cell | Content Cell 

166 

167 :param md: Markdown instance 

168 """ 

169 md.block.register("table", TABLE_PATTERN, parse_table, before="paragraph") 

170 md.block.register("nptable", NP_TABLE_PATTERN, parse_nptable, before="paragraph") 

171 

172 if md.renderer and md.renderer.NAME == "html": 

173 md.renderer.register("table", render_table) 

174 md.renderer.register("table_head", render_table_head) 

175 md.renderer.register("table_body", render_table_body) 

176 md.renderer.register("table_row", render_table_row) 

177 md.renderer.register("table_cell", render_table_cell) 

178 

179 

180def table_in_quote(md: "Markdown") -> None: 

181 """Enable table plugin in block quotes.""" 

182 md.block.insert_rule(md.block.block_quote_rules, "table", before="paragraph") 

183 md.block.insert_rule(md.block.block_quote_rules, "nptable", before="paragraph") 

184 

185 

186def table_in_list(md: "Markdown") -> None: 

187 """Enable table plugin in list.""" 

188 md.block.insert_rule(md.block.list_rules, "table", before="paragraph") 

189 md.block.insert_rule(md.block.list_rules, "nptable", before="paragraph")