Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markdown_it/rules_core/replacements.py: 37%
57 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:07 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:07 +0000
1"""Simple typographic replacements
3* ``(c)``, ``(C)`` → ©
4* ``(tm)``, ``(TM)`` → ™
5* ``(r)``, ``(R)`` → ®
6* ``(p)``, ``(P)`` → §
7* ``+-`` → ±
8* ``...`` → …
9* ``?....`` → ?..
10* ``!....`` → !..
11* ``????????`` → ???
12* ``!!!!!`` → !!!
13* ``,,,`` → ,
14* ``--`` → &ndash
15* ``---`` → &mdash
16"""
17from __future__ import annotations
19import logging
20import re
22from ..token import Token
23from .state_core import StateCore
25LOGGER = logging.getLogger(__name__)
27# TODO:
28# - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾
29# - miltiplication 2 x 4 -> 2 × 4
31RARE_RE = re.compile(r"\+-|\.\.|\?\?\?\?|!!!!|,,|--")
33# Workaround for phantomjs - need regex without /g flag,
34# or root check will fail every second time
35# SCOPED_ABBR_TEST_RE = r"\((c|tm|r|p)\)"
37SCOPED_ABBR_RE = re.compile(r"\((c|tm|r|p)\)", flags=re.IGNORECASE)
39PLUS_MINUS_RE = re.compile(r"\+-")
41ELLIPSIS_RE = re.compile(r"\.{2,}")
43ELLIPSIS_QUESTION_EXCLAMATION_RE = re.compile(r"([?!])…")
45QUESTION_EXCLAMATION_RE = re.compile(r"([?!]){4,}")
47COMMA_RE = re.compile(r",{2,}")
49EM_DASH_RE = re.compile(r"(^|[^-])---(?=[^-]|$)", flags=re.MULTILINE)
51EN_DASH_RE = re.compile(r"(^|\s)--(?=\s|$)", flags=re.MULTILINE)
53EN_DASH_INDENT_RE = re.compile(r"(^|[^-\s])--(?=[^-\s]|$)", flags=re.MULTILINE)
56SCOPED_ABBR = {"c": "©", "r": "®", "p": "§", "tm": "™"}
59def replaceFn(match: re.Match[str]):
60 return SCOPED_ABBR[match.group(1).lower()]
63def replace_scoped(inlineTokens: list[Token]) -> None:
64 inside_autolink = 0
66 for token in inlineTokens:
67 if token.type == "text" and not inside_autolink:
68 token.content = SCOPED_ABBR_RE.sub(replaceFn, token.content)
70 if token.type == "link_open" and token.info == "auto":
71 inside_autolink -= 1
73 if token.type == "link_close" and token.info == "auto":
74 inside_autolink += 1
77def replace_rare(inlineTokens: list[Token]) -> None:
78 inside_autolink = 0
80 for token in inlineTokens:
81 if token.type == "text" and not inside_autolink:
82 if RARE_RE.search(token.content):
83 # +- -> ±
84 token.content = PLUS_MINUS_RE.sub("±", token.content)
86 # .., ..., ....... -> …
87 token.content = ELLIPSIS_RE.sub("…", token.content)
89 # but ?..... & !..... -> ?.. & !..
90 token.content = ELLIPSIS_QUESTION_EXCLAMATION_RE.sub(
91 "\\1..", token.content
92 )
93 token.content = QUESTION_EXCLAMATION_RE.sub("\\1\\1\\1", token.content)
95 # ,, ,,, ,,,, -> ,
96 token.content = COMMA_RE.sub(",", token.content)
98 # em-dash
99 token.content = EM_DASH_RE.sub("\\1\u2014", token.content)
101 # en-dash
102 token.content = EN_DASH_RE.sub("\\1\u2013", token.content)
103 token.content = EN_DASH_INDENT_RE.sub("\\1\u2013", token.content)
105 if token.type == "link_open" and token.info == "auto":
106 inside_autolink -= 1
108 if token.type == "link_close" and token.info == "auto":
109 inside_autolink += 1
112def replace(state: StateCore) -> None:
113 if not state.md.options.typographer:
114 return
116 for token in state.tokens:
117 if token.type != "inline":
118 continue
119 if token.children is None:
120 continue
122 if SCOPED_ABBR_RE.search(token.content):
123 replace_scoped(token.children)
125 if RARE_RE.search(token.content):
126 replace_rare(token.children)