extends Node class XmlNode extends Reference: func is_element() -> bool: return false func is_text() -> bool: return false class XmlElement extends XmlNode: var node_name: String var attributes: Dictionary # String to String var children: Array # of XmlNode func is_element() -> bool: return true func take_named_child_element(p_node_name: String) -> XmlElement: for idx in range(self.children.size()): var child := self.children[idx] as XmlElement if child != null and child.node_name == p_node_name: self.children.remove(idx) return child return null func as_string(level: int = 0) -> String: var result = '\t'.repeat(level) + "XmlElement \"%s\"" % self.node_name if self.attributes.size() > 0: result += ", Attributes: " + String(self.attributes) for child in self.children: result += '\n' + '\t '.repeat(level) + child.as_string(level + 1) return result class XmlText extends XmlNode: var data: String func is_text() -> bool: return true func as_string(_level: int = 0) -> String: return "XmlText \"%s\"" % self.data ## Wrapper over XMLParser for TCP stream oriented XML data. # todo: Save namespaces. # todo: Ability to parse from partially arrived data, with saving for future resume. class Parser extends Reference: var _root: XmlElement = null var _element_stack: Array # of XmlElement var _pending: PoolByteArray func take_root() -> XmlElement: var result := self._root assert(result != null) self._root = null return result ## Returns true if root element is closed, otherwise more tags are expected to come later. func parse_a_bit(data): # -> bool or Error: var error: int var parser := XMLParser.new() if data is String: data = data.to_utf8() if data == null: data = PoolByteArray() var total = self._pending + data if total.size() == 0: return false error = parser.open_buffer(total) if error != OK: push_error("Error opening a buffer for XML parsing") return error self._pending = PoolByteArray() while parser.read() == OK: if parser.get_node_type() == XMLParser.NODE_ELEMENT: var element := XmlElement.new() element.node_name = parser.get_node_name() var attribute_count := parser.get_attribute_count() for idx in range(attribute_count): element.attributes[parser.get_attribute_name(idx)] = parser.get_attribute_value(idx) if self._root != null: self._element_stack[self._element_stack.size() - 1].children.push_back(element) else: self._root = element # todo: Handle _root empty element. if not parser.is_empty(): self._element_stack.push_back(element) elif parser.get_node_type() == XMLParser.NODE_ELEMENT_END: if self._element_stack.size() == 0: push_error("Closing element closes nothing" % [parser.get_node_name()]) return ERR_PARSE_ERROR var popped := self._element_stack.pop_back() as XmlElement if popped.node_name != parser.get_node_name(): push_error("Element <%s> closes sooner than <%s>" % [parser.get_node_name(), popped.node_name]) return ERR_PARSE_ERROR if self._element_stack.size() == 0: # todo: Handle partial data. if parser.read() == OK: self._pending = total.subarray(parser.get_node_offset(), -1) return true elif parser.get_node_type() == XMLParser.NODE_TEXT: if self._element_stack.size() == 0: push_error("Text node should be a child of an element") return ERR_PARSE_ERROR var text = XmlText.new() text.data = parser.get_node_data() self._element_stack[self._element_stack.size() - 1].children.push_back(text) elif parser.get_node_type() == XMLParser.NODE_UNKNOWN: ## Needed for things like version specification. pass else: push_error("Node type unimplemented") return self._element_stack.size() == 0