port babycode parser
This commit is contained in:
parent
fd257e701f
commit
9126ce4f61
241
app/lib/babycode_parser.py
Normal file
241
app/lib/babycode_parser.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
# originally written in lua by kaesa
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
PAT_EMOTE = r"[^\s:]"
|
||||||
|
PAT_BBCODE_TAG = r"\w"
|
||||||
|
PAT_BBCODE_ATTR = r"[^\s\]]"
|
||||||
|
PAT_LINK = r"https?:\/\/[\w\-_.?:\/=&~@#%]+[\w\-\/]"
|
||||||
|
|
||||||
|
class Parser:
|
||||||
|
def __init__(self, src_str):
|
||||||
|
self.valid_bbcode_tags = []
|
||||||
|
self.valid_emotes = []
|
||||||
|
self.bbcode_tags_only_text_children = [],
|
||||||
|
self.source = src_str
|
||||||
|
self.position = 0
|
||||||
|
self.position_stack = []
|
||||||
|
|
||||||
|
|
||||||
|
def advance(self, count = 1):
|
||||||
|
self.position += count
|
||||||
|
|
||||||
|
|
||||||
|
def is_end_of_source(self, offset = 0):
|
||||||
|
return self.position + offset >= len(self.source)
|
||||||
|
|
||||||
|
|
||||||
|
def save_position(self):
|
||||||
|
self.position_stack.append(self.position)
|
||||||
|
|
||||||
|
|
||||||
|
def restore_position(self):
|
||||||
|
self.position = self.position_stack.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def forget_position(self):
|
||||||
|
self.position_stack.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def peek_char(self, offset = 0):
|
||||||
|
if self.is_end_of_source(offset):
|
||||||
|
return ""
|
||||||
|
return self.source[self.position + offset]
|
||||||
|
|
||||||
|
|
||||||
|
def get_char(self):
|
||||||
|
char = self.peek_char()
|
||||||
|
self.advance()
|
||||||
|
return char
|
||||||
|
|
||||||
|
|
||||||
|
def check_char(self, wanted):
|
||||||
|
char = self.peek_char()
|
||||||
|
|
||||||
|
if char == wanted:
|
||||||
|
self.advance()
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_str(self, wanted):
|
||||||
|
self.save_position()
|
||||||
|
|
||||||
|
# for each char in wanted
|
||||||
|
for i in range(len(wanted)):
|
||||||
|
if not self.check_char(wanted[i]):
|
||||||
|
self.restore_position()
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.forget_position()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def match_pattern(self, pattern):
|
||||||
|
buf = ""
|
||||||
|
while not self.is_end_of_source():
|
||||||
|
ch = self.peek_char()
|
||||||
|
|
||||||
|
if not re.match(pattern, ch):
|
||||||
|
break
|
||||||
|
|
||||||
|
self.advance()
|
||||||
|
buf = buf + ch
|
||||||
|
|
||||||
|
return buf
|
||||||
|
|
||||||
|
|
||||||
|
def parse_emote(self):
|
||||||
|
self.save_position()
|
||||||
|
|
||||||
|
if not self.check_char(":"):
|
||||||
|
self.restore_position()
|
||||||
|
return None
|
||||||
|
|
||||||
|
name = self.match_pattern(PAT_EMOTE)
|
||||||
|
|
||||||
|
if not self.check_char(":"):
|
||||||
|
self.restore_position()
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not name in self.valid_emotes:
|
||||||
|
self.restore_position()
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.forget_position()
|
||||||
|
return {
|
||||||
|
"type": "emote",
|
||||||
|
"name": name
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_bbcode_open(self):
|
||||||
|
self.save_position()
|
||||||
|
|
||||||
|
if not self.check_char("["):
|
||||||
|
self.restore_position()
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
name = self.match_pattern(PAT_BBCODE_TAG)
|
||||||
|
|
||||||
|
if name == "":
|
||||||
|
self.restore_position()
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
attr = None
|
||||||
|
|
||||||
|
if self.check_char("="):
|
||||||
|
attr = self.match_pattern(PAT_BBCODE_ATTR)
|
||||||
|
|
||||||
|
if not self.check_char("]"):
|
||||||
|
self.restore_position()
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
if not name in self.valid_bbcode_tags:
|
||||||
|
self.restore_position()
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
self.forget_position()
|
||||||
|
return name, attr
|
||||||
|
|
||||||
|
|
||||||
|
def parse_bbcode(self):
|
||||||
|
self.save_position()
|
||||||
|
|
||||||
|
name, attr = self.parse_bbcode_open()
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
self.restore_position()
|
||||||
|
return None
|
||||||
|
|
||||||
|
children = []
|
||||||
|
|
||||||
|
while not self.is_end_of_source():
|
||||||
|
if self.check_str(f"[/{name}]"):
|
||||||
|
break
|
||||||
|
|
||||||
|
if name in self.bbcode_tags_only_text_children:
|
||||||
|
ch = self.get_char()
|
||||||
|
|
||||||
|
if len(children) == 0:
|
||||||
|
children.append(ch)
|
||||||
|
else:
|
||||||
|
children[1] = children[1] + ch
|
||||||
|
else:
|
||||||
|
element = self.parse_element(children)
|
||||||
|
|
||||||
|
if element is None:
|
||||||
|
self.restore_position()
|
||||||
|
return None
|
||||||
|
|
||||||
|
children.append(element)
|
||||||
|
|
||||||
|
self.forget_position()
|
||||||
|
return {
|
||||||
|
"type": "bbcode",
|
||||||
|
"name": name,
|
||||||
|
"attr": attr,
|
||||||
|
"children": children,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_rule(self):
|
||||||
|
if not self.check_str("---"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"type": "rule"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_link(self):
|
||||||
|
self.save_position()
|
||||||
|
|
||||||
|
# extract printable chars (extreme hack edition)
|
||||||
|
word = self.match_pattern(r'[ -~]')
|
||||||
|
|
||||||
|
if not re.match(PAT_LINK, word):
|
||||||
|
self.restore_position()
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.forget_position()
|
||||||
|
return {
|
||||||
|
"type": "link",
|
||||||
|
"url": word
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_element(self, siblings):
|
||||||
|
if self.is_end_of_source():
|
||||||
|
return None
|
||||||
|
|
||||||
|
element = self.parse_emote() \
|
||||||
|
or self.parse_bbcode() \
|
||||||
|
or self.parse_rule() \
|
||||||
|
or self.parse_link()
|
||||||
|
|
||||||
|
if element is None:
|
||||||
|
if len(siblings) > 0:
|
||||||
|
last = siblings[-1]
|
||||||
|
|
||||||
|
if isinstance(last, str):
|
||||||
|
siblings.pop()
|
||||||
|
return last + self.get_char()
|
||||||
|
|
||||||
|
return self.get_char()
|
||||||
|
|
||||||
|
return element
|
||||||
|
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
elements = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
element = self.parse_element(elements)
|
||||||
|
if element is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
elements.append(element)
|
||||||
|
|
||||||
|
return elements
|
Loading…
Reference in New Issue
Block a user