forked from sparklemotion/nokogiri
/
node_set.rb
341 lines (296 loc) · 8.89 KB
/
node_set.rb
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
module Nokogiri
module XML
####
# A NodeSet contains a list of Nokogiri::XML::Node objects. Typically
# a NodeSet is return as a result of searching a Document via
# Nokogiri::XML::Searchable#css or Nokogiri::XML::Searchable#xpath
class NodeSet
include Nokogiri::XML::Searchable
include Enumerable
# The Document this NodeSet is associated with
attr_accessor :document
alias :clone :dup
# Create a NodeSet with +document+ defaulting to +list+
def initialize document, list = []
@document = document
document.decorate(self)
list.each { |x| self << x }
yield self if block_given?
end
###
# Get the first element of the NodeSet.
def first n = nil
return self[0] unless n
list = []
[n, length].min.times { |i| list << self[i] }
list
end
###
# Get the last element of the NodeSet.
def last
self[-1]
end
###
# Is this NodeSet empty?
def empty?
length == 0
end
###
# Returns the index of the first node in self that is == to +node+ or meets the given block. Returns nil if no match is found.
def index(node = nil)
if node
warn "given block not used" if block_given?
each_with_index { |member, j| return j if member == node }
elsif block_given?
each_with_index { |member, j| return j if yield(member) }
end
nil
end
###
# Insert +datum+ before the first Node in this NodeSet
def before datum
first.before datum
end
###
# Insert +datum+ after the last Node in this NodeSet
def after datum
last.after datum
end
alias :<< :push
alias :remove :unlink
###
# call-seq: css *rules, [namespace-bindings, custom-pseudo-class]
#
# Search this node set for CSS +rules+. +rules+ must be one or more CSS
# selectors. For example:
#
# For more information see Nokogiri::XML::Searchable#css
def css *args
rules, handler, ns, _ = extract_params(args)
paths = css_rules_to_xpath(rules, ns)
inject(NodeSet.new(document)) do |set, node|
set + xpath_internal(node, paths, handler, ns, nil)
end
end
###
# call-seq: xpath *paths, [namespace-bindings, variable-bindings, custom-handler-class]
#
# Search this node set for XPath +paths+. +paths+ must be one or more XPath
# queries.
#
# For more information see Nokogiri::XML::Searchable#xpath
def xpath *args
paths, handler, ns, binds = extract_params(args)
inject(NodeSet.new(document)) do |set, node|
set + xpath_internal(node, paths, handler, ns, binds)
end
end
###
# Search this NodeSet's nodes' immediate children using CSS selector +selector+
def > selector
ns = document.root.namespaces
xpath CSS.xpath_for(selector, :prefix => "./", :ns => ns).first
end
###
# call-seq: search *paths, [namespace-bindings, xpath-variable-bindings, custom-handler-class]
#
# Search this object for +paths+, and return only the first
# result. +paths+ must be one or more XPath or CSS queries.
#
# See Searchable#search for more information.
#
# Or, if passed an integer, index into the NodeSet:
#
# node_set.at(3) # same as node_set[3]
#
def at *args
if args.length == 1 && args.first.is_a?(Numeric)
return self[args.first]
end
super(*args)
end
alias :% :at
###
# Filter this list for nodes that match +expr+
def filter expr
find_all { |node| node.matches?(expr) }
end
###
# Add the class attribute +name+ to all Node objects in the
# NodeSet.
#
# See Nokogiri::XML::Node#add_class for more information.
def add_class name
each do |el|
el.add_class(name)
end
self
end
###
# Append the class attribute +name+ to all Node objects in the
# NodeSet.
#
# See Nokogiri::XML::Node#append_class for more information.
def append_class name
each do |el|
el.append_class(name)
end
self
end
###
# Remove the class attribute +name+ from all Node objects in the
# NodeSet.
#
# See Nokogiri::XML::Node#remove_class for more information.
def remove_class name = nil
each do |el|
el.remove_class(name)
end
self
end
###
# Set the attribute +key+ to +value+ or the return value of +blk+
# on all Node objects in the NodeSet.
def attr key, value = nil, &blk
unless Hash === key || key && (value || blk)
return first.attribute(key)
end
hash = key.is_a?(Hash) ? key : { key => value }
hash.each { |k,v| each { |el| el[k] = v || blk[el] } }
self
end
alias :set :attr
alias :attribute :attr
###
# Remove the attributed named +name+ from all Node objects in the NodeSet
def remove_attr name
each { |el| el.delete name }
self
end
alias remove_attribute remove_attr
###
# Iterate over each node, yielding to +block+
def each
return to_enum unless block_given?
0.upto(length - 1) do |x|
yield self[x]
end
end
###
# Get the inner text of all contained Node objects
#
# Note: This joins the text of all Node objects in the NodeSet:
#
# doc = Nokogiri::XML('<xml><a><d>foo</d><d>bar</d></a></xml>')
# doc.css('d').text # => "foobar"
#
# Instead, if you want to return the text of all nodes in the NodeSet:
#
# doc.css('d').map(&:text) # => ["foo", "bar"]
#
# See Nokogiri::XML::Node#content for more information.
def inner_text
collect(&:inner_text).join('')
end
alias :text :inner_text
###
# Get the inner html of all contained Node objects
def inner_html *args
collect{|j| j.inner_html(*args) }.join('')
end
###
# Wrap this NodeSet with +html+
def wrap(html)
each do |j|
new_parent = document.parse(html).first
j.add_next_sibling(new_parent)
new_parent.add_child(j)
end
self
end
###
# Convert this NodeSet to a string.
def to_s
map(&:to_s).join
end
###
# Convert this NodeSet to HTML
def to_html *args
if Nokogiri.jruby?
options = args.first.is_a?(Hash) ? args.shift : {}
if !options[:save_with]
options[:save_with] = Node::SaveOptions::NO_DECLARATION | Node::SaveOptions::NO_EMPTY_TAGS | Node::SaveOptions::AS_HTML
end
args.insert(0, options)
end
map { |x| x.to_html(*args) }.join
end
###
# Convert this NodeSet to XHTML
def to_xhtml *args
map { |x| x.to_xhtml(*args) }.join
end
###
# Convert this NodeSet to XML
def to_xml *args
map { |x| x.to_xml(*args) }.join
end
alias :size :length
alias :to_ary :to_a
###
# Removes the last element from set and returns it, or +nil+ if
# the set is empty
def pop
return nil if length == 0
delete last
end
###
# Returns the first element of the NodeSet and removes it. Returns
# +nil+ if the set is empty.
def shift
return nil if length == 0
delete first
end
###
# Equality -- Two NodeSets are equal if the contain the same number
# of elements and if each element is equal to the corresponding
# element in the other NodeSet
def == other
return false unless other.is_a?(Nokogiri::XML::NodeSet)
return false unless length == other.length
each_with_index do |node, i|
return false unless node == other[i]
end
true
end
###
# Returns a new NodeSet containing all the children of all the nodes in
# the NodeSet
def children
node_set = NodeSet.new(document)
each do |node|
node.children.each { |n| node_set.push(n) }
end
node_set
end
###
# Returns a new NodeSet containing all the nodes in the NodeSet
# in reverse order
def reverse
node_set = NodeSet.new(document)
(length - 1).downto(0) do |x|
node_set.push self[x]
end
node_set
end
###
# Return a nicely formated string representation
def inspect
"[#{map(&:inspect).join ', '}]"
end
alias :+ :|
# @private
IMPLIED_XPATH_CONTEXTS = [ './/'.freeze, 'self::'.freeze ].freeze # :nodoc:
end
end
end