Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/markdown/extensions/abbr.py: 88%

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

106 statements  

1# Abbreviation Extension for Python-Markdown 

2# ========================================== 

3 

4# This extension adds abbreviation handling to Python-Markdown. 

5 

6# See https://Python-Markdown.github.io/extensions/abbreviations 

7# for documentation. 

8 

9# Original code Copyright 2007-2008 [Waylan Limberg](https://github.com/waylan) 

10# and [Seemant Kulleen](http://www.kulleen.org/) 

11 

12# All changes Copyright 2008-2014 The Python Markdown Project 

13 

14# License: [BSD](https://opensource.org/licenses/bsd-license.php) 

15 

16""" 

17This extension adds abbreviation handling to Python-Markdown. 

18 

19See the [documentation](https://Python-Markdown.github.io/extensions/abbreviations) 

20for details. 

21""" 

22 

23from __future__ import annotations 

24 

25from . import Extension 

26from ..blockprocessors import BlockProcessor 

27from ..inlinepatterns import InlineProcessor 

28from ..treeprocessors import Treeprocessor 

29from ..util import AtomicString, deprecated 

30from typing import TYPE_CHECKING 

31import re 

32import xml.etree.ElementTree as etree 

33 

34if TYPE_CHECKING: # pragma: no cover 

35 from .. import Markdown 

36 from ..blockparser import BlockParser 

37 

38 

39class AbbrExtension(Extension): 

40 """ Abbreviation Extension for Python-Markdown. """ 

41 

42 def __init__(self, **kwargs): 

43 """ Initiate Extension and set up configs. """ 

44 self.config = { 

45 'glossary': [ 

46 {}, 

47 'A dictionary where the `key` is the abbreviation and the `value` is the definition.' 

48 "Default: `{}`" 

49 ], 

50 } 

51 """ Default configuration options. """ 

52 super().__init__(**kwargs) 

53 self.abbrs = {} 

54 self.glossary = {} 

55 

56 def reset(self): 

57 """ Clear all previously defined abbreviations. """ 

58 self.abbrs.clear() 

59 if (self.glossary): 

60 self.abbrs.update(self.glossary) 

61 

62 def reset_glossary(self): 

63 """ Clear all abbreviations from the glossary. """ 

64 self.glossary.clear() 

65 

66 def load_glossary(self, dictionary: dict[str, str]): 

67 """Adds `dictionary` to our glossary. Any abbreviations that already exist will be overwritten.""" 

68 if dictionary: 

69 self.glossary = {**dictionary, **self.glossary} 

70 

71 def extendMarkdown(self, md): 

72 """ 

73 Register the processors. 

74 

75 | Class Instance | Registry | Name | Priority | 

76 | ------------------------------------------------------------------- | ---------------------------------------------------------------- | ------ | :------: | 

77 | [`AbbrTreeprocessor`][markdown.extensions.abbr.AbbrTreeprocessor] | [`treeprocessors`][markdown.treeprocessors.build_treeprocessors] | `abbr` | `7` | 

78 | [`AbbrBlockprocessor`][markdown.extensions.abbr.AbbrBlockprocessor] | [`blockprocessors`][markdown.blockprocessors.build_block_parser] | `abbr` | `16` | 

79 

80 """ 

81 # flake8: noqa: E501 75-78 

82 if (self.config['glossary'][0]): 

83 self.load_glossary(self.config['glossary'][0]) 

84 self.abbrs.update(self.glossary) 

85 md.registerExtension(self) 

86 md.treeprocessors.register(AbbrTreeprocessor(md, self.abbrs), 'abbr', 7) 

87 md.parser.blockprocessors.register(AbbrBlockprocessor(md.parser, self.abbrs), 'abbr', 16) 

88 

89 

90class AbbrTreeprocessor(Treeprocessor): 

91 """ Replace abbreviation text with `<abbr>` elements. """ 

92 

93 def __init__(self, md: Markdown | None = None, abbrs: dict | None = None): 

94 self.abbrs: dict = abbrs if abbrs is not None else {} 

95 self.RE: re.RegexObject | None = None 

96 super().__init__(md) 

97 

98 def create_element(self, title: str, text: str, tail: str) -> etree.Element: 

99 ''' Create an `abbr` element. ''' 

100 abbr = etree.Element('abbr', {'title': title}) 

101 abbr.text = AtomicString(text) 

102 abbr.tail = tail 

103 return abbr 

104 

105 def iter_element(self, el: etree.Element, parent: etree.Element | None = None) -> None: 

106 ''' Recursively iterate over elements, run regex on text and wrap matches in `abbr` tags. ''' 

107 for child in reversed(el): 

108 self.iter_element(child, el) 

109 if text := el.text: 

110 if not isinstance(text, AtomicString): 

111 for m in reversed(list(self.RE.finditer(text))): 

112 if self.abbrs[m.group(0)]: 

113 abbr = self.create_element(self.abbrs[m.group(0)], m.group(0), text[m.end():]) 

114 el.insert(0, abbr) 

115 text = text[:m.start()] 

116 el.text = text 

117 if parent is not None and el.tail: 

118 tail = el.tail 

119 index = list(parent).index(el) + 1 

120 if not isinstance(tail, AtomicString): 

121 for m in reversed(list(self.RE.finditer(tail))): 

122 abbr = self.create_element(self.abbrs[m.group(0)], m.group(0), tail[m.end():]) 

123 parent.insert(index, abbr) 

124 tail = tail[:m.start()] 

125 el.tail = tail 

126 

127 def run(self, root: etree.Element) -> etree.Element | None: 

128 ''' Step through tree to find known abbreviations. ''' 

129 if not self.abbrs: 

130 # No abbreviations defined. Skip running processor. 

131 return 

132 # Build and compile regex 

133 abbr_list = list(self.abbrs.keys()) 

134 abbr_list.sort(key=len, reverse=True) 

135 self.RE = re.compile(f"\\b(?:{ '|'.join(re.escape(key) for key in abbr_list) })\\b") 

136 # Step through tree and modify on matches 

137 self.iter_element(root) 

138 

139 

140class AbbrBlockprocessor(BlockProcessor): 

141 """ Parse text for abbreviation references. """ 

142 

143 RE = re.compile(r'^[*]\[(?P<abbr>[^\\]*?)\][ ]?:[ ]*\n?[ ]*(?P<title>.*)$', re.MULTILINE) 

144 

145 def __init__(self, parser: BlockParser, abbrs: dict): 

146 self.abbrs: dict = abbrs 

147 super().__init__(parser) 

148 

149 def test(self, parent: etree.Element, block: str) -> bool: 

150 return True 

151 

152 def run(self, parent: etree.Element, blocks: list[str]) -> bool: 

153 """ 

154 Find and remove all abbreviation references from the text. 

155 Each reference is added to the abbreviation collection. 

156 

157 """ 

158 block = blocks.pop(0) 

159 m = self.RE.search(block) 

160 if m: 

161 abbr = m.group('abbr').strip() 

162 title = m.group('title').strip() 

163 if title and abbr: 

164 if title == "''" or title == '""': 

165 self.abbrs.pop(abbr) 

166 else: 

167 self.abbrs[abbr] = title 

168 if block[m.end():].strip(): 

169 # Add any content after match back to blocks as separate block 

170 blocks.insert(0, block[m.end():].lstrip('\n')) 

171 if block[:m.start()].strip(): 

172 # Add any content before match back to blocks as separate block 

173 blocks.insert(0, block[:m.start()].rstrip('\n')) 

174 return True 

175 # No match. Restore block. 

176 blocks.insert(0, block) 

177 return False 

178 

179 

180AbbrPreprocessor = deprecated("This class has been renamed to `AbbrBlockprocessor`.")(AbbrBlockprocessor) 

181 

182 

183@deprecated("This class will be removed in the future; use `AbbrTreeprocessor` instead.") 

184class AbbrInlineProcessor(InlineProcessor): 

185 """ Abbreviation inline pattern. """ 

186 

187 def __init__(self, pattern: str, title: str): 

188 super().__init__(pattern) 

189 self.title = title 

190 

191 def handleMatch(self, m: re.Match[str], data: str) -> tuple[etree.Element, int, int]: 

192 abbr = etree.Element('abbr') 

193 abbr.text = AtomicString(m.group('abbr')) 

194 abbr.set('title', self.title) 

195 return abbr, m.start(0), m.end(0) 

196 

197 

198def makeExtension(**kwargs): # pragma: no cover 

199 return AbbrExtension(**kwargs)