Skip to content

Instantly share code, notes, and snippets.

@dalexeev
Last active January 4, 2024 22:24
Show Gist options
  • Save dalexeev/cfcb94a41e80de301cc595b74f7b5ebd to your computer and use it in GitHub Desktop.
Save dalexeev/cfcb94a41e80de301cc595b74f7b5ebd to your computer and use it in GitHub Desktop.
GDScript BBCode parser/preprocessor
# This option roughly matches the described requirements of `get_parsed_text()` emulation.
# See https://github.com/godotengine/godot/blob/179dfdc8d78b5bd5377dd115af026df58308bdaf/scene/gui/rich_text_label.cpp#L3999.
const TAGS_TO_STRIP: Array[String] = [
"b", "i", "code", "table", "cell", "u", "s", "center", "fill", "left", "right",
"ul", "ol", "lang", "p", "url", "hint", "dropcap", "color", "outline_color",
"font_size", "opentype_features", "otf", "font", "outline_size", "fade", "shake",
"wave", "tornado", "rainbow", "pulse", "bgcolor", "fgcolor",
]
const TAGS_TO_REPLACE: Dictionary = {
"lb": "[",
"rb": "]",
"lrm": "\u200E",
"rlm": "\u200F",
"lre": "\u202A",
"rle": "\u202B",
"lro": "\u202D",
"rlo": "\u202E",
"pdf": "\u202C",
"alm": "\u061c",
"lri": "\u2066",
"rli": "\u2027",
"fsi": "\u2068",
"pdi": "\u2069",
"zwj": "\u200D",
"zwnj": "\u200C",
"wj": "\u2060",
"shy": "\u00AD",
"img": "",
}
func preprocess_bbcode(input: String) -> String:
var output: String = ""
var pos: int = 0
var tag_stack: Array[String] = []
while true:
var lb_pos: int = input.find("[", pos)
if lb_pos < 0:
break
var rb_pos: int = input.find("]", lb_pos + 1)
if rb_pos < 0:
break
output += input.substr(pos, lb_pos - pos)
pos = rb_pos + 1
var tag: String = input.substr(lb_pos + 1, rb_pos - lb_pos - 1)
if tag.begins_with("/"):
if tag_stack.is_empty() or tag.trim_prefix("/") != tag_stack[-1]:
output += "[" + tag + "]"
else:
tag_stack.pop_back()
else:
var tag_name: String = tag.get_slice(" ", 0).get_slice("=", 0)
if TAGS_TO_STRIP.has(tag_name):
tag_stack.push_back(tag_name)
elif TAGS_TO_REPLACE.has(tag_name):
output += TAGS_TO_REPLACE[tag_name]
else:
output += "[" + tag + "]"
return output + input.substr(pos)
# This option is designed to handle pseudo-tags. The rest of the tags are saved as is,
# you can add a check only for individual tags, for example `[url]`.
const SINGLE_TAGS: Array[String] = [
"lb", "rb", "lrm", "rlm", "lre", "rle", "lro", "rlo", "pdf", "alm", "lri", "rli",
"fsi", "pdi", "zwj", "zwnj", "wj", "shy", "img",
]
func preprocess_bbcode(input: String) -> String:
var output: String = ""
var pos: int = 0
var tag_stack: Array[String] = []
while true:
var lb_pos: int = input.find("[", pos)
if lb_pos < 0:
break
var rb_pos: int = input.find("]", lb_pos + 1)
if rb_pos < 0:
break
output += input.substr(pos, lb_pos - pos)
pos = rb_pos + 1
var tag: String = input.substr(lb_pos + 1, rb_pos - lb_pos - 1)
if tag.begins_with("/"):
if tag_stack.is_empty() or tag.trim_prefix("/") != tag_stack[-1]:
output += "[lb]" + tag + "]" # Escape the tag just in case.
else:
if tag == "/user":
output += "[/color][/b][/url]"
elif tag == "/item":
output += "[/color][/url]"
elif tag == "/url":
output += "[lb]/url]"
tag_stack.pop_back()
else:
var tag_name: String = tag.get_slice(" ", 0).get_slice("=", 0)
var params: String = tag.substr(tag_name.length() + 1)
if not SINGLE_TAGS.has(tag_name):
tag_stack.push_back(tag_name)
if tag_name == "user":
output += "[url=user:%s][b][color=blue]" % params
elif tag_name == "item":
output += "[url=item:%s][img=res://items/%s.png][color=green]" % [params, params]
elif tag_name == "url":
output += ("[" + tag + "]").replace("[", "[lb]")
else:
output += "[" + tag + "]" # Leave it as is, regardless of the validity.
return output + input.substr(pos)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment