1"""Simple typographic replacements 
    2 
    3* ``(c)``, ``(C)`` → © 
    4* ``(tm)``, ``(TM)`` → ™ 
    5* ``(r)``, ``(R)`` → ® 
    6* ``+-`` → ± 
    7* ``...`` → … 
    8* ``?....`` → ?.. 
    9* ``!....`` → !.. 
    10* ``????????`` → ??? 
    11* ``!!!!!`` → !!! 
    12* ``,,,`` → , 
    13* ``--`` → &ndash 
    14* ``---`` → &mdash 
    15""" 
    16 
    17from __future__ import annotations 
    18 
    19import logging 
    20import re 
    21 
    22from ..token import Token 
    23from .state_core import StateCore 
    24 
    25LOGGER = logging.getLogger(__name__) 
    26 
    27# TODO: 
    28# - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ 
    29# - multiplication 2 x 4 -> 2 × 4 
    30 
    31RARE_RE = re.compile(r"\+-|\.\.|\?\?\?\?|!!!!|,,|--") 
    32 
    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)\)" 
    36 
    37SCOPED_ABBR_RE = re.compile(r"\((c|tm|r)\)", flags=re.IGNORECASE) 
    38 
    39PLUS_MINUS_RE = re.compile(r"\+-") 
    40 
    41ELLIPSIS_RE = re.compile(r"\.{2,}") 
    42 
    43ELLIPSIS_QUESTION_EXCLAMATION_RE = re.compile(r"([?!])…") 
    44 
    45QUESTION_EXCLAMATION_RE = re.compile(r"([?!]){4,}") 
    46 
    47COMMA_RE = re.compile(r",{2,}") 
    48 
    49EM_DASH_RE = re.compile(r"(^|[^-])---(?=[^-]|$)", flags=re.MULTILINE) 
    50 
    51EN_DASH_RE = re.compile(r"(^|\s)--(?=\s|$)", flags=re.MULTILINE) 
    52 
    53EN_DASH_INDENT_RE = re.compile(r"(^|[^-\s])--(?=[^-\s]|$)", flags=re.MULTILINE) 
    54 
    55 
    56SCOPED_ABBR = {"c": "©", "r": "®", "tm": "™"} 
    57 
    58 
    59def replaceFn(match: re.Match[str]) -> str: 
    60    return SCOPED_ABBR[match.group(1).lower()] 
    61 
    62 
    63def replace_scoped(inlineTokens: list[Token]) -> None: 
    64    inside_autolink = 0 
    65 
    66    for token in inlineTokens: 
    67        if token.type == "text" and not inside_autolink: 
    68            token.content = SCOPED_ABBR_RE.sub(replaceFn, token.content) 
    69 
    70        if token.type == "link_open" and token.info == "auto": 
    71            inside_autolink -= 1 
    72 
    73        if token.type == "link_close" and token.info == "auto": 
    74            inside_autolink += 1 
    75 
    76 
    77def replace_rare(inlineTokens: list[Token]) -> None: 
    78    inside_autolink = 0 
    79 
    80    for token in inlineTokens: 
    81        if ( 
    82            token.type == "text" 
    83            and (not inside_autolink) 
    84            and RARE_RE.search(token.content) 
    85        ): 
    86            # +- -> ± 
    87            token.content = PLUS_MINUS_RE.sub("±", token.content) 
    88 
    89            # .., ..., ....... -> … 
    90            token.content = ELLIPSIS_RE.sub("…", token.content) 
    91 
    92            # but ?..... & !..... -> ?.. & !.. 
    93            token.content = ELLIPSIS_QUESTION_EXCLAMATION_RE.sub("\\1..", token.content) 
    94            token.content = QUESTION_EXCLAMATION_RE.sub("\\1\\1\\1", token.content) 
    95 
    96            # ,,  ,,,  ,,,, -> , 
    97            token.content = COMMA_RE.sub(",", token.content) 
    98 
    99            # em-dash 
    100            token.content = EM_DASH_RE.sub("\\1\u2014", token.content) 
    101 
    102            # en-dash 
    103            token.content = EN_DASH_RE.sub("\\1\u2013", token.content) 
    104            token.content = EN_DASH_INDENT_RE.sub("\\1\u2013", token.content) 
    105 
    106        if token.type == "link_open" and token.info == "auto": 
    107            inside_autolink -= 1 
    108 
    109        if token.type == "link_close" and token.info == "auto": 
    110            inside_autolink += 1 
    111 
    112 
    113def replace(state: StateCore) -> None: 
    114    if not state.md.options.typographer: 
    115        return 
    116 
    117    for token in state.tokens: 
    118        if token.type != "inline": 
    119            continue 
    120        if token.children is None: 
    121            continue 
    122 
    123        if SCOPED_ABBR_RE.search(token.content): 
    124            replace_scoped(token.children) 
    125 
    126        if RARE_RE.search(token.content): 
    127            replace_rare(token.children)