blocks.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import re
  2. import markdown
  3. from markdown.blockprocessors import BlockProcessor, HRProcessor
  4. from markdown.extensions.fenced_code import FencedBlockPreprocessor
  5. from markdown.preprocessors import Preprocessor
  6. from markdown.util import etree
  7. from django.utils.crypto import get_random_string
  8. QUOTE_START = get_random_string(32)
  9. QUOTE_END = get_random_string(32)
  10. class BBCodeHRProcessor(HRProcessor):
  11. RE = r'^\[hr\]*'
  12. # Detect hr on any line of a block.
  13. SEARCH_RE = re.compile(RE, re.MULTILINE | re.IGNORECASE)
  14. class QuoteExtension(markdown.Extension):
  15. def extendMarkdown(self, md):
  16. md.registerExtension(self)
  17. md.preprocessors.add('misago_bbcode_quote', QuotePreprocessor(md), '_end')
  18. md.parser.blockprocessors.add(
  19. 'misago_bbcode_quote', QuoteBlockProcessor(md.parser), '>code'
  20. )
  21. class QuotePreprocessor(Preprocessor):
  22. QUOTE_BLOCK_RE = re.compile(
  23. r'''
  24. \[quote\](?P<text>.*?)\[/quote\]
  25. '''.strip(), re.IGNORECASE | re.MULTILINE | re.DOTALL
  26. )
  27. QUOTE_BLOCK_TITLE_RE = re.compile(
  28. r'''
  29. \[quote=("?)(?P<title>.*?)("?)](?P<text>.*?)\[/quote\]
  30. '''.strip(), re.IGNORECASE | re.MULTILINE | re.DOTALL
  31. )
  32. def run(self, lines):
  33. text = '\n'.join(lines)
  34. while self.QUOTE_BLOCK_RE.search(text):
  35. text = self.QUOTE_BLOCK_RE.sub(self.replace, text)
  36. while self.QUOTE_BLOCK_TITLE_RE.search(text):
  37. text = self.QUOTE_BLOCK_TITLE_RE.sub(self.replace_titled, text)
  38. return text.split('\n')
  39. def replace(self, matchobj):
  40. text = matchobj.group('text')
  41. return '\n\n%s\n\n%s\n\n%s\n\n' % (QUOTE_START, text, QUOTE_END)
  42. def replace_titled(self, matchobj):
  43. title = matchobj.group('title').strip()
  44. text = matchobj.group('text')
  45. if title:
  46. return '\n\n%s%s\n\n%s\n\n%s\n\n' % (QUOTE_START, title, text, QUOTE_END)
  47. else:
  48. return '\n\n%s\n\n%s\n\n%s\n\n' % (QUOTE_START, text, QUOTE_END)
  49. class QuoteBlockProcessor(BlockProcessor):
  50. def __init__(self, *args, **kwargs):
  51. super().__init__(*args, **kwargs)
  52. self._title = None
  53. self._quote = 0
  54. self._children = []
  55. def test(self, parent, block):
  56. return block.strip().startswith(QUOTE_START) or self._quote
  57. def run(self, parent, blocks):
  58. block = blocks.pop(0)
  59. if block.strip().startswith(QUOTE_START):
  60. self._quote += 1
  61. if self._quote == 1:
  62. self._title = block[len(QUOTE_START):].strip() or None
  63. self._children.append(block)
  64. if block.strip() == QUOTE_END:
  65. self._quote -= 1
  66. if not self._quote:
  67. children, self._children = self._children[1:-1], []
  68. title, self._title = self._title, None
  69. aside = etree.SubElement(parent, 'aside')
  70. aside.set('class', 'quote-block')
  71. heading = etree.SubElement(aside, 'div')
  72. heading.set('class', 'quote-heading')
  73. blockquote = etree.SubElement(aside, 'blockquote')
  74. blockquote.set('class', 'quote-body')
  75. if title:
  76. heading.text = title
  77. self.parser.parseBlocks(blockquote, children)
  78. class CodeBlockExtension(markdown.Extension):
  79. def extendMarkdown(self, md):
  80. md.registerExtension(self)
  81. md.preprocessors.add(
  82. 'misago_code_bbcode', CodeBlockPreprocessor(md), ">normalize_whitespace"
  83. )
  84. class CodeBlockPreprocessor(FencedBlockPreprocessor):
  85. FENCED_BLOCK_RE = re.compile(
  86. r'''
  87. \[code(=("?)(?P<lang>.*?)("?))?](([ ]*\n)+)?(?P<code>.*?)((\s|\n)+)?\[/code\]
  88. ''', re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE
  89. )