Class: Itonoko::XML::Node

Inherits:
Object
  • Object
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
COMMENT_NODE =
8
DOCUMENT_NODE =
9
DOCUMENT_TYPE_NODE =
10
DOCUMENT_FRAGMENT_NODE =
11
ESCAPE_TEXT =
{ "&" => "&amp;", "<" => "&lt;", ">" => "&gt;" }.freeze
ESCAPE_ATTR =
{ "&" => "&amp;", "<" => "&lt;", ">" => "&gt;", '"' => "&quot;" }.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.



22
23
24
25
26
27
28
29
# File 'lib/itonoko/xml/node.rb', line 22

def initialize(node_type, node_name, document = nil)
  @node_type  = node_type
  @node_name  = node_name
  @document   = document
  @parent     = nil
  @children   = []
  @attributes = {}
end

Instance Attribute Details

#childrenObject (readonly)

Returns the value of attribute children.



20
21
22
# File 'lib/itonoko/xml/node.rb', line 20

def children
  @children
end

#documentObject

Returns the value of attribute document.



19
20
21
# File 'lib/itonoko/xml/node.rb', line 19

def document
  @document
end

#node_nameObject (readonly)

Returns the value of attribute node_name.



20
21
22
# File 'lib/itonoko/xml/node.rb', line 20

def node_name
  @node_name
end

#node_typeObject (readonly)

Returns the value of attribute node_type.



20
21
22
# File 'lib/itonoko/xml/node.rb', line 20

def node_type
  @node_type
end

#parentObject

Returns the value of attribute parent.



19
20
21
# File 'lib/itonoko/xml/node.rb', line 19

def parent
  @parent
end

Instance Method Details

#==(other) ⇒ Object



311
312
313
# File 'lib/itonoko/xml/node.rb', line 311

def ==(other)
  equal?(other)
end

#[](attr_name) ⇒ Object



87
88
89
# File 'lib/itonoko/xml/node.rb', line 87

def [](attr_name)
  @attributes[attr_name.to_s]
end

#[]=(attr_name, value) ⇒ Object



91
92
93
# File 'lib/itonoko/xml/node.rb', line 91

def []=(attr_name, value)
  @attributes[attr_name.to_s] = value.to_s
end

#add_child(node_or_markup) ⇒ Object Also known as: <<

── tree manipulation ─────────────────────────────────────────



148
149
150
151
152
153
154
155
156
157
# File 'lib/itonoko/xml/node.rb', line 148

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



171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/itonoko/xml/node.rb', line 171

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



185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/itonoko/xml/node.rb', line 185

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



62
63
64
65
66
67
68
69
70
71
# File 'lib/itonoko/xml/node.rb', line 62

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

#at(*queries) ⇒ Object



252
253
254
# File 'lib/itonoko/xml/node.rb', line 252

def at(*queries)
  search(*queries).first
end

#at_css(selector) ⇒ Object



240
241
242
# File 'lib/itonoko/xml/node.rb', line 240

def at_css(selector)
  css(selector).first
end

#at_xpath(expr, namespaces = {}) ⇒ Object



244
245
246
# File 'lib/itonoko/xml/node.rb', line 244

def at_xpath(expr, namespaces = {})
  xpath(expr, namespaces).first
end

#attribute(name) ⇒ Object



115
116
117
118
119
# File 'lib/itonoko/xml/node.rb', line 115

def attribute(name)
  val = @attributes[name.to_s]
  return nil unless val
  Attr.new(name.to_s, val, document)
end

#attribute_nodesObject



128
129
130
# File 'lib/itonoko/xml/node.rb', line 128

def attribute_nodes
  @attributes.map { |k, v| Attr.new(k, v, document) }
end

#attributesObject



121
122
123
124
125
126
# File 'lib/itonoko/xml/node.rb', line 121

def attributes
  @attributes.transform_values { |v| Attr.new(nil, v, document) }
             .tap do |h|
               @attributes.each_key { |k| h[k].name = k }
             end
end

#cdata_node?Boolean

Returns:

  • (Boolean)


327
328
329
# File 'lib/itonoko/xml/node.rb', line 327

def cdata_node?
  node_type == CDATA_SECTION_NODE
end

#childObject



77
78
79
# File 'lib/itonoko/xml/node.rb', line 77

def child
  children.first
end

#comment?Boolean

Returns:

  • (Boolean)


323
324
325
# File 'lib/itonoko/xml/node.rb', line 323

def comment?
  node_type == COMMENT_NODE
end

#css(selector) ⇒ Object

── search ────────────────────────────────────────────────────



230
231
232
233
# File 'lib/itonoko/xml/node.rb', line 230

def css(selector)
  require_relative "../css/matcher"
  CSS::Matcher.match(self, selector)
end

#descriptionObject



339
340
341
# File 'lib/itonoko/xml/node.rb', line 339

def description
  node_name
end

#document?Boolean

Returns:

  • (Boolean)


335
336
337
# File 'lib/itonoko/xml/node.rb', line 335

def document?
  node_type == DOCUMENT_NODE
end

#element?Boolean

Returns:

  • (Boolean)


315
316
317
# File 'lib/itonoko/xml/node.rb', line 315

def element?
  node_type == ELEMENT_NODE
end

#element_childrenObject



73
74
75
# File 'lib/itonoko/xml/node.rb', line 73

def element_children
  NodeSet.new(document, children.select { |c| c.node_type == ELEMENT_NODE })
end

#fragment?Boolean

Returns:

  • (Boolean)


331
332
333
# File 'lib/itonoko/xml/node.rb', line 331

def fragment?
  node_type == DOCUMENT_FRAGMENT_NODE
end

#get_attribute(name) ⇒ Object



95
96
97
# File 'lib/itonoko/xml/node.rb', line 95

def get_attribute(name)
  @attributes[name.to_s]
end

#has_attribute?(name) ⇒ Boolean

Returns:

  • (Boolean)


107
108
109
# File 'lib/itonoko/xml/node.rb', line 107

def has_attribute?(name)
  @attributes.key?(name.to_s)
end

#inner_htmlObject

── serialization ─────────────────────────────────────────────



262
263
264
# File 'lib/itonoko/xml/node.rb', line 262

def inner_html
  children.map { |c| c.to_html }.join
end

#inner_html=(markup) ⇒ Object



220
221
222
223
224
225
226
# File 'lib/itonoko/xml/node.rb', line 220

def inner_html=(markup)
  @children = []
  frag = document.is_a?(HTML::Document) ?
         HTML::DocumentFragment.parse(markup, document) :
         XML::DocumentFragment.parse(markup, document)
  frag.children.each { |c| add_child(c) }
end

#inspectObject



307
308
309
# File 'lib/itonoko/xml/node.rb', line 307

def inspect
  "#<#{self.class} name=#{node_name.inspect} children=#{children.length}>"
end

#keysObject



111
112
113
# File 'lib/itonoko/xml/node.rb', line 111

def keys
  @attributes.keys
end

#matches?(selector) ⇒ Boolean

Returns:

  • (Boolean)


256
257
258
# File 'lib/itonoko/xml/node.rb', line 256

def matches?(selector)
  CSS::Matcher.matches_selector?(self, selector)
end

#nameObject

── attributes ────────────────────────────────────────────────



83
84
85
# File 'lib/itonoko/xml/node.rb', line 83

def name
  node_name
end

#next_elementObject



50
51
52
53
54
# File 'lib/itonoko/xml/node.rb', line 50

def next_element
  sib = next_sibling
  sib = sib.next_sibling while sib && sib.node_type != ELEMENT_NODE
  sib
end

#next_siblingObject



38
39
40
41
42
# File 'lib/itonoko/xml/node.rb', line 38

def next_sibling
  return nil unless parent
  idx = parent.children.index(self)
  idx && parent.children[idx + 1]
end

#prepend_child(node_or_markup) ⇒ Object



160
161
162
163
164
165
166
167
168
169
# File 'lib/itonoko/xml/node.rb', line 160

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_elementObject



56
57
58
59
60
# File 'lib/itonoko/xml/node.rb', line 56

def previous_element
  sib = previous_sibling
  sib = sib.previous_sibling while sib && sib.node_type != ELEMENT_NODE
  sib
end

#previous_siblingObject



44
45
46
47
48
# File 'lib/itonoko/xml/node.rb', line 44

def previous_sibling
  return nil unless parent
  idx = parent.children.index(self)
  idx && idx > 0 ? parent.children[idx - 1] : nil
end

#removeObject Also known as: unlink



199
200
201
202
203
# File 'lib/itonoko/xml/node.rb', line 199

def remove
  parent&.children&.delete(self)
  @parent = nil
  self
end

#remove_attribute(name) ⇒ Object



103
104
105
# File 'lib/itonoko/xml/node.rb', line 103

def remove_attribute(name)
  @attributes.delete(name.to_s)
end

#replace(node_or_markup) ⇒ Object



206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/itonoko/xml/node.rb', line 206

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

#rootObject

── navigation ────────────────────────────────────────────────



33
34
35
36
# File 'lib/itonoko/xml/node.rb', line 33

def root
  return self if parent.nil? || parent.node_type == DOCUMENT_NODE
  parent.root
end

#search(*queries) ⇒ Object



248
249
250
# File 'lib/itonoko/xml/node.rb', line 248

def search(*queries)
  NodeSet.new(document, queries.flat_map { |q| css(q).to_a })
end

#set_attribute(name, value) ⇒ Object



99
100
101
# File 'lib/itonoko/xml/node.rb', line 99

def set_attribute(name, value)
  @attributes[name.to_s] = value.to_s
end

#textObject Also known as: content, inner_text

── content ───────────────────────────────────────────────────



134
135
136
# File 'lib/itonoko/xml/node.rb', line 134

def text
  children.map(&:text).join
end

#text=(str) ⇒ Object Also known as: content=



140
141
142
143
# File 'lib/itonoko/xml/node.rb', line 140

def text=(str)
  @children = [Text.new(str.to_s, document)]
  @children.first.parent = self
end

#text?Boolean

Returns:

  • (Boolean)


319
320
321
# File 'lib/itonoko/xml/node.rb', line 319

def text?
  node_type == TEXT_NODE
end

#to_htmlObject



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/itonoko/xml/node.rb', line 266

def to_html
  html_mode = document.nil? || document.is_a?(HTML::Document)
  case node_type
  when ELEMENT_NODE
    serialize_element(html_mode)
  when TEXT_NODE, CDATA_SECTION_NODE
    escape_text(node_name)
  when COMMENT_NODE
    "<!--#{node_name}-->"
  when PROCESSING_INSTRUCTION_NODE
    "<?#{node_name}?>"
  when DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE
    children.map { |c| c.to_html }.join
  else
    ""
  end
end

#to_sObject



303
304
305
# File 'lib/itonoko/xml/node.rb', line 303

def to_s
  to_html
end

#to_xml(options = {}) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/itonoko/xml/node.rb', line 284

def to_xml(options = {})
  case node_type
  when ELEMENT_NODE
    serialize_element(false)
  when TEXT_NODE
    escape_text(node_name)
  when CDATA_SECTION_NODE
    "<![CDATA[#{node_name}]]>"
  when COMMENT_NODE
    "<!--#{node_name}-->"
  when PROCESSING_INSTRUCTION_NODE
    "<?#{node_name}?>"
  when DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE
    children.map { |c| c.to_xml }.join
  else
    ""
  end
end

#xpath(expr, namespaces = {}) ⇒ Object



235
236
237
238
# File 'lib/itonoko/xml/node.rb', line 235

def xpath(expr, namespaces = {})
  require_relative "../xpath/evaluator"
  XPath::Evaluator.new(self, namespaces).evaluate(expr)
end