119 lines
4.4 KiB
GDScript
119 lines
4.4 KiB
GDScript
extends Node
|
|
class_name Xml
|
|
|
|
class XmlNode extends Reference:
|
|
func is_element() -> bool: return false
|
|
func is_text() -> bool: return false
|
|
|
|
class XmlElement extends XmlNode:
|
|
var 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_name: String) -> XmlElement:
|
|
for idx in range(self.children.size()):
|
|
var child := self.children[idx] as XmlElement
|
|
if child != null and child.name == p_name:
|
|
self.children.remove(idx)
|
|
return child
|
|
return null
|
|
|
|
func as_string(level: int = 0) -> String:
|
|
var result = '\t'.repeat(level) + "XmlElement \"%s\"" % self.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.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 </%s> closes nothing" % [parser.get_node_name()])
|
|
return ERR_PARSE_ERROR
|
|
|
|
var popped := self._element_stack.pop_back() as XmlElement
|
|
if popped.name != parser.get_node_name():
|
|
push_error("Element <%s> closes sooner than <%s>" % [parser.get_node_name(), popped.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 <?xml ?> version specification.
|
|
pass
|
|
|
|
else:
|
|
push_error("Node type unimplemented")
|
|
|
|
return self._element_stack.size() == 0
|