blocks.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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('misago_bbcode_quote', QuoteBlockProcessor(md.parser), '>code')
  20. class QuotePreprocessor(Preprocessor):
  21. QUOTE_BLOCK_RE = re.compile(r'''
  22. \[quote\](?P<text>.*?)\[/quote\]
  23. '''.strip(), re.IGNORECASE | re.MULTILINE | re.DOTALL);
  24. QUOTE_BLOCK_TITLE_RE = re.compile(r'''
  25. \[quote=("?)(?P<title>.*?)("?)](?P<text>.*?)\[/quote\]
  26. '''.strip(), re.IGNORECASE | re.MULTILINE | re.DOTALL);
  27. def run(self, lines):
  28. text = '\n'.join(lines)
  29. while self.QUOTE_BLOCK_RE.search(text):
  30. text = self.QUOTE_BLOCK_RE.sub(self.replace, text)
  31. while self.QUOTE_BLOCK_TITLE_RE.search(text):
  32. text = self.QUOTE_BLOCK_TITLE_RE.sub(self.replace_titled, text)
  33. return text.split('\n')
  34. def replace(self, matchobj):
  35. text = matchobj.group('text')
  36. return '\n\n{}\n\n{}\n\n{}\n\n'.format(QUOTE_START, text, QUOTE_END)
  37. def replace_titled(self, matchobj):
  38. title = matchobj.group('title').strip()
  39. text = matchobj.group('text')
  40. if title:
  41. return '\n\n{}{}\n\n{}\n\n{}\n\n'.format(QUOTE_START, title, text, QUOTE_END)
  42. else:
  43. return '\n\n{}\n\n{}\n\n{}\n\n'.format(QUOTE_START, text, QUOTE_END)
  44. class QuoteBlockProcessor(BlockProcessor):
  45. def __init__(self, *args, **kwargs):
  46. super(QuoteBlockProcessor, self).__init__(*args, **kwargs)
  47. self._title = None
  48. self._quote = 0
  49. self._children = []
  50. def test(self, parent, block):
  51. return block.strip().startswith(QUOTE_START) or self._quote
  52. def run(self, parent, blocks):
  53. block = blocks.pop(0)
  54. if block.strip().startswith(QUOTE_START):
  55. self._quote += 1
  56. if self._quote == 1:
  57. self._title = block[len(QUOTE_START):].strip() or None
  58. self._children.append(block)
  59. if block.strip() == QUOTE_END:
  60. self._quote -= 1
  61. if not self._quote:
  62. children, self._children = self._children[1:-1], []
  63. title, self._title = self._title, None
  64. aside = etree.SubElement(parent, 'aside')
  65. aside.set('class', 'quote-block')
  66. heading = etree.SubElement(aside, 'div')
  67. heading.set('class', 'quote-heading')
  68. blockquote = etree.SubElement(aside, 'blockquote')
  69. blockquote.set('class', 'quote-body')
  70. if title:
  71. heading.text = title
  72. self.parser.parseBlocks(blockquote, children)
  73. class CodeBlockExtension(markdown.Extension):
  74. def extendMarkdown(self, md):
  75. md.registerExtension(self)
  76. md.preprocessors.add('misago_code_bbcode',
  77. CodeBlockPreprocessor(md),
  78. ">normalize_whitespace")
  79. class CodeBlockPreprocessor(FencedBlockPreprocessor):
  80. FENCED_BLOCK_RE = re.compile(r'''
  81. \[code(=("?)(?P<lang>.*?)("?))?](([ ]*\n)+)?(?P<code>.*?)((\s|\n)+)?\[/code\]
  82. ''', re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE)