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
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
1import re
2import string
3from typing import Any, Dict, Tuple, Union
5from .util import escape_url
7PREVENT_BACKSLASH = r"(?<!\\)(?:\\\\)*"
8PUNCTUATION = r"[" + re.escape(string.punctuation) + r"]"
10LINK_LABEL = r"(?:[^\\\[\]]|\\.){0,500}"
12ASCII_WHITESPACE = " \t\n\r\f"
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)
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")
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")")
92def unescape_char(text: str) -> str:
93 return _ESCAPE_CHAR_RE.sub(r"\1", text)
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
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
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
117 if found:
118 text = src[start_pos : pos - 1]
119 return text, pos
120 return None, pos # FIX: return pos instead of None
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
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
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
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
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
175 if not block and level != 0:
176 return None, None, pos
177 return src[start:pos], pos, pos
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
185 pos = _skip_ascii_whitespace(src, pos, max_pos)
186 if pos >= max_pos:
187 return None, None
189 opener = src[pos]
190 closer = {"'": "'", '"': '"', "(": ")"}.get(opener)
191 if closer is None:
192 return None, None
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
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
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
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
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
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
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