Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markdown_it/rules_block/table.py: 99%

158 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:07 +0000

1# GFM table, https://github.github.com/gfm/#tables-extension- 

2import re 

3 

4from ..common.utils import charCodeAt, isSpace 

5from .state_block import StateBlock 

6 

7headerLineRe = re.compile(r"^:?-+:?$") 

8enclosingPipesRe = re.compile(r"^\||\|$") 

9 

10 

11def getLine(state: StateBlock, line: int): 

12 pos = state.bMarks[line] + state.tShift[line] 

13 maximum = state.eMarks[line] 

14 

15 # return state.src.substr(pos, max - pos) 

16 return state.src[pos:maximum] 

17 

18 

19def escapedSplit(string): 

20 result = [] 

21 pos = 0 

22 max = len(string) 

23 isEscaped = False 

24 lastPos = 0 

25 current = "" 

26 ch = charCodeAt(string, pos) 

27 

28 while pos < max: 

29 if ch == 0x7C: # /* | */ 

30 if not isEscaped: 

31 # pipe separating cells, '|' 

32 result.append(current + string[lastPos:pos]) 

33 current = "" 

34 lastPos = pos + 1 

35 else: 

36 # escaped pipe, '\|' 

37 current += string[lastPos : pos - 1] 

38 lastPos = pos 

39 

40 isEscaped = ch == 0x5C # /* \ */ 

41 pos += 1 

42 

43 ch = charCodeAt(string, pos) 

44 

45 result.append(current + string[lastPos:]) 

46 

47 return result 

48 

49 

50def table(state: StateBlock, startLine: int, endLine: int, silent: bool): 

51 tbodyLines = None 

52 

53 # should have at least two lines 

54 if startLine + 2 > endLine: 

55 return False 

56 

57 nextLine = startLine + 1 

58 

59 if state.sCount[nextLine] < state.blkIndent: 

60 return False 

61 

62 # if it's indented more than 3 spaces, it should be a code block 

63 if state.sCount[nextLine] - state.blkIndent >= 4: 

64 return False 

65 

66 # first character of the second line should be '|', '-', ':', 

67 # and no other characters are allowed but spaces; 

68 # basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp 

69 

70 pos = state.bMarks[nextLine] + state.tShift[nextLine] 

71 if pos >= state.eMarks[nextLine]: 

72 return False 

73 first_ch = state.srcCharCode[pos] 

74 pos += 1 

75 if first_ch not in {0x7C, 0x2D, 0x3A}: # not in {"|", "-", ":"} 

76 return False 

77 

78 if pos >= state.eMarks[nextLine]: 

79 return False 

80 second_ch = state.srcCharCode[pos] 

81 pos += 1 

82 # not in {"|", "-", ":"} and not space 

83 if second_ch not in {0x7C, 0x2D, 0x3A} and not isSpace(second_ch): 

84 return False 

85 

86 # if first character is '-', then second character must not be a space 

87 # (due to parsing ambiguity with list) 

88 if first_ch == 0x2D and isSpace(second_ch): 

89 return False 

90 

91 while pos < state.eMarks[nextLine]: 

92 ch = state.srcCharCode[pos] 

93 

94 # /* | */ /* - */ /* : */ 

95 if ch not in {0x7C, 0x2D, 0x3A} and not isSpace(ch): 

96 return False 

97 

98 pos += 1 

99 

100 lineText = getLine(state, startLine + 1) 

101 

102 columns = lineText.split("|") 

103 aligns = [] 

104 for i in range(len(columns)): 

105 t = columns[i].strip() 

106 if not t: 

107 # allow empty columns before and after table, but not in between columns; 

108 # e.g. allow ` |---| `, disallow ` ---||--- ` 

109 if i == 0 or i == len(columns) - 1: 

110 continue 

111 else: 

112 return False 

113 

114 if not headerLineRe.search(t): 

115 return False 

116 if charCodeAt(t, len(t) - 1) == 0x3A: # /* : */ 

117 # /* : */ 

118 aligns.append("center" if charCodeAt(t, 0) == 0x3A else "right") 

119 elif charCodeAt(t, 0) == 0x3A: # /* : */ 

120 aligns.append("left") 

121 else: 

122 aligns.append("") 

123 

124 lineText = getLine(state, startLine).strip() 

125 if "|" not in lineText: 

126 return False 

127 if state.sCount[startLine] - state.blkIndent >= 4: 

128 return False 

129 columns = escapedSplit(lineText) 

130 if columns and columns[0] == "": 

131 columns.pop(0) 

132 if columns and columns[-1] == "": 

133 columns.pop() 

134 

135 # header row will define an amount of columns in the entire table, 

136 # and align row should be exactly the same (the rest of the rows can differ) 

137 columnCount = len(columns) 

138 if columnCount == 0 or columnCount != len(aligns): 

139 return False 

140 

141 if silent: 

142 return True 

143 

144 oldParentType = state.parentType 

145 state.parentType = "table" 

146 

147 # use 'blockquote' lists for termination because it's 

148 # the most similar to tables 

149 terminatorRules = state.md.block.ruler.getRules("blockquote") 

150 

151 token = state.push("table_open", "table", 1) 

152 token.map = tableLines = [startLine, 0] 

153 

154 token = state.push("thead_open", "thead", 1) 

155 token.map = [startLine, startLine + 1] 

156 

157 token = state.push("tr_open", "tr", 1) 

158 token.map = [startLine, startLine + 1] 

159 

160 for i in range(len(columns)): 

161 token = state.push("th_open", "th", 1) 

162 if aligns[i]: 

163 token.attrs = {"style": "text-align:" + aligns[i]} 

164 

165 token = state.push("inline", "", 0) 

166 # note in markdown-it this map was removed in v12.0.0 however, we keep it, 

167 # since it is helpful to propagate to children tokens 

168 token.map = [startLine, startLine + 1] 

169 token.content = columns[i].strip() 

170 token.children = [] 

171 

172 token = state.push("th_close", "th", -1) 

173 

174 token = state.push("tr_close", "tr", -1) 

175 token = state.push("thead_close", "thead", -1) 

176 

177 nextLine = startLine + 2 

178 while nextLine < endLine: 

179 if state.sCount[nextLine] < state.blkIndent: 

180 break 

181 

182 terminate = False 

183 for i in range(len(terminatorRules)): 

184 if terminatorRules[i](state, nextLine, endLine, True): 

185 terminate = True 

186 break 

187 

188 if terminate: 

189 break 

190 lineText = getLine(state, nextLine).strip() 

191 if not lineText: 

192 break 

193 if state.sCount[nextLine] - state.blkIndent >= 4: 

194 break 

195 columns = escapedSplit(lineText) 

196 if columns and columns[0] == "": 

197 columns.pop(0) 

198 if columns and columns[-1] == "": 

199 columns.pop() 

200 

201 if nextLine == startLine + 2: 

202 token = state.push("tbody_open", "tbody", 1) 

203 token.map = tbodyLines = [startLine + 2, 0] 

204 

205 token = state.push("tr_open", "tr", 1) 

206 token.map = [nextLine, nextLine + 1] 

207 

208 for i in range(columnCount): 

209 token = state.push("td_open", "td", 1) 

210 if aligns[i]: 

211 token.attrs = {"style": "text-align:" + aligns[i]} 

212 

213 token = state.push("inline", "", 0) 

214 # note in markdown-it this map was removed in v12.0.0 however, we keep it, 

215 # since it is helpful to propagate to children tokens 

216 token.map = [nextLine, nextLine + 1] 

217 try: 

218 token.content = columns[i].strip() if columns[i] else "" 

219 except IndexError: 

220 token.content = "" 

221 token.children = [] 

222 

223 token = state.push("td_close", "td", -1) 

224 

225 token = state.push("tr_close", "tr", -1) 

226 

227 nextLine += 1 

228 

229 if tbodyLines: 

230 token = state.push("tbody_close", "tbody", -1) 

231 tbodyLines[1] = nextLine 

232 

233 token = state.push("table_close", "table", -1) 

234 

235 tableLines[1] = nextLine 

236 state.parentType = oldParentType 

237 state.line = nextLine 

238 return True