Class: Itonoko::XML::Node
- Inherits:
-
Object
- Object
- Itonoko::XML::Node
show all
- Defined in:
- lib/itonoko/xml/node.rb
Constant Summary
collapse
- ELEMENT_NODE =
1
- ATTRIBUTE_NODE =
2
- TEXT_NODE =
3
- CDATA_SECTION_NODE =
4
- PROCESSING_INSTRUCTION_NODE =
7
8
- DOCUMENT_NODE =
9
- DOCUMENT_TYPE_NODE =
10
- DOCUMENT_FRAGMENT_NODE =
11
- EMPTY_ATTRS =
Shared frozen constants — leaf nodes use these to avoid per-node allocation.
{}.freeze
- EMPTY_CHILDREN =
[].freeze
Instance Attribute Summary collapse
Instance Method Summary
collapse
Constructor Details
#initialize(node_type, node_name, document = nil) ⇒ Node
Returns a new instance of Node.
25
26
27
28
29
30
31
32
|
# File 'lib/itonoko/xml/node.rb', line 25
def initialize(node_type, node_name, document = nil)
@node_type = node_type
@node_name = node_name
@document = document
@parent = nil
@children = []
@attributes = EMPTY_ATTRS end
|
Instance Attribute Details
#children ⇒ Object
Returns the value of attribute children.
23
24
25
|
# File 'lib/itonoko/xml/node.rb', line 23
def children
@children
end
|
#document ⇒ Object
Returns the value of attribute document.
22
23
24
|
# File 'lib/itonoko/xml/node.rb', line 22
def document
@document
end
|
#node_name ⇒ Object
Returns the value of attribute node_name.
23
24
25
|
# File 'lib/itonoko/xml/node.rb', line 23
def node_name
@node_name
end
|
#node_type ⇒ Object
Returns the value of attribute node_type.
23
24
25
|
# File 'lib/itonoko/xml/node.rb', line 23
def node_type
@node_type
end
|
#parent ⇒ Object
Returns the value of attribute parent.
22
23
24
|
# File 'lib/itonoko/xml/node.rb', line 22
def parent
@parent
end
|
Instance Method Details
#==(other) ⇒ Object
340
341
342
|
# File 'lib/itonoko/xml/node.rb', line 340
def ==(other)
equal?(other)
end
|
#[](attr_name) ⇒ Object
90
91
92
|
# File 'lib/itonoko/xml/node.rb', line 90
def [](attr_name)
@attributes[attr_name.to_s]
end
|
#[]=(attr_name, value) ⇒ Object
94
95
96
97
98
99
100
|
# File 'lib/itonoko/xml/node.rb', line 94
def []=(attr_name, value)
if @attributes.frozen?
@attributes = { attr_name.to_s => value.to_s }
else
@attributes[attr_name.to_s] = value.to_s
end
end
|
#_collect_text(buf) ⇒ Object
Override in subclasses for leaf nodes.
152
153
154
|
# File 'lib/itonoko/xml/node.rb', line 152
def _collect_text(buf)
@children.each { |c| c._collect_text(buf) }
end
|
#add_child(node_or_markup) ⇒ Object
Also known as:
<<
── tree manipulation ─────────────────────────────────────────
172
173
174
175
176
177
178
179
180
181
|
# File 'lib/itonoko/xml/node.rb', line 172
def add_child(node_or_markup)
nodes = coerce_nodes(node_or_markup)
nodes.each do |node|
node.parent&.children&.delete(node)
node.parent = self
node.document = document
@children << node
end
nodes.length == 1 ? nodes.first : NodeSet.new(document, nodes)
end
|
#add_next_sibling(node_or_markup) ⇒ Object
Also known as:
after
195
196
197
198
199
200
201
202
203
204
205
206
|
# File 'lib/itonoko/xml/node.rb', line 195
def add_next_sibling(node_or_markup)
raise "no parent" unless parent
nodes = coerce_nodes(node_or_markup)
idx = parent.children.index(self) + 1
nodes.each_with_index do |node, i|
node.parent&.children&.delete(node)
node.parent = parent
node.document = document
parent.children.insert(idx + i, node)
end
nodes.length == 1 ? nodes.first : NodeSet.new(document, nodes)
end
|
#add_previous_sibling(node_or_markup) ⇒ Object
Also known as:
before
209
210
211
212
213
214
215
216
217
218
219
220
|
# File 'lib/itonoko/xml/node.rb', line 209
def add_previous_sibling(node_or_markup)
raise "no parent" unless parent
nodes = coerce_nodes(node_or_markup)
idx = parent.children.index(self)
nodes.each_with_index do |node, i|
node.parent&.children&.delete(node)
node.parent = parent
node.document = document
parent.children.insert(idx + i, node)
end
nodes.length == 1 ? nodes.first : NodeSet.new(document, nodes)
end
|
#ancestors(selector = nil) ⇒ Object
65
66
67
68
69
70
71
72
73
74
|
# File 'lib/itonoko/xml/node.rb', line 65
def ancestors(selector = nil)
result = []
node = parent
while node
result << node if node.node_type != DOCUMENT_NODE
node = node.parent
end
list = NodeSet.new(document, result)
selector ? list.select { |n| n.matches_css?(selector) } : list
end
|
#append_child(node) ⇒ Object
── fast path for parsers (skips parent-removal + coercion) ───
163
164
165
166
167
168
|
# File 'lib/itonoko/xml/node.rb', line 163
def append_child(node)
node.parent = self
node.document = @document
@children << node
node
end
|
#at(*queries) ⇒ Object
276
277
278
|
# File 'lib/itonoko/xml/node.rb', line 276
def at(*queries)
search(*queries).first
end
|
#at_css(selector) ⇒ Object
264
265
266
|
# File 'lib/itonoko/xml/node.rb', line 264
def at_css(selector)
css(selector).first
end
|
#at_xpath(expr, namespaces = {}) ⇒ Object
268
269
270
|
# File 'lib/itonoko/xml/node.rb', line 268
def at_xpath(expr, namespaces = {})
xpath(expr, namespaces).first
end
|
#attribute(name) ⇒ Object
123
124
125
126
127
|
# File 'lib/itonoko/xml/node.rb', line 123
def attribute(name)
val = @attributes[name.to_s]
return nil unless val
Attr.new(name.to_s, val, document)
end
|
#attribute_nodes ⇒ Object
136
137
138
|
# File 'lib/itonoko/xml/node.rb', line 136
def attribute_nodes
@attributes.map { |k, v| Attr.new(k, v, document) }
end
|
#attributes ⇒ Object
129
130
131
132
133
134
|
# File 'lib/itonoko/xml/node.rb', line 129
def attributes
@attributes.each_with_object({}) do |(k, v), h|
a = Attr.new(k, v, document)
h[k] = a
end
end
|
#cdata_node? ⇒ Boolean
356
357
358
|
# File 'lib/itonoko/xml/node.rb', line 356
def cdata_node?
node_type == CDATA_SECTION_NODE
end
|
#child ⇒ Object
80
81
82
|
# File 'lib/itonoko/xml/node.rb', line 80
def child
children.first
end
|
352
353
354
|
# File 'lib/itonoko/xml/node.rb', line 352
def
node_type == COMMENT_NODE
end
|
#css(selector) ⇒ Object
── search ────────────────────────────────────────────────────
254
255
256
257
|
# File 'lib/itonoko/xml/node.rb', line 254
def css(selector)
require_relative "../css/matcher"
CSS::Matcher.match(self, selector)
end
|
#description ⇒ Object
368
369
370
|
# File 'lib/itonoko/xml/node.rb', line 368
def description
node_name
end
|
#document? ⇒ Boolean
364
365
366
|
# File 'lib/itonoko/xml/node.rb', line 364
def document?
node_type == DOCUMENT_NODE
end
|
#element? ⇒ Boolean
344
345
346
|
# File 'lib/itonoko/xml/node.rb', line 344
def element?
node_type == ELEMENT_NODE
end
|
#element_children ⇒ Object
76
77
78
|
# File 'lib/itonoko/xml/node.rb', line 76
def element_children
NodeSet.new(document, children.select { |c| c.node_type == ELEMENT_NODE })
end
|
#get_attribute(name) ⇒ Object
102
103
104
|
# File 'lib/itonoko/xml/node.rb', line 102
def get_attribute(name)
@attributes[name.to_s]
end
|
#has_attribute?(name) ⇒ Boolean
115
116
117
|
# File 'lib/itonoko/xml/node.rb', line 115
def has_attribute?(name)
@attributes.key?(name.to_s)
end
|
#inner_html ⇒ Object
String concat instead of map+join — one less intermediate Array.
287
288
289
290
291
|
# File 'lib/itonoko/xml/node.rb', line 287
def inner_html
buf = +""
@children.each { |c| buf << c.to_html }
buf
end
|
#inner_html=(markup) ⇒ Object
#inspect ⇒ Object
336
337
338
|
# File 'lib/itonoko/xml/node.rb', line 336
def inspect
"#<#{self.class} name=#{node_name.inspect} children=#{children.length}>"
end
|
#keys ⇒ Object
119
120
121
|
# File 'lib/itonoko/xml/node.rb', line 119
def keys
@attributes.keys
end
|
#matches?(selector) ⇒ Boolean
#name ⇒ Object
── attributes ────────────────────────────────────────────────
86
87
88
|
# File 'lib/itonoko/xml/node.rb', line 86
def name
node_name
end
|
#next_element ⇒ Object
53
54
55
56
57
|
# File 'lib/itonoko/xml/node.rb', line 53
def next_element
sib = next_sibling
sib = sib.next_sibling while sib && sib.node_type != ELEMENT_NODE
sib
end
|
#next_sibling ⇒ Object
41
42
43
44
45
|
# File 'lib/itonoko/xml/node.rb', line 41
def next_sibling
return nil unless parent
idx = parent.children.index(self)
idx && parent.children[idx + 1]
end
|
#prepend_child(node_or_markup) ⇒ Object
184
185
186
187
188
189
190
191
192
193
|
# File 'lib/itonoko/xml/node.rb', line 184
def prepend_child(node_or_markup)
nodes = coerce_nodes(node_or_markup)
nodes.reverse_each do |node|
node.parent&.children&.delete(node)
node.parent = self
node.document = document
@children.unshift(node)
end
nodes.length == 1 ? nodes.first : NodeSet.new(document, nodes)
end
|
#previous_element ⇒ Object
59
60
61
62
63
|
# File 'lib/itonoko/xml/node.rb', line 59
def previous_element
sib = previous_sibling
sib = sib.previous_sibling while sib && sib.node_type != ELEMENT_NODE
sib
end
|
#previous_sibling ⇒ Object
47
48
49
50
51
|
# File 'lib/itonoko/xml/node.rb', line 47
def previous_sibling
return nil unless parent
idx = parent.children.index(self)
idx && idx > 0 ? parent.children[idx - 1] : nil
end
|
#remove ⇒ Object
Also known as:
unlink
223
224
225
226
227
|
# File 'lib/itonoko/xml/node.rb', line 223
def remove
parent&.children&.delete(self)
@parent = nil
self
end
|
#remove_attribute(name) ⇒ Object
110
111
112
113
|
# File 'lib/itonoko/xml/node.rb', line 110
def remove_attribute(name)
return if @attributes.frozen?
@attributes.delete(name.to_s)
end
|
#replace(node_or_markup) ⇒ Object
230
231
232
233
234
235
236
237
238
239
240
241
242
|
# File 'lib/itonoko/xml/node.rb', line 230
def replace(node_or_markup)
raise "no parent" unless parent
nodes = coerce_nodes(node_or_markup)
idx = parent.children.index(self)
parent.children.delete_at(idx)
nodes.reverse_each do |node|
node.parent = parent
node.document = document
parent.children.insert(idx, node)
end
@parent = nil
nodes.length == 1 ? nodes.first : NodeSet.new(document, nodes)
end
|
#root ⇒ Object
── navigation ────────────────────────────────────────────────
36
37
38
39
|
# File 'lib/itonoko/xml/node.rb', line 36
def root
return self if parent.nil? || parent.node_type == DOCUMENT_NODE
parent.root
end
|
#search(*queries) ⇒ Object
272
273
274
|
# File 'lib/itonoko/xml/node.rb', line 272
def search(*queries)
NodeSet.new(document, queries.flat_map { |q| css(q).to_a })
end
|
#set_attribute(name, value) ⇒ Object
106
107
108
|
# File 'lib/itonoko/xml/node.rb', line 106
def set_attribute(name, value)
self[name] = value
end
|
#text ⇒ Object
Also known as:
content, inner_text
Accumulator-based text extraction — avoids intermediate arrays and join.
143
144
145
146
147
|
# File 'lib/itonoko/xml/node.rb', line 143
def text
buf = +""
_collect_text(buf)
buf
end
|
#text=(str) ⇒ Object
Also known as:
content=
156
157
158
|
# File 'lib/itonoko/xml/node.rb', line 156
def text=(str)
@children = [Text.new(str.to_s, document).tap { |t| t.parent = self }]
end
|
#text? ⇒ Boolean
348
349
350
|
# File 'lib/itonoko/xml/node.rb', line 348
def text?
node_type == TEXT_NODE
end
|
#to_s ⇒ Object
332
333
334
|
# File 'lib/itonoko/xml/node.rb', line 332
def to_s
to_html
end
|
#to_xml(options = {}) ⇒ Object
#xpath(expr, namespaces = {}) ⇒ Object
259
260
261
262
|
# File 'lib/itonoko/xml/node.rb', line 259
def xpath(expr, namespaces = {})
require_relative "../xpath/evaluator"
XPath::Evaluator.new(self, namespaces).evaluate(expr)
end
|