blocks.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. from __future__ import unicode_literals
  2. import re
  3. from django.utils.crypto import get_random_string
  4. import markdown
  5. from markdown.blockprocessors import BlockProcessor, HRProcessor
  6. from markdown.preprocessors import Preprocessor
  7. from markdown.util import etree
  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('misago_bbcode_quote', QuoteBlockProcessor(md.parser), '>code')
  19. class QuotePreprocessor(Preprocessor):
  20. QUOTE_BLOCK_RE = re.compile(r'''
  21. \[quote\](?P<text>.*?)\[/quote\]
  22. '''.strip(), re.IGNORECASE | re.MULTILINE | re.DOTALL);
  23. QUOTE_BLOCK_AUTHORED_RE = re.compile(r'''
  24. \[quote=("?)(@?)(?P<author>[0-9a-zA-Z]+)("?)](?P<text>.*?)\[/quote\]
  25. '''.strip(), re.IGNORECASE | re.MULTILINE | re.DOTALL);
  26. def run(self, lines):
  27. text = '\n'.join(lines)
  28. while self.QUOTE_BLOCK_RE.search(text):
  29. text = self.QUOTE_BLOCK_RE.sub(self.replace, text)
  30. while self.QUOTE_BLOCK_AUTHORED_RE.search(text):
  31. text = self.QUOTE_BLOCK_AUTHORED_RE.sub(self.replace_authored, text)
  32. return text.split('\n')
  33. def replace(self, matchobj):
  34. text = matchobj.group('text')
  35. return '\n\n{}\n\n{}\n\n{}\n\n'.format(QUOTE_START, text, QUOTE_END)
  36. def replace_authored(self, matchobj):
  37. author = matchobj.group('author').lstrip('@').strip()
  38. text = matchobj.group('text')
  39. if author:
  40. return '\n\n{}{}\n\n{}\n\n{}\n\n'.format(QUOTE_START, author, text, QUOTE_END)
  41. else:
  42. return '\n\n{}\n\n{}\n\n{}\n\n'.format(QUOTE_START, text, QUOTE_END)
  43. class QuoteBlockProcessor(BlockProcessor):
  44. def __init__(self, *args, **kwargs):
  45. super(QuoteBlockProcessor, self).__init__(*args, **kwargs)
  46. self._author = None
  47. self._quote = 0
  48. self._children = []
  49. def test(self, parent, block):
  50. return block.strip().startswith(QUOTE_START) or self._quote
  51. def run(self, parent, blocks):
  52. block = blocks.pop(0)
  53. if block.strip().startswith(QUOTE_START):
  54. self._quote += 1
  55. if self._quote == 1:
  56. self._author = block[len(QUOTE_START):].strip() or None
  57. self._children.append(block)
  58. if block.strip() == QUOTE_END:
  59. self._quote -= 1
  60. if not self._quote:
  61. children, self._children = self._children[1:-1], []
  62. author, self._author = self._author, None
  63. blockquote = etree.SubElement(parent, 'blockquote')
  64. header = etree.SubElement(blockquote, 'header')
  65. if author:
  66. header.text = '@{}'.format(author)
  67. self.parser.parseBlocks(blockquote, children)