quote.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import re
  2. from xml.etree.ElementTree import SubElement
  3. import markdown
  4. from django.utils.crypto import get_random_string
  5. from markdown.blockprocessors import BlockProcessor
  6. from markdown.preprocessors import Preprocessor
  7. QUOTE_START = get_random_string(32)
  8. QUOTE_END = get_random_string(32)
  9. class QuoteExtension(markdown.Extension):
  10. def extendMarkdown(self, md):
  11. md.registerExtension(self)
  12. md.preprocessors.register(QuotePreprocessor(md), "misago_bbcode_quote", 200)
  13. md.parser.blockprocessors.register(
  14. QuoteBlockProcessor(md.parser), "misago_bbcode_quote", 90
  15. )
  16. class QuotePreprocessor(Preprocessor):
  17. QUOTE_BLOCK_RE = re.compile(
  18. r"""
  19. \[quote\](?P<text>.*?)\[/quote\]
  20. """.strip(),
  21. re.IGNORECASE | re.MULTILINE | re.DOTALL,
  22. )
  23. QUOTE_BLOCK_TITLE_RE = re.compile(
  24. r"""
  25. \[quote=("?)(?P<title>.*?)("?)](?P<text>.*?)\[/quote\]
  26. """.strip(),
  27. re.IGNORECASE | re.MULTILINE | re.DOTALL,
  28. )
  29. def run(self, lines):
  30. text = "\n".join(lines)
  31. while self.QUOTE_BLOCK_RE.search(text):
  32. text = self.QUOTE_BLOCK_RE.sub(self.replace, text)
  33. while self.QUOTE_BLOCK_TITLE_RE.search(text):
  34. text = self.QUOTE_BLOCK_TITLE_RE.sub(self.replace_titled, text)
  35. return text.split("\n")
  36. def replace(self, matchobj):
  37. text = matchobj.group("text")
  38. return "\n\n%s\n\n%s\n\n%s\n\n" % (QUOTE_START, text, QUOTE_END)
  39. def replace_titled(self, matchobj):
  40. title = matchobj.group("title").strip()
  41. text = matchobj.group("text")
  42. if title:
  43. return "\n\n%s%s\n\n%s\n\n%s\n\n" % (QUOTE_START, title, text, QUOTE_END)
  44. return "\n\n%s\n\n%s\n\n%s\n\n" % (QUOTE_START, text, QUOTE_END)
  45. class QuoteBlockProcessor(BlockProcessor):
  46. def __init__(self, *args, **kwargs):
  47. super().__init__(*args, **kwargs)
  48. self._title = None
  49. self._quote = 0
  50. self._children = []
  51. def test(self, parent, block):
  52. return block.strip().startswith(QUOTE_START) or self._quote
  53. def run(self, parent, blocks):
  54. block = blocks.pop(0)
  55. if block.strip().startswith(QUOTE_START):
  56. self._quote += 1
  57. if self._quote == 1:
  58. self._title = block[len(QUOTE_START) :].strip() or None
  59. self._children.append(block)
  60. if block.strip() == QUOTE_END:
  61. self._quote -= 1
  62. if not self._quote:
  63. children, self._children = self._children[1:-1], []
  64. title, self._title = self._title, None
  65. aside = SubElement(parent, "aside")
  66. aside.set("class", "quote-block")
  67. heading = SubElement(aside, "div")
  68. heading.set("class", "quote-heading")
  69. heading.set("data-noquote", "1")
  70. blockquote = SubElement(aside, "blockquote")
  71. blockquote.set("class", "quote-body")
  72. blockquote.set("data-block", "quote")
  73. if title:
  74. blockquote.set("data-author", title)
  75. if title:
  76. heading.text = title
  77. self.parser.parseBlocks(blockquote, children)