Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/markdown/blockprocessors.py: 99%

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

317 statements  

1# Python Markdown 

2 

3# A Python implementation of John Gruber's Markdown. 

4 

5# Documentation: https://python-markdown.github.io/ 

6# GitHub: https://github.com/Python-Markdown/markdown/ 

7# PyPI: https://pypi.org/project/Markdown/ 

8 

9# Started by Manfred Stienstra (http://www.dwerg.net/). 

10# Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). 

11# Currently maintained by Waylan Limberg (https://github.com/waylan), 

12# Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). 

13 

14# Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later) 

15# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) 

16# Copyright 2004 Manfred Stienstra (the original version) 

17 

18# License: BSD (see LICENSE.md for details). 

19 

20""" 

21A block processor parses blocks of text and adds new elements to the ElementTree. Blocks of text, 

22separated from other text by blank lines, may have a different syntax and produce a differently 

23structured tree than other Markdown. Block processors excel at handling code formatting, equation 

24layouts, tables, etc. 

25""" 

26 

27from __future__ import annotations 

28 

29import logging 

30import re 

31import xml.etree.ElementTree as etree 

32from typing import TYPE_CHECKING, Any 

33from . import util 

34from .blockparser import BlockParser 

35 

36if TYPE_CHECKING: # pragma: no cover 

37 from markdown import Markdown 

38 

39logger = logging.getLogger('MARKDOWN') 

40 

41 

42def build_block_parser(md: Markdown, **kwargs: Any) -> BlockParser: 

43 """ Build the default block parser used by Markdown. """ 

44 parser = BlockParser(md) 

45 parser.blockprocessors.register(EmptyBlockProcessor(parser), 'empty', 100) 

46 parser.blockprocessors.register(ListIndentProcessor(parser), 'indent', 90) 

47 parser.blockprocessors.register(CodeBlockProcessor(parser), 'code', 80) 

48 parser.blockprocessors.register(HashHeaderProcessor(parser), 'hashheader', 70) 

49 parser.blockprocessors.register(SetextHeaderProcessor(parser), 'setextheader', 60) 

50 parser.blockprocessors.register(HRProcessor(parser), 'hr', 50) 

51 parser.blockprocessors.register(OListProcessor(parser), 'olist', 40) 

52 parser.blockprocessors.register(UListProcessor(parser), 'ulist', 30) 

53 parser.blockprocessors.register(BlockQuoteProcessor(parser), 'quote', 20) 

54 parser.blockprocessors.register(ReferenceProcessor(parser), 'reference', 15) 

55 parser.blockprocessors.register(ParagraphProcessor(parser), 'paragraph', 10) 

56 return parser 

57 

58 

59class BlockProcessor: 

60 """ Base class for block processors. 

61 

62 Each subclass will provide the methods below to work with the source and 

63 tree. Each processor will need to define it's own `test` and `run` 

64 methods. The `test` method should return True or False, to indicate 

65 whether the current block should be processed by this processor. If the 

66 test passes, the parser will call the processors `run` method. 

67 

68 Attributes: 

69 BlockProcessor.parser (BlockParser): The `BlockParser` instance this is attached to. 

70 BlockProcessor.tab_length (int): The tab length set on the `Markdown` instance. 

71 

72 """ 

73 

74 def __init__(self, parser: BlockParser): 

75 self.parser = parser 

76 self.tab_length = parser.md.tab_length 

77 

78 def lastChild(self, parent: etree.Element) -> etree.Element | None: 

79 """ Return the last child of an `etree` element. """ 

80 if len(parent): 

81 return parent[-1] 

82 else: 

83 return None 

84 

85 def detab(self, text: str, length: int | None = None) -> tuple[str, str]: 

86 """ Remove a tab from the front of each line of the given text. """ 

87 if length is None: 

88 length = self.tab_length 

89 newtext = [] 

90 lines = text.split('\n') 

91 for line in lines: 

92 if line.startswith(' ' * length): 

93 newtext.append(line[length:]) 

94 elif not line.strip(): 

95 newtext.append('') 

96 else: 

97 break 

98 return '\n'.join(newtext), '\n'.join(lines[len(newtext):]) 

99 

100 def looseDetab(self, text: str, level: int = 1) -> str: 

101 """ Remove a tab from front of lines but allowing dedented lines. """ 

102 lines = text.split('\n') 

103 for i in range(len(lines)): 

104 if lines[i].startswith(' '*self.tab_length*level): 

105 lines[i] = lines[i][self.tab_length*level:] 

106 return '\n'.join(lines) 

107 

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

109 """ Test for block type. Must be overridden by subclasses. 

110 

111 As the parser loops through processors, it will call the `test` 

112 method on each to determine if the given block of text is of that 

113 type. This method must return a boolean `True` or `False`. The 

114 actual method of testing is left to the needs of that particular 

115 block type. It could be as simple as `block.startswith(some_string)` 

116 or a complex regular expression. As the block type may be different 

117 depending on the parent of the block (i.e. inside a list), the parent 

118 `etree` element is also provided and may be used as part of the test. 

119 

120 Keyword arguments: 

121 parent: An `etree` element which will be the parent of the block. 

122 block: A block of text from the source which has been split at blank lines. 

123 """ 

124 pass # pragma: no cover 

125 

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

127 """ Run processor. Must be overridden by subclasses. 

128 

129 When the parser determines the appropriate type of a block, the parser 

130 will call the corresponding processor's `run` method. This method 

131 should parse the individual lines of the block and append them to 

132 the `etree`. 

133 

134 Note that both the `parent` and `etree` keywords are pointers 

135 to instances of the objects which should be edited in place. Each 

136 processor must make changes to the existing objects as there is no 

137 mechanism to return new/different objects to replace them. 

138 

139 This means that this method should be adding `SubElements` or adding text 

140 to the parent, and should remove (`pop`) or add (`insert`) items to 

141 the list of blocks. 

142 

143 If `False` is returned, this will have the same effect as returning `False` 

144 from the `test` method. 

145 

146 Keyword arguments: 

147 parent: An `etree` element which is the parent of the current block. 

148 blocks: A list of all remaining blocks of the document. 

149 """ 

150 pass # pragma: no cover 

151 

152 

153class ListIndentProcessor(BlockProcessor): 

154 """ Process children of list items. 

155 

156 Example 

157 

158 * a list item 

159 process this part 

160 

161 or this part 

162 

163 """ 

164 

165 ITEM_TYPES = ['li'] 

166 """ List of tags used for list items. """ 

167 LIST_TYPES = ['ul', 'ol'] 

168 """ Types of lists this processor can operate on. """ 

169 

170 def __init__(self, *args): 

171 super().__init__(*args) 

172 self.INDENT_RE = re.compile(r'^(([ ]{%s})+)' % self.tab_length) 

173 

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

175 # A list calls `parseBlocks` once per level of nesting, so the 

176 # nesting has to stop short of the interpreter's limit. The markers which no 

177 # longer nest are kept as text, as they are for a blockquote. 

178 return block.startswith(' '*self.tab_length) and \ 

179 not self.parser.state.isstate('detabbed') and \ 

180 not util.nearing_recursion_limit() and \ 

181 (parent.tag in self.ITEM_TYPES or 

182 (len(parent) and parent[-1] is not None and 

183 (parent[-1].tag in self.LIST_TYPES))) 

184 

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

186 block = blocks.pop(0) 

187 level, sibling = self.get_level(parent, block) 

188 block = self.looseDetab(block, level) 

189 

190 self.parser.state.set('detabbed') 

191 if parent.tag in self.ITEM_TYPES: 

192 # It's possible that this parent has a `ul` or `ol` child list 

193 # with a member. If that is the case, then that should be the 

194 # parent. This is intended to catch the edge case of an indented 

195 # list whose first member was parsed previous to this point 

196 # see `OListProcessor` 

197 if len(parent) and parent[-1].tag in self.LIST_TYPES: 

198 self.parser.parseBlocks(parent[-1], [block]) 

199 else: 

200 # The parent is already a `li`. Just parse the child block. 

201 self.parser.parseBlocks(parent, [block]) 

202 elif sibling.tag in self.ITEM_TYPES: 

203 # The sibling is a `li`. Use it as parent. 

204 self.parser.parseBlocks(sibling, [block]) 

205 elif len(sibling) and sibling[-1].tag in self.ITEM_TYPES: 

206 # The parent is a list (`ol` or `ul`) which has children. 

207 # Assume the last child `li` is the parent of this block. 

208 if sibling[-1].text: 

209 # If the parent `li` has text, that text needs to be moved to a `p` 

210 # The `p` must be 'inserted' at beginning of list in the event 

211 # that other children already exist i.e.; a nested sub-list. 

212 p = etree.Element('p') 

213 p.text = sibling[-1].text 

214 sibling[-1].text = '' 

215 sibling[-1].insert(0, p) 

216 self.parser.parseChunk(sibling[-1], block) 

217 else: 

218 self.create_item(sibling, block) 

219 self.parser.state.reset() 

220 

221 def create_item(self, parent: etree.Element, block: str) -> None: 

222 """ Create a new `li` and parse the block with it as the parent. """ 

223 li = etree.SubElement(parent, 'li') 

224 self.parser.parseBlocks(li, [block]) 

225 

226 def get_level(self, parent: etree.Element, block: str) -> tuple[int, etree.Element]: 

227 """ Get level of indentation based on list level. """ 

228 # Get indent level 

229 m = self.INDENT_RE.match(block) 

230 if m: 

231 indent_level = len(m.group(1))/self.tab_length 

232 else: 

233 indent_level = 0 

234 if self.parser.state.isstate('list'): 

235 # We're in a tight-list - so we already are at correct parent. 

236 level = 1 

237 else: 

238 # We're in a loose-list - so we need to find parent. 

239 level = 0 

240 # Step through children of tree to find matching indent level. 

241 while indent_level > level: 

242 child = self.lastChild(parent) 

243 if (child is not None and 

244 (child.tag in self.LIST_TYPES or child.tag in self.ITEM_TYPES)): 

245 if child.tag in self.LIST_TYPES: 

246 level += 1 

247 parent = child 

248 else: 

249 # No more child levels. If we're short of `indent_level`, 

250 # we have a code block. So we stop here. 

251 break 

252 return level, parent 

253 

254 

255class CodeBlockProcessor(BlockProcessor): 

256 """ Process code blocks. """ 

257 

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

259 return block.startswith(' '*self.tab_length) 

260 

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

262 sibling = self.lastChild(parent) 

263 block = blocks.pop(0) 

264 theRest = '' 

265 if (sibling is not None and sibling.tag == "pre" and 

266 len(sibling) and sibling[0].tag == "code"): 

267 # The previous block was a code block. As blank lines do not start 

268 # new code blocks, append this block to the previous, adding back 

269 # line breaks removed from the split into a list. 

270 code = sibling[0] 

271 block, theRest = self.detab(block) 

272 code.text = util.AtomicString( 

273 '{}\n{}\n'.format(code.text, util.code_escape(block.rstrip())) 

274 ) 

275 else: 

276 # This is a new code block. Create the elements and insert text. 

277 pre = etree.SubElement(parent, 'pre') 

278 code = etree.SubElement(pre, 'code') 

279 block, theRest = self.detab(block) 

280 code.text = util.AtomicString('%s\n' % util.code_escape(block.rstrip())) 

281 if theRest: 

282 # This block contained unindented line(s) after the first indented 

283 # line. Insert these lines as the first block of the master blocks 

284 # list for future processing. 

285 blocks.insert(0, theRest) 

286 

287 

288class BlockQuoteProcessor(BlockProcessor): 

289 """ Process blockquotes. """ 

290 

291 RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)') 

292 

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

294 return bool(self.RE.search(block)) and not util.nearing_recursion_limit() 

295 

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

297 block = blocks.pop(0) 

298 m = self.RE.search(block) 

299 if m: 

300 before = block[:m.start()] # Lines before blockquote 

301 # Pass lines before blockquote in recursively for parsing first. 

302 self.parser.parseBlocks(parent, [before]) 

303 # Remove `> ` from beginning of each line. 

304 block = '\n'.join( 

305 [self.clean(line) for line in block[m.start():].split('\n')] 

306 ) 

307 sibling = self.lastChild(parent) 

308 if sibling is not None and sibling.tag == "blockquote": 

309 # Previous block was a blockquote so set that as this blocks parent 

310 quote = sibling 

311 else: 

312 # This is a new blockquote. Create a new parent element. 

313 quote = etree.SubElement(parent, 'blockquote') 

314 # Recursively parse block with blockquote as parent. 

315 # change parser state so blockquotes embedded in lists use `p` tags 

316 self.parser.state.set('blockquote') 

317 self.parser.parseChunk(quote, block) 

318 self.parser.state.reset() 

319 

320 def clean(self, line: str) -> str: 

321 """ Remove `>` from beginning of a line. """ 

322 m = self.RE.match(line) 

323 if line.strip() == ">": 

324 return "" 

325 elif m: 

326 return m.group(2) 

327 else: 

328 return line 

329 

330 

331class OListProcessor(BlockProcessor): 

332 """ Process ordered list blocks. """ 

333 

334 TAG: str = 'ol' 

335 """ The tag used for the the wrapping element. """ 

336 STARTSWITH: str = '1' 

337 """ 

338 The integer (as a string ) with which the list starts. For example, if a list is initialized as 

339 `3. Item`, then the `ol` tag will be assigned an HTML attribute of `starts="3"`. Default: `"1"`. 

340 """ 

341 LAZY_OL: bool = True 

342 """ Ignore `STARTSWITH` if `True`. """ 

343 SIBLING_TAGS: list[str] = ['ol', 'ul'] 

344 """ 

345 Markdown does not require the type of a new list item match the previous list item type. 

346 This is the list of types which can be mixed. 

347 """ 

348 

349 def __init__(self, parser: BlockParser): 

350 super().__init__(parser) 

351 # Detect an item (`1. item`). `group(1)` contains contents of item. 

352 self.RE = re.compile(r'^[ ]{0,%d}\d+\.[ ]+(.*)' % (self.tab_length - 1)) 

353 # Detect items on secondary lines. they can be of either list type. 

354 self.CHILD_RE = re.compile(r'^[ ]{0,%d}((\d+\.)|[*+-])[ ]+(.*)' % 

355 (self.tab_length - 1)) 

356 # Detect indented (nested) items of either type 

357 self.INDENT_RE = re.compile(r'^[ ]{%d,%d}((\d+\.)|[*+-])[ ]+.*' % 

358 (self.tab_length, self.tab_length * 2 - 1)) 

359 

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

361 # As in `ListIndentProcessor`, nesting stops short of the recursion limit. 

362 # `UListProcessor` inherits this method, so unordered lists are covered too. 

363 return bool(self.RE.match(block)) and not util.nearing_recursion_limit() 

364 

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

366 # Check for multiple items in one block. 

367 items = self.get_items(blocks.pop(0)) 

368 sibling = self.lastChild(parent) 

369 

370 if sibling is not None and sibling.tag in self.SIBLING_TAGS: 

371 # Previous block was a list item, so set that as parent 

372 lst = sibling 

373 # make sure previous item is in a `p` - if the item has text, 

374 # then it isn't in a `p` 

375 if lst[-1].text: 

376 # since it's possible there are other children for this 

377 # sibling, we can't just `SubElement` the `p`, we need to 

378 # insert it as the first item. 

379 p = etree.Element('p') 

380 p.text = lst[-1].text 

381 lst[-1].text = '' 

382 lst[-1].insert(0, p) 

383 # if the last item has a tail, then the tail needs to be put in a `p` 

384 # likely only when a header is not followed by a blank line 

385 lch = self.lastChild(lst[-1]) 

386 if lch is not None and lch.tail: 

387 p = etree.SubElement(lst[-1], 'p') 

388 p.text = lch.tail.lstrip() 

389 lch.tail = '' 

390 

391 # parse first block differently as it gets wrapped in a `p`. 

392 li = etree.SubElement(lst, 'li') 

393 self.parser.state.set('looselist') 

394 firstitem = items.pop(0) 

395 self.parser.parseBlocks(li, [firstitem]) 

396 self.parser.state.reset() 

397 elif parent.tag in ['ol', 'ul']: 

398 # this catches the edge case of a multi-item indented list whose 

399 # first item is in a blank parent-list item: 

400 # * * subitem1 

401 # * subitem2 

402 # see also `ListIndentProcessor` 

403 lst = parent 

404 else: 

405 # This is a new list so create parent with appropriate tag. 

406 lst = etree.SubElement(parent, self.TAG) 

407 # Check if a custom start integer is set 

408 if not self.LAZY_OL and self.STARTSWITH != '1': 

409 lst.attrib['start'] = self.STARTSWITH 

410 

411 self.parser.state.set('list') 

412 # Loop through items in block, recursively parsing each with the 

413 # appropriate parent. 

414 for item in items: 

415 if item.startswith(' '*self.tab_length): 

416 # Item is indented. Parse with last item as parent 

417 self.parser.parseBlocks(lst[-1], [item]) 

418 else: 

419 # New item. Create `li` and parse with it as parent 

420 li = etree.SubElement(lst, 'li') 

421 self.parser.parseBlocks(li, [item]) 

422 self.parser.state.reset() 

423 

424 def get_items(self, block: str) -> list[str]: 

425 """ Break a block into list items. """ 

426 # The lines of an item are collected in a list and joined once at the end. 

427 # Appending per line, as in `item = '{}\n{}'.format(item, line)`, copies the 

428 # whole item every time, which is quadratic in the number of lines of an item. 

429 items: list[list[str]] = [] 

430 for line in block.split('\n'): 

431 m = self.CHILD_RE.match(line) 

432 if m: 

433 # This is a new list item 

434 # Check first item for the start index 

435 if not items and self.TAG == 'ol': 

436 # Detect the integer value of first list item 

437 INTEGER_RE = re.compile(r'(\d+)') 

438 self.STARTSWITH = INTEGER_RE.match(m.group(1)).group() 

439 # Append to the list 

440 items.append([m.group(3)]) 

441 elif self.INDENT_RE.match(line): 

442 # This is an indented (possibly nested) item. 

443 if items[-1][0].startswith(' '*self.tab_length): 

444 # Previous item was indented. Append to that item. 

445 items[-1].append(line) 

446 else: 

447 items.append([line]) 

448 else: 

449 # This is another line of previous item. Append to that item. 

450 items[-1].append(line) 

451 return ['\n'.join(item) for item in items] 

452 

453 

454class UListProcessor(OListProcessor): 

455 """ Process unordered list blocks. """ 

456 

457 TAG: str = 'ul' 

458 """ The tag used for the the wrapping element. """ 

459 

460 def __init__(self, parser: BlockParser): 

461 super().__init__(parser) 

462 # Detect an item (`1. item`). `group(1)` contains contents of item. 

463 self.RE = re.compile(r'^[ ]{0,%d}[*+-][ ]+(.*)' % (self.tab_length - 1)) 

464 

465 

466class HashHeaderProcessor(BlockProcessor): 

467 """ Process Hash Headers. """ 

468 

469 # Detect a header at start of any line in block 

470 RE = re.compile(r'(?:^|\n)(?P<level>#{1,6})(?P<header>(?:\\.|[^\\#]|#+(?=[^#\n]))*?)#*(?:\n|$)') 

471 

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

473 # Check for a line starting with `#` before running the regex, which is 

474 # slow to scan long blocks. 

475 return (block.startswith('#') or '\n#' in block) and bool(self.RE.search(block)) 

476 

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

478 block = blocks.pop(0) 

479 m = self.RE.search(block) 

480 if m: 

481 before = block[:m.start()] # All lines before header 

482 after = block[m.end():] # All lines after header 

483 if before: 

484 # As the header was not the first line of the block and the 

485 # lines before the header must be parsed first, 

486 # recursively parse this lines as a block. 

487 self.parser.parseBlocks(parent, [before]) 

488 # Create header using named groups from RE 

489 h = etree.SubElement(parent, 'h%d' % len(m.group('level'))) 

490 h.text = m.group('header').strip() 

491 if after: 

492 # Insert remaining lines as first block for future parsing. 

493 if self.parser.state.isstate('looselist'): 

494 # This is a weird edge case where a header is a child of a loose list 

495 # and there is no blank line after the header. To ensure proper 

496 # parsing, the line(s) after need to be detabbed. See #1443. 

497 after = self.looseDetab(after) 

498 blocks.insert(0, after) 

499 else: # pragma: no cover 

500 # This should never happen, but just in case... 

501 logger.warn("We've got a problem header: %r" % block) 

502 

503 

504class SetextHeaderProcessor(BlockProcessor): 

505 """ Process Setext-style Headers. """ 

506 

507 # Detect Setext-style header. Must be first 2 lines of block. 

508 RE = re.compile(r'^.*?\n(?:=+|-+)[ ]*(\n|$)', re.MULTILINE) 

509 

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

511 return bool(self.RE.match(block)) 

512 

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

514 lines = blocks.pop(0).split('\n', 2) 

515 # Determine level. `=` is 1 and `-` is 2. 

516 if lines[1].startswith('='): 

517 level = 1 

518 else: 

519 level = 2 

520 h = etree.SubElement(parent, 'h%d' % level) 

521 h.text = lines[0].strip() 

522 if len(lines) > 2: 

523 # Block contains additional lines. Add to master blocks for later. 

524 blocks.insert(0, lines[2]) 

525 

526 

527class HRProcessor(BlockProcessor): 

528 """ Process Horizontal Rules. """ 

529 

530 # Python's `re` module doesn't officially support atomic grouping. However you can fake it. 

531 # See https://stackoverflow.com/a/13577411/866026 

532 RE = r'^[ ]{0,3}(?=(?P<atomicgroup>(-+[ ]{0,2}){3,}|(_+[ ]{0,2}){3,}|(\*+[ ]{0,2}){3,}))(?P=atomicgroup)[ ]*$' 

533 # Detect hr on any line of a block. 

534 SEARCH_RE = re.compile(RE, re.MULTILINE) 

535 

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

537 m = self.SEARCH_RE.search(block) 

538 if m: 

539 # Save match object on class instance so we can use it later. 

540 self.match = m 

541 return True 

542 return False 

543 

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

545 block = blocks.pop(0) 

546 match = self.match 

547 # Check for lines in block before `hr`. 

548 prelines = block[:match.start()].rstrip('\n') 

549 if prelines: 

550 # Recursively parse lines before `hr` so they get parsed first. 

551 self.parser.parseBlocks(parent, [prelines]) 

552 # create hr 

553 etree.SubElement(parent, 'hr') 

554 # check for lines in block after `hr`. 

555 postlines = block[match.end():].lstrip('\n') 

556 if postlines: 

557 # Add lines after `hr` to master blocks for later parsing. 

558 blocks.insert(0, postlines) 

559 

560 

561class EmptyBlockProcessor(BlockProcessor): 

562 """ Process blocks that are empty or start with an empty line. """ 

563 

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

565 return not block or block.startswith('\n') 

566 

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

568 block = blocks.pop(0) 

569 filler = '\n\n' 

570 if block: 

571 # Starts with empty line 

572 # Only replace a single line. 

573 filler = '\n' 

574 # Save the rest for later. 

575 theRest = block[1:] 

576 if theRest: 

577 # Add remaining lines to master blocks for later. 

578 blocks.insert(0, theRest) 

579 sibling = self.lastChild(parent) 

580 if (sibling is not None and sibling.tag == 'pre' and 

581 len(sibling) and sibling[0].tag == 'code'): 

582 # Last block is a code block. Append to preserve whitespace. 

583 sibling[0].text = util.AtomicString( 

584 '{}{}'.format(sibling[0].text, filler) 

585 ) 

586 

587 

588class ReferenceProcessor(BlockProcessor): 

589 """ Process link references. """ 

590 RE = re.compile( 

591 r'^[ ]{0,3}\[([^\[\]]*)\]:[ ]*(?:\n[ ]*)?([^\s]+)[ ]*(?:\n[ ]*)?((["\'])(.*)\4[ ]*|\((.*)\)[ ]*)?$', 

592 re.MULTILINE 

593 ) 

594 

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

596 return True 

597 

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

599 block = blocks.pop(0) 

600 m = self.RE.search(block) 

601 if m: 

602 start = end = m.start() 

603 # Record the definitions on the following lines in this pass rather 

604 # than one per pass through the block processors. A definition after 

605 # the first which spans lines is left for the next pass, as another 

606 # processor may claim part of it. 

607 for m in self.RE.finditer(block, start): 

608 if m.start() > start and (block[end:m.start()].strip('\n') or '\n' in m.group()): 

609 break 

610 id = m.group(1).strip().lower() 

611 link = m.group(2).lstrip('<').rstrip('>') 

612 title = m.group(5) or m.group(6) 

613 self.parser.md.references[id] = (link, title) 

614 end = m.end() 

615 if block[end:].strip(): 

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

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

618 if block[:start].strip(): 

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

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

621 return True 

622 # No match. Restore block. 

623 blocks.insert(0, block) 

624 return False 

625 

626 

627class ParagraphProcessor(BlockProcessor): 

628 """ Process Paragraph blocks. """ 

629 

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

631 return True 

632 

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

634 block = blocks.pop(0) 

635 if block.strip(): 

636 # Not a blank block. Add to parent, otherwise throw it away. 

637 if self.parser.state.isstate('list'): 

638 # The parent is a tight-list. 

639 # 

640 # Check for any children. This will likely only happen in a 

641 # tight-list when a header isn't followed by a blank line. 

642 # For example: 

643 # 

644 # * # Header 

645 # Line 2 of list item - not part of header. 

646 sibling = self.lastChild(parent) 

647 if sibling is not None: 

648 # Insert after sibling. 

649 if sibling.tail: 

650 sibling.tail = '{}\n{}'.format(sibling.tail, block) 

651 else: 

652 sibling.tail = '\n%s' % block 

653 else: 

654 # Append to parent.text 

655 if parent.text: 

656 parent.text = '{}\n{}'.format(parent.text, block) 

657 else: 

658 parent.text = block.lstrip() 

659 else: 

660 # Create a regular paragraph 

661 p = etree.SubElement(parent, 'p') 

662 p.text = block.lstrip()