/
reader.cr
153 lines (131 loc) · 4.45 KB
/
reader.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
require "./libxml2"
require "./parser_options"
struct XML::Reader
# Creates a new reader from a string.
#
# See `XML::ParserOptions.default` for default options.
def initialize(str : String, options : XML::ParserOptions = XML::ParserOptions.default)
@reader = LibXML.xmlReaderForMemory(str, str.bytesize, nil, nil, options)
LibXML.xmlTextReaderSetErrorHandler @reader, ->(arg, msg, severity, locator) do
msg_str = String.new(msg).chomp
line_number = LibXML.xmlTextReaderLocatorLineNumber(locator)
raise Error.new(msg_str, line_number)
end
end
# Creates a new reader from an IO.
#
# See `XML::ParserOptions.default` for default options.
def initialize(io : IO, options : XML::ParserOptions = XML::ParserOptions.default)
@reader = LibXML.xmlReaderForIO(
->(context, buffer, length) { Box(IO).unbox(context).read(Slice.new(buffer, length)).to_i },
->(context) { Box(IO).unbox(context).close; 0 },
Box(IO).box(io),
nil,
nil,
options
)
end
# Moves the reader to the next node.
def read
LibXML.xmlTextReaderRead(@reader) == 1
end
# Moves the reader to the next node while skipping subtrees.
def next
LibXML.xmlTextReaderNext(@reader) == 1
end
# Moves the reader to the next sibling node while skipping subtrees.
def next_sibling
result = LibXML.xmlTextReaderNextSibling(@reader)
# Work around libxml2 with incomplete xmlTextReaderNextSibling()
# see: https://gitlab.gnome.org/GNOME/libxml2/issues/7
if result == -1
node = LibXML.xmlTextReaderCurrentNode(@reader)
if node.null?
LibXML.xmlTextReaderRead(@reader) == 1
elsif !node.value.next.null?
LibXML.xmlTextReaderNext(@reader) == 1
else
false
end
else
result == 1
end
end
# Returns the `XML::Type` of the node.
def node_type
LibXML.xmlTextReaderNodeType(@reader)
end
# Returns the name of the node.
def name
value = LibXML.xmlTextReaderConstName(@reader)
value ? String.new(value) : ""
end
# Checks if the node is an empty element.
def empty_element?
LibXML.xmlTextReaderIsEmptyElement(@reader) == 1
end
# Checks if the node has any attributes.
def has_attributes?
LibXML.xmlTextReaderHasAttributes(@reader) == 1
end
# Returns attribute count of the node.
def attributes_count
LibXML.xmlTextReaderAttributeCount(@reader)
end
# Moves to the first `XML::Type::ATTRIBUTE_NODE` of the node.
def move_to_first_attribute
LibXML.xmlTextReaderMoveToFirstAttribute(@reader) == 1
end
# Moves to the next `XML::Type::ATTRIBUTE_NODE` of the node.
def move_to_next_attribute
LibXML.xmlTextReaderMoveToNextAttribute(@reader) == 1
end
# Moves to the `XML::Type::ATTRIBUTE_NODE` with the specified name.
def move_to_attribute(name : String)
LibXML.xmlTextReaderMoveToAttribute(@reader, name) == 1
end
# Gets the attribute content for the *attribute* given by name.
# Raises `KeyError` if attribute is not found.
def [](attribute : String) : String
self[attribute]? || raise(KeyError.new("Missing attribute: #{attribute}"))
end
# Gets the attribute content for the *attribute* given by name.
# Returns `nil` if attribute is not found.
def []?(attribute : String) : String?
value = LibXML.xmlTextReaderGetAttribute(@reader, attribute)
String.new(value) if value
end
# Moves from the `XML::Type::ATTRIBUTE_NODE` to its containing `XML::Type::ELEMENT_NODE`.
def move_to_element
LibXML.xmlTextReaderMoveToElement(@reader) == 1
end
# Returns the current nesting depth of the reader.
def depth
LibXML.xmlTextReaderDepth(@reader)
end
# Returns the node's XML content including subtrees.
def read_inner_xml
xml = LibXML.xmlTextReaderReadInnerXml(@reader)
xml ? String.new(xml) : ""
end
# Returns the XML for the node and its content including subtrees.
def read_outer_xml
xml = LibXML.xmlTextReaderReadOuterXml(@reader)
xml ? String.new(xml) : ""
end
# Expand the node to a `XML::Node` that can be searched with XPath etc.
# The returned `XML::Node` is only valid until the next call to `#read`.
def expand
xml = LibXML.xmlTextReaderExpand(@reader)
XML::Node.new(xml) if xml
end
# Returns the text content of the node.
def value
value = LibXML.xmlTextReaderConstValue(@reader)
value ? String.new(value) : ""
end
# Returns a reference to the underlying `LibXML::XMLTextReader`.
def to_unsafe
@reader
end
end