Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/markdown_it/rules_core/replacements.py: 39%

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

57 statements  

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)