/bbcodepy/parser.py

https://github.com/vishnevskiy/bbcodepy · Python · 112 lines · 83 code · 29 blank · 0 comment · 29 complexity · 45885f72e310e8982db6d63cfbce810b MD5 · raw file

  1. from .tags import BUILTIN_TAGS, Tag
  2. from .renderer import Renderer
  3. import re
  4. _WHITESPACE = ' '
  5. _TOKEN_RE = re.compile(r'(\[/?.+?\])')
  6. _START_NEWLINE_RE = re.compile(r'^\r?\n')
  7. class Parser(object):
  8. def __init__(self, allowed_tags=None):
  9. if allowed_tags is None:
  10. self.tags = BUILTIN_TAGS.copy()
  11. else:
  12. self.tags = {}
  13. for tag in allowed_tags:
  14. if tag in BUILTIN_TAGS:
  15. self.tags[tag] = BUILTIN_TAGS[tag]
  16. self.renderer = Renderer()
  17. def register_tag(self, name, tag):
  18. self.tags[name] = tag
  19. def _parse_params(self, token):
  20. params = []
  21. if token:
  22. target = key = []
  23. value = []
  24. terminate = _WHITESPACE
  25. skip_next = False
  26. for c in token:
  27. if skip_next:
  28. skip_next = False
  29. elif target == key and c == '=':
  30. target = value
  31. elif not value and c == '"':
  32. terminate = c
  33. elif c != terminate:
  34. target.append(c)
  35. else:
  36. params.append((''.join(key).lower(), ''.join(value)))
  37. if not terminate.isspace():
  38. skip_next = True
  39. target = key = []
  40. value = []
  41. terminate = _WHITESPACE
  42. params.append((''.join(key).lower(), ''.join(value)))
  43. return params
  44. def _create_text_node(self, parent, text):
  45. if parent.children and parent.children[-1].STRIP_OUTER:
  46. text = _START_NEWLINE_RE.sub('', text, 1)
  47. Tag(self.renderer, text=text, parent=parent)
  48. def parse(self, bbcode):
  49. current = root = Tag(self.renderer)
  50. tokens = _TOKEN_RE.split(bbcode)
  51. while tokens:
  52. token = tokens.pop(0)
  53. if re.match(_TOKEN_RE, token):
  54. params = self._parse_params(token[1:-1])
  55. tag_name = params[0][0]
  56. if tag_name in current.CLOSED_BY:
  57. tokens.insert(0, token)
  58. tag_name = '/' + current.name
  59. params = []
  60. if tag_name[0] == '/':
  61. tag_name = tag_name[1:]
  62. if tag_name not in self.tags:
  63. self._create_text_node(current, token)
  64. continue
  65. if current.name == tag_name:
  66. current = current.parent
  67. else:
  68. cls = self.tags.get(tag_name)
  69. if cls is None:
  70. self._create_text_node(current, token)
  71. continue
  72. tag = cls(self.renderer, tag_name, parent=current, params=params)
  73. if not tag.SELF_CLOSE and (tag_name not in cls.CLOSED_BY or current.name != tag_name):
  74. current = tag
  75. else:
  76. self._create_text_node(current, token)
  77. return root
  78. def to_html(self, bbcode, prettify=False):
  79. html = self.parse(bbcode).to_html()
  80. if prettify:
  81. from BeautifulSoup import BeautifulSoup
  82. html = BeautifulSoup(html).prettify()
  83. return html