add search engine - ringle
This commit is contained in:
@@ -6,8 +6,9 @@ class CSSRule:
|
||||
var event_prefix: String = ""
|
||||
var properties: Dictionary = {}
|
||||
var specificity: int = 0
|
||||
var selector_type: String = "simple" # simple, descendant, child, adjacent_sibling, general_sibling, attribute
|
||||
var selector_parts: Array = [] # For complex selectors
|
||||
var selector_type: String = "simple" # simple, descendant, child, adjacent_sibling, general_sibling, attribute
|
||||
var selector_parts: Array = [] # For complex selectors
|
||||
var is_user_css: bool = false
|
||||
|
||||
func init(sel: String = ""):
|
||||
selector = sel
|
||||
@@ -52,9 +53,9 @@ class CSSRule:
|
||||
func calculate_specificity():
|
||||
specificity = 1
|
||||
if selector.begins_with("."):
|
||||
specificity += 10
|
||||
specificity += 20
|
||||
if selector.contains("["):
|
||||
specificity += 10 # Attribute selectors
|
||||
specificity += 10
|
||||
match selector_type:
|
||||
"child":
|
||||
specificity += 8
|
||||
@@ -68,6 +69,10 @@ class CSSRule:
|
||||
specificity += 4
|
||||
if event_prefix.length() > 0:
|
||||
specificity += 10
|
||||
|
||||
if is_user_css:
|
||||
specificity += 100
|
||||
|
||||
|
||||
class CSSStylesheet:
|
||||
var rules: Array[CSSRule] = []
|
||||
@@ -287,7 +292,7 @@ func init(css_content: String = ""):
|
||||
stylesheet = CSSStylesheet.new()
|
||||
css_text = css_content
|
||||
|
||||
func parse() -> void:
|
||||
func parse(is_user_css: bool = false) -> void:
|
||||
if css_text.is_empty():
|
||||
return
|
||||
|
||||
@@ -295,7 +300,7 @@ func parse() -> void:
|
||||
var rules = extract_rules(cleaned_css)
|
||||
|
||||
for rule_data in rules:
|
||||
var rule = parse_rule(rule_data)
|
||||
var rule = parse_rule(rule_data, is_user_css)
|
||||
if rule:
|
||||
stylesheet.add_rule(rule)
|
||||
|
||||
@@ -355,9 +360,10 @@ func find_matching_brace(css: String, start_pos: int) -> int:
|
||||
|
||||
return -1
|
||||
|
||||
func parse_rule(rule_data: Dictionary) -> CSSRule:
|
||||
func parse_rule(rule_data: Dictionary, is_user_css: bool = false) -> CSSRule:
|
||||
var rule = CSSRule.new()
|
||||
rule.selector = rule_data.selector
|
||||
rule.is_user_css = is_user_css
|
||||
rule.init(rule.selector)
|
||||
var properties_text = rule_data.properties
|
||||
|
||||
@@ -397,6 +403,7 @@ func parse_utility_class(rule: CSSRule, utility_name: String) -> void:
|
||||
pseudo_rule.event_prefix = pseudo
|
||||
pseudo_rule.selector_type = rule.selector_type
|
||||
pseudo_rule.selector_parts = rule.selector_parts.duplicate()
|
||||
pseudo_rule.is_user_css = rule.is_user_css
|
||||
pseudo_rule.calculate_specificity()
|
||||
pseudo_rule.specificity += 100
|
||||
|
||||
|
||||
@@ -25,20 +25,20 @@ class HTMLElement:
|
||||
return get_attribute("id")
|
||||
|
||||
func get_collapsed_text() -> String:
|
||||
var collapsed = text_content.strip_edges()
|
||||
var collapsed = HTMLParser.unescape_html_entities(text_content).strip_edges()
|
||||
# Replace multiple whitespace characters with single space
|
||||
var regex = RegEx.new()
|
||||
regex.compile("\\s+")
|
||||
return regex.sub(collapsed, " ", true)
|
||||
|
||||
func get_preserved_text() -> String:
|
||||
return text_content
|
||||
return HTMLParser.unescape_html_entities(text_content)
|
||||
|
||||
func get_bbcode_formatted_text(parser: HTMLParser) -> String:
|
||||
var styles = {}
|
||||
if parser != null:
|
||||
styles = parser.get_element_styles_with_inheritance(self, "", [])
|
||||
return HTMLParser.get_bbcode_with_styles(self, styles, parser)
|
||||
return HTMLParser.get_bbcode_with_styles(self, styles, parser, [])
|
||||
|
||||
func is_inline_element() -> bool:
|
||||
return tag_name in ["b", "i", "u", "small", "mark", "code", "span", "a", "input"]
|
||||
@@ -68,10 +68,69 @@ var bitcode: PackedByteArray
|
||||
var parse_result: ParseResult
|
||||
|
||||
func _init(data: PackedByteArray):
|
||||
bitcode = data
|
||||
var html_string = data.get_string_from_utf8()
|
||||
html_string = preprocess_html_entities(html_string)
|
||||
bitcode = html_string.to_utf8_buffer()
|
||||
xml_parser = XMLParser.new()
|
||||
parse_result = ParseResult.new()
|
||||
|
||||
static func unescape_html_entities(text: String) -> String:
|
||||
return text.replace("<", "<").replace(">", ">").replace(""", "\"").replace("'", "'").replace("&", "&")
|
||||
|
||||
static func preprocess_html_entities(html: String) -> String:
|
||||
var result = ""
|
||||
var i = 0
|
||||
var in_tag = false
|
||||
|
||||
while i < html.length():
|
||||
var char = html[i]
|
||||
|
||||
if char == "<":
|
||||
# Check if this starts a valid HTML tag
|
||||
var tag_end = html.find(">", i)
|
||||
if tag_end != -1:
|
||||
var potential_tag = html.substr(i, tag_end - i + 1)
|
||||
# Simple check for valid tag pattern
|
||||
if is_valid_tag_pattern(potential_tag):
|
||||
result += potential_tag
|
||||
i = tag_end + 1
|
||||
continue
|
||||
# If not a valid tag, escape it
|
||||
result += "<"
|
||||
elif char == ">":
|
||||
# Escape standalone > that's not part of a tag
|
||||
result += ">"
|
||||
else:
|
||||
result += char
|
||||
|
||||
i += 1
|
||||
|
||||
return result
|
||||
|
||||
static func is_valid_tag_pattern(tag: String) -> bool:
|
||||
if tag.length() < 3: # Minimum: <x>
|
||||
return false
|
||||
|
||||
if not tag.begins_with("<") or not tag.ends_with(">"):
|
||||
return false
|
||||
|
||||
var inner = tag.substr(1, tag.length() - 2).strip_edges()
|
||||
|
||||
if inner.begins_with("/"):
|
||||
inner = inner.substr(1).strip_edges()
|
||||
|
||||
# Handle self-closing tags
|
||||
if inner.ends_with("/"):
|
||||
inner = inner.substr(0, inner.length() - 1).strip_edges()
|
||||
|
||||
# Extract tag name (first part before space or attributes)
|
||||
var tag_name = inner.split(" ")[0].split("\t")[0]
|
||||
|
||||
# Valid tag names contain only letters, numbers, and hyphens
|
||||
var regex = RegEx.new()
|
||||
regex.compile("^[a-zA-Z][a-zA-Z0-9-]*$")
|
||||
return regex.search(tag_name) != null
|
||||
|
||||
# Main parsing function
|
||||
func parse() -> ParseResult:
|
||||
xml_parser.open_buffer(bitcode)
|
||||
@@ -408,7 +467,7 @@ func apply_element_styles(node: Control, element: HTMLElement, parser: HTMLParse
|
||||
var styles = parser.get_element_styles_with_inheritance(element, "", [])
|
||||
if node.get("rich_text_label"):
|
||||
var label = node.rich_text_label
|
||||
var text = HTMLParser.get_bbcode_with_styles(element, styles, parser)
|
||||
var text = HTMLParser.get_bbcode_with_styles(element, styles, parser, [])
|
||||
label.text = text
|
||||
|
||||
static func apply_element_bbcode_formatting(element: HTMLElement, styles: Dictionary, content: String, parser: HTMLParser = null) -> String:
|
||||
@@ -478,7 +537,13 @@ static func apply_element_bbcode_formatting(element: HTMLElement, styles: Dictio
|
||||
|
||||
return formatted_content
|
||||
|
||||
static func get_bbcode_with_styles(element: HTMLElement, styles: Dictionary, parser: HTMLParser) -> String:
|
||||
static func get_bbcode_with_styles(element: HTMLElement, styles: Dictionary, parser: HTMLParser, visited_elements: Array = []) -> String:
|
||||
if element in visited_elements:
|
||||
return ""
|
||||
|
||||
var new_visited = visited_elements.duplicate()
|
||||
new_visited.append(element)
|
||||
|
||||
var text = ""
|
||||
if element.text_content.length() > 0:
|
||||
text += element.get_collapsed_text()
|
||||
@@ -486,8 +551,8 @@ static func get_bbcode_with_styles(element: HTMLElement, styles: Dictionary, par
|
||||
for child in element.children:
|
||||
var child_styles = styles
|
||||
if parser != null:
|
||||
child_styles = parser.get_element_styles_with_inheritance(child, "", [])
|
||||
var child_content = HTMLParser.get_bbcode_with_styles(child, child_styles, parser)
|
||||
child_styles = parser.get_element_styles_with_inheritance(child, "", new_visited)
|
||||
var child_content = HTMLParser.get_bbcode_with_styles(child, child_styles, parser, new_visited)
|
||||
child_content = apply_element_bbcode_formatting(child, child_styles, child_content, parser)
|
||||
text += child_content
|
||||
|
||||
|
||||
@@ -642,8 +642,6 @@ func execute_lua_script(code: String, vm: LuauVM):
|
||||
script_start_time = Time.get_ticks_msec() / 1000.0
|
||||
threaded_vm.execute_script_async(code)
|
||||
|
||||
|
||||
|
||||
func _on_threaded_script_completed(result: Dictionary):
|
||||
var execution_time = (Time.get_ticks_msec() / 1000.0) - script_start_time
|
||||
|
||||
@@ -689,6 +687,10 @@ func _handle_dom_operation(operation: Dictionary):
|
||||
LuaDOMUtils.handle_insert_after(operation, dom_parser, self)
|
||||
"replace_child":
|
||||
LuaDOMUtils.handle_replace_child(operation, dom_parser, self)
|
||||
"focus_element":
|
||||
_handle_element_focus(operation)
|
||||
"unfocus_element":
|
||||
_handle_element_unfocus(operation)
|
||||
_:
|
||||
pass # Unknown operation type, ignore
|
||||
|
||||
@@ -786,6 +788,53 @@ func _handle_text_getting(operation: Dictionary):
|
||||
return element.text_content
|
||||
return ""
|
||||
|
||||
func _handle_element_focus(operation: Dictionary):
|
||||
var element_id: String = operation.element_id
|
||||
|
||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||
if not dom_node:
|
||||
return
|
||||
|
||||
var focusable_control = _find_focusable_control(dom_node)
|
||||
if focusable_control and focusable_control.has_method("grab_focus"):
|
||||
focusable_control.call_deferred("grab_focus")
|
||||
|
||||
func _handle_element_unfocus(operation: Dictionary):
|
||||
var element_id: String = operation.element_id
|
||||
|
||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||
if not dom_node:
|
||||
return
|
||||
|
||||
var focusable_control = _find_focusable_control(dom_node)
|
||||
if focusable_control and focusable_control.has_method("release_focus"):
|
||||
focusable_control.call_deferred("release_focus")
|
||||
|
||||
func _find_focusable_control(node: Node) -> Control:
|
||||
if not node:
|
||||
return null
|
||||
|
||||
if node is Control and node.focus_mode != Control.FOCUS_NONE and node.has_method("grab_focus"):
|
||||
return node
|
||||
|
||||
if node.has_method("get_children"):
|
||||
for child in node.get_children():
|
||||
if child.visible and child is Control:
|
||||
if child is LineEdit or child is TextEdit or child is SpinBox or child is OptionButton:
|
||||
if child.focus_mode != Control.FOCUS_NONE:
|
||||
return child
|
||||
|
||||
if child is SpinBox:
|
||||
var line_edit = child.get_line_edit()
|
||||
if line_edit and line_edit.focus_mode != Control.FOCUS_NONE:
|
||||
return line_edit
|
||||
|
||||
var focusable_child = _find_focusable_control(child)
|
||||
if focusable_child:
|
||||
return focusable_child
|
||||
|
||||
return null
|
||||
|
||||
func _handle_body_event_registration(operation: Dictionary):
|
||||
var event_name: String = operation.event_name
|
||||
var callback_ref: int = operation.callback_ref
|
||||
|
||||
Reference in New Issue
Block a user