@tool class XMLNode: extends RefCounted var text: String func _init(text: String) -> void: self.text = text func _get_solid_text() -> String: assert(false, "This method is abstract and must be overriden in derived class") return "" func get_elements(text: String) -> Array[XMLNodeElement]: assert(false, "This method is abstract and must be overriden in derived class") return [] func _dump(target: PackedStringArray, indent: String, level: int) -> void: target.append(indent.repeat(level) + _get_solid_text()) class XMLNodeParent: extends XMLNode var children: Array[XMLNode] func _init(text: String) -> void: super(text) func _get_opening_tag() -> String: return "" func _get_closing_tag() -> String: return "" func _get_solid_text() -> String: assert(false, "This method is abstract and must be overriden in derived class") return "" func _dump_children(target: PackedStringArray, indent: String, level: int) -> void: for child in children: child._dump(target, indent, level) func _dump(target: PackedStringArray, indent: String, level: int) -> void: var tag_indent: String = indent.repeat(level) if children.is_empty(): target.append(tag_indent + _get_solid_text()) else: target.append(tag_indent + _get_opening_tag()) _dump_children(target, indent, level + 1) target.append(tag_indent + _get_closing_tag()) func get_elements(text: String) -> Array[XMLNodeElement]: var result: Array[XMLNodeElement] result.append_array(children.filter(func(n): return n is XMLNodeElement and n.text == text)) return result class XMLNodeRoot: extends XMLNodeParent func _init() -> void: super("") func dump_to_string(indent: String = " ", new_line: String = "\n") -> String: var target: PackedStringArray _dump_children(target, indent, 0) return new_line.join(target) func dump_to_buffer(indent: String = " ", new_line: String = "\n") -> PackedByteArray: return dump_to_string(indent, new_line).to_utf8_buffer() func dump_to_file(absolute_file_path: String, indent: String = " ", new_line: String = "\n") -> void: DirAccess.make_dir_recursive_absolute(absolute_file_path.get_base_dir()) var file: FileAccess = FileAccess.open(absolute_file_path, FileAccess.WRITE) file.store_string(dump_to_string(indent, new_line)) file.close() class XMLNodeElement: extends XMLNodeParent var attributes: Dictionary var closed: bool func _init(text: String, closed: bool = false) -> void: super(text) self.closed = closed func _get_attributes_string() -> String: return "".join(attributes.keys().map(func(k): return " %s=\"%s\"" % [k, attributes[k]])) func _get_opening_tag() -> String: return "<%s%s>" % [text, _get_attributes_string()] func _get_closing_tag() -> String:return "" % [text] func _get_solid_text() -> String: return "<%s%s/>" % [text, _get_attributes_string()] func get_string(attribute: String) -> String: return attributes[attribute] func get_int(attribute: String) -> int: return attributes[attribute].to_int() func get_int_encoded_hex_color(attribute: String, with_alpha: bool = false) -> Color: var arr: PackedByteArray arr.resize(4) arr.encode_u32(0, attributes[attribute].to_int()) if not with_alpha: arr.resize(3) return Color(arr.hex_encode()) func get_vector2i(attribute_x: String, attribute_y: String) -> Vector2i: return Vector2i(attributes[attribute_x].to_int(), attributes[attribute_y].to_int()) func get_rect2i(attribute_position_x: String, attribute_position_y: String, attribute_size_x: String, attribute_size_y: String) -> Rect2i: return Rect2i( attributes[attribute_position_x].to_int(), attributes[attribute_position_y].to_int(), attributes[attribute_size_x].to_int(), attributes[attribute_size_y].to_int()) func get_bool(attribute: String) -> bool: var raw_value: String = attributes[attribute] if raw_value.is_empty(): return false if raw_value.is_valid_int(): return bool(raw_value.to_int()) if raw_value.nocasecmp_to("True") == 0: return true if raw_value.nocasecmp_to("False") == 0: return false push_warning("Failed to parse bool value from string: \"%s\", returning false..." % [raw_value]) return false class XMLNodeText: extends XMLNode func _init(text: String) -> void: super(text) func _get_solid_text() -> String: return text.strip_edges() func _dump(target: PackedStringArray, indent: String, level: int) -> void: var text: String = _get_solid_text() if not text.is_empty(): target.append(indent.repeat(level) + text) class XMLNodeCData: extends XMLNode func _init(text: String) -> void: super(text) func _get_solid_text() -> String: return "" % [text] class XMLNodeComment: extends XMLNode func _init(text: String) -> void: super(text) func _get_solid_text() -> String: return "" % [text] class XMLNodeUnknown: extends XMLNode func _init(text: String) -> void: super(text) func _get_solid_text() -> String: return "<%s>" % [text] static func parse_file(path: String) -> XMLNodeRoot: var parser = XMLParser.new() parser.open(path) return __parse_xml(parser) static func parse_buffer(buffer: PackedByteArray) -> XMLNodeRoot: var parser = XMLParser.new() parser.open_buffer(buffer) return __parse_xml(parser) static func parse_string(xml_string: String) -> XMLNodeRoot: return parse_buffer(xml_string.to_utf8_buffer()) static func __parse_xml(parser: XMLParser) -> XMLNodeRoot: var root = XMLNodeRoot.new() var stack: Array[XMLNode] = [root] while parser.read() != ERR_FILE_EOF: match parser.get_node_type(): XMLParser.NODE_ELEMENT: var node: XMLNode = XMLNodeElement.new(parser.get_node_name()) for attr_idx in parser.get_attribute_count(): node.attributes[parser.get_attribute_name(attr_idx)] = \ parser.get_attribute_value(attr_idx) stack.back().children.push_back(node) if not parser.is_empty(): stack.push_back(node) XMLParser.NODE_ELEMENT_END: if stack.size() < 2: push_warning("Extra end tag found") else: stack.pop_back() XMLParser.NODE_TEXT: var text: String = parser.get_node_data().strip_edges() if not text.is_empty(): stack.back().children.push_back(XMLNodeText.new(text)) XMLParser.NODE_CDATA: stack.back().children.push_back(XMLNodeCData.new(parser.get_node_data())) XMLParser.NODE_NONE: push_error("Incorrect XML node found") XMLParser.NODE_UNKNOWN: stack.back().children.push_back(XMLNodeUnknown.new(parser.get_node_name())) XMLParser.NODE_COMMENT: stack.back().children.push_back(XMLNodeComment.new(parser.get_node_name())) return root