blocks.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import re
  2. import markdown
  3. from django.utils.crypto import get_random_string
  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. 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(),
  26. 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(),
  32. re.IGNORECASE | re.MULTILINE | re.DOTALL,
  33. )
  34. def run(self, lines):
  35. text = "\n".join(lines)
  36. while self.QUOTE_BLOCK_RE.search(text):
  37. text = self.QUOTE_BLOCK_RE.sub(self.replace, text)
  38. while self.QUOTE_BLOCK_TITLE_RE.search(text):
  39. text = self.QUOTE_BLOCK_TITLE_RE.sub(self.replace_titled, text)
  40. return text.split("\n")
  41. def replace(self, matchobj):
  42. text = matchobj.group("text")
  43. return "\n\n%s\n\n%s\n\n%s\n\n" % (QUOTE_START, text, QUOTE_END)
  44. def replace_titled(self, matchobj):
  45. title = matchobj.group("title").strip()
  46. text = matchobj.group("text")
  47. if title:
  48. return "\n\n%s%s\n\n%s\n\n%s\n\n" % (QUOTE_START, title, text, QUOTE_END)
  49. else:
  50. return "\n\n%s\n\n%s\n\n%s\n\n" % (QUOTE_START, text, QUOTE_END)
  51. class QuoteBlockProcessor(BlockProcessor):
  52. def __init__(self, *args, **kwargs):
  53. super().__init__(*args, **kwargs)
  54. self._title = None
  55. self._quote = 0
  56. self._children = []
  57. def test(self, parent, block):
  58. return block.strip().startswith(QUOTE_START) or self._quote
  59. def run(self, parent, blocks):
  60. block = blocks.pop(0)
  61. if block.strip().startswith(QUOTE_START):
  62. self._quote += 1
  63. if self._quote == 1:
  64. self._title = block[len(QUOTE_START) :].strip() or None
  65. self._children.append(block)
  66. if block.strip() == QUOTE_END:
  67. self._quote -= 1
  68. if not self._quote:
  69. children, self._children = self._children[1:-1], []
  70. title, self._title = self._title, None
  71. aside = etree.SubElement(parent, "aside")
  72. aside.set("class", "quote-block")
  73. heading = etree.SubElement(aside, "div")
  74. heading.set("class", "quote-heading")
  75. blockquote = etree.SubElement(aside, "blockquote")
  76. blockquote.set("class", "quote-body")
  77. if title:
  78. heading.text = title
  79. self.parser.parseBlocks(blockquote, children)
  80. class CodeBlockExtension(markdown.Extension):
  81. def extendMarkdown(self, md):
  82. md.registerExtension(self)
  83. md.preprocessors.add(
  84. "misago_code_bbcode", CodeBlockPreprocessor(md), ">normalize_whitespace"
  85. )
  86. class CodeBlockPreprocessor(FencedBlockPreprocessor):
  87. FENCED_BLOCK_RE = re.compile(
  88. r"""
  89. \[code(=("?)(?P<lang>.*?)("?))?](([ ]*\n)+)?(?P<code>.*?)((\s|\n)+)?\[/code\]
  90. """,
  91. re.IGNORECASE | re.MULTILINE | re.DOTALL | re.VERBOSE,
  92. )