1# Process html entity - {, ¯, ", ...
2import re
3
4from ..common.entities import entities
5from ..common.utils import fromCodePoint, isValidEntityCode
6from .state_inline import StateInline
7
8# NB: no leading `^` -- these are applied via `.match(src, pos)`, which already
9# anchors at `pos`. Anchoring this way avoids building `src[pos:]` on every
10# `&`, which is an O(len) copy per call and makes long runs quadratic.
11DIGITAL_RE = re.compile(r"&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));", re.IGNORECASE)
12NAMED_RE = re.compile(r"&([a-z][a-z0-9]{1,31});", re.IGNORECASE)
13
14
15def entity(state: StateInline, silent: bool) -> bool:
16 pos = state.pos
17 maximum = state.posMax
18
19 if state.src[pos] != "&":
20 return False
21
22 if pos + 1 >= maximum:
23 return False
24
25 if state.src[pos + 1] == "#":
26 if match := DIGITAL_RE.match(state.src, pos):
27 if not silent:
28 match1 = match.group(1)
29 code = (
30 int(match1[1:], 16) if match1[0].lower() == "x" else int(match1, 10)
31 )
32
33 token = state.push("text_special", "", 0)
34 token.content = (
35 fromCodePoint(code)
36 if isValidEntityCode(code)
37 else fromCodePoint(0xFFFD)
38 )
39 token.markup = match.group(0)
40 token.info = "entity"
41
42 state.pos += len(match.group(0))
43 return True
44
45 else:
46 if (match := NAMED_RE.match(state.src, pos)) and match.group(1) in entities:
47 if not silent:
48 token = state.push("text_special", "", 0)
49 token.content = entities[match.group(1)]
50 token.markup = match.group(0)
51 token.info = "entity"
52
53 state.pos += len(match.group(0))
54 return True
55
56 return False