blocks.py 3.7 KB

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