Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mistune/helpers.py: 86%

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

160 statements  

1import re 

2import string 

3from typing import Any, Dict, Tuple, Union 

4 

5from .util import escape_url 

6 

7PREVENT_BACKSLASH = r"(?<!\\)(?:\\\\)*" 

8PUNCTUATION = r"[" + re.escape(string.punctuation) + r"]" 

9 

10LINK_LABEL = r"(?:[^\\\[\]]|\\.){0,500}" 

11 

12ASCII_WHITESPACE = " \t\n\r\f" 

13 

14HTML_TAGNAME = r"[A-Za-z][A-Za-z0-9-]*" 

15HTML_ATTRIBUTES = ( 

16 r"(?:\s+[A-Za-z_:][A-Za-z0-9_.:-]*" 

17 r'(?:\s*=\s*(?:[^ !"\'=<>`]+|\'[^\']*?\'|"[^\"]*?"))?)*' 

18) 

19 

20BLOCK_TAGS = ( 

21 "address", 

22 "article", 

23 "aside", 

24 "base", 

25 "basefont", 

26 "blockquote", 

27 "body", 

28 "caption", 

29 "center", 

30 "col", 

31 "colgroup", 

32 "dd", 

33 "details", 

34 "dialog", 

35 "dir", 

36 "div", 

37 "dl", 

38 "dt", 

39 "fieldset", 

40 "figcaption", 

41 "figure", 

42 "footer", 

43 "form", 

44 "frame", 

45 "frameset", 

46 "h1", 

47 "h2", 

48 "h3", 

49 "h4", 

50 "h5", 

51 "h6", 

52 "head", 

53 "header", 

54 "hr", 

55 "html", 

56 "iframe", 

57 "legend", 

58 "li", 

59 "link", 

60 "main", 

61 "menu", 

62 "menuitem", 

63 "meta", 

64 "nav", 

65 "noframes", 

66 "ol", 

67 "optgroup", 

68 "option", 

69 "p", 

70 "param", 

71 "section", 

72 "source", 

73 "summary", 

74 "table", 

75 "tbody", 

76 "td", 

77 "tfoot", 

78 "th", 

79 "thead", 

80 "title", 

81 "tr", 

82 "track", 

83 "ul", 

84) 

85PRE_TAGS = ("pre", "script", "style", "textarea") 

86 

87_INLINE_LINK_LABEL_RE = re.compile(LINK_LABEL + r"\]") 

88_INLINE_SQUARE_BRACKET_RE = re.compile(PREVENT_BACKSLASH + r"[\[\]]") 

89_ESCAPE_CHAR_RE = re.compile(r"\\(" + PUNCTUATION + r")") 

90 

91 

92def unescape_char(text: str) -> str: 

93 return _ESCAPE_CHAR_RE.sub(r"\1", text) 

94 

95 

96def parse_link_text(src: str, pos: int) -> Union[Tuple[str, int], Tuple[None, int]]: 

97 level = 1 

98 found = False 

99 start_pos = pos 

100 

101 while pos < len(src): 

102 m = _INLINE_SQUARE_BRACKET_RE.search(src, pos) 

103 if not m: 

104 pos = len(src) # FIX: record we scanned to end 

105 break 

106 

107 pos = m.end() 

108 marker = m.group(0) 

109 if marker == "]": 

110 level -= 1 

111 if level == 0: 

112 found = True 

113 break 

114 else: 

115 level += 1 

116 

117 if found: 

118 text = src[start_pos : pos - 1] 

119 return text, pos 

120 return None, pos # FIX: return pos instead of None 

121 

122 

123def parse_link_label(src: str, start_pos: int) -> Union[Tuple[str, int], Tuple[None, None]]: 

124 m = _INLINE_LINK_LABEL_RE.match(src, start_pos) 

125 if m: 

126 label = m.group(0)[:-1] 

127 return label, m.end() 

128 return None, None 

129 

130 

131def parse_link_href(src: str, start_pos: int, block: bool = False) -> Union[Tuple[str, int], Tuple[None, None]]: 

132 href, href_pos, _end_pos = _parse_link_href(src, start_pos, block=block) 

133 if href is None: 

134 return None, None 

135 assert href_pos is not None 

136 return href, href_pos 

137 

138 

139def _parse_link_href( 

140 src: str, start_pos: int, block: bool = False 

141) -> Tuple[Union[str, None], Union[int, None], int]: 

142 pos = _skip_link_start_whitespace(src, start_pos) 

143 if pos >= len(src): 

144 return None, None, pos 

145 

146 if src[pos] == "<": 

147 href, href_pos = _parse_angle_link_href(src, pos) 

148 if href is None: 

149 return None, None, pos 

150 assert href_pos is not None 

151 return href, href_pos, href_pos 

152 if block and src[pos] in ASCII_WHITESPACE: 

153 return None, None, pos 

154 

155 start = pos 

156 level = 0 

157 while pos < len(src): 

158 c = src[pos] 

159 if c in ASCII_WHITESPACE: 

160 break 

161 if c == "\x00": 

162 return None, None, pos 

163 if c == "\\" and pos + 1 < len(src) and src[pos + 1] in string.punctuation: 

164 pos = min(pos + 2, len(src)) 

165 continue 

166 if not block: 

167 if c == "(": 

168 level += 1 

169 elif c == ")": 

170 if level == 0: 

171 break 

172 level -= 1 

173 pos += 1 

174 

175 if not block and level != 0: 

176 return None, None, pos 

177 return src[start:pos], pos, pos 

178 

179 

180def parse_link_title(src: str, start_pos: int, max_pos: int) -> Union[Tuple[str, int], Tuple[None, None]]: 

181 pos = start_pos 

182 if pos >= max_pos or src[pos] not in ASCII_WHITESPACE: 

183 return None, None 

184 

185 pos = _skip_ascii_whitespace(src, pos, max_pos) 

186 if pos >= max_pos: 

187 return None, None 

188 

189 opener = src[pos] 

190 closer = {"'": "'", '"': '"', "(": ")"}.get(opener) 

191 if closer is None: 

192 return None, None 

193 

194 pos += 1 

195 title = [] 

196 while pos < max_pos: 

197 c = src[pos] 

198 if c == "\x00": 

199 return None, None 

200 if c == "\\": 

201 if pos + 1 < max_pos: 

202 title.append(src[pos : pos + 2]) 

203 pos += 2 

204 continue 

205 return None, None 

206 if c == closer: 

207 return unescape_char("".join(title)), pos + 1 

208 title.append(src[pos]) 

209 pos += 1 

210 return None, None 

211 

212 

213def parse_link(src: str, pos: int) -> Union[Tuple[Dict[str, Any], int], Tuple[None, None]]: 

214 attrs, next_pos, _end_pos = parse_link_with_end(src, pos) 

215 if attrs is None: 

216 return None, None 

217 assert next_pos is not None 

218 return attrs, next_pos 

219 

220 

221def parse_link_with_end( 

222 src: str, pos: int 

223) -> Tuple[Union[Dict[str, Any], None], Union[int, None], int]: 

224 href, href_pos, scan_end = _parse_link_href(src, pos) 

225 if href is None: 

226 return None, None, scan_end 

227 assert href_pos is not None 

228 title, title_pos = parse_link_title(src, href_pos, len(src)) 

229 next_pos = title_pos or href_pos 

230 next_pos = _skip_ascii_whitespace(src, next_pos) 

231 if next_pos >= len(src) or src[next_pos] != ")": 

232 return None, None, next_pos 

233 

234 href = unescape_char(href) 

235 attrs = {"url": escape_url(href)} 

236 if title: 

237 attrs["title"] = title 

238 return attrs, next_pos + 1, next_pos + 1 

239 

240 

241def _skip_ascii_whitespace(src: str, pos: int, max_pos: Union[int, None] = None) -> int: 

242 if max_pos is None: 

243 max_pos = len(src) 

244 while pos < max_pos and src[pos] in ASCII_WHITESPACE: 

245 pos += 1 

246 return pos 

247 

248 

249def _skip_link_start_whitespace(src: str, pos: int) -> int: 

250 while pos < len(src) and src[pos] in " \t": 

251 pos += 1 

252 if pos < len(src) and src[pos] in "\n\r": 

253 if src[pos] == "\r" and pos + 1 < len(src) and src[pos + 1] == "\n": 

254 pos += 2 

255 else: 

256 pos += 1 

257 while pos < len(src) and src[pos] in " \t": 

258 pos += 1 

259 return pos 

260 

261 

262def _parse_angle_link_href(src: str, pos: int) -> Union[Tuple[str, int], Tuple[None, None]]: 

263 start = pos + 1 

264 pos = start 

265 while pos < len(src): 

266 c = src[pos] 

267 if c == ">": 

268 return src[start:pos], pos + 1 

269 if c in "<\\\n\r\x00": 

270 return None, None 

271 pos += 1 

272 return None, None