nokolexbor 0.3.3-x86_64-darwin → 0.3.5-x86_64-darwin

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cead1d5973dbabeb2bac9e3e413d21e7dbe4e36e42e58fdf22ad110e02f3decf
4
- data.tar.gz: 443c22b64508e785863c7e552ead72cc00f71369b216f6ea38d2e29d10bb4f8a
3
+ metadata.gz: 2a0c4d52b626f7d8567f4efc3dcf3ad66f89b01b54070c1231e4d6f1f48d135d
4
+ data.tar.gz: 4fb65cf148b777ef42d5d7e2a06b3d578b59194cbc8d49bc904a8cfef4c491a3
5
5
  SHA512:
6
- metadata.gz: 82efd32a9058e2e3ee63e1215dddfa3bb24fbe11a81d50f795738d10406b0d09eed48d00e301aab44796124150e7cf3d6cfd11295d833f0edfda3b21a50a5bb5
7
- data.tar.gz: abce68297cb6cdfd144291d1ad40b8c1c5d16b512400890e5405eedf16976e3d8434a79838cab2690ebb54763f3770c996c1c4b5926b5196efb1ed25f3116524
6
+ metadata.gz: 912239a30461c910936f8182c6e3f580065c86a6362b06726214201d9dce59f6879b8fee4c95e24776190fdf4a0ef99234ab4b843587cec735dbfbbae2aa4f1d
7
+ data.tar.gz: cb85061c416597510856bd18404224b6931c5c63bc483b030ce3c92fd95584120dc5c08f0422b1335b5e9a9de94236973aa2a5cd7bfc5841147ac9422356231c
Binary file
Binary file
Binary file
Binary file
@@ -2,6 +2,33 @@
2
2
 
3
3
  module Nokolexbor
4
4
  class Document < Nokolexbor::Node
5
+ # Create an {Element} with +name+ belonging to this document, optionally setting contents or
6
+ # attributes.
7
+ #
8
+ # @param name [String]
9
+ # @param contents_or_attrs [#to_s, Hash]
10
+ #
11
+ # @return [Element]
12
+ #
13
+ # @example An empty element without attributes
14
+ # doc.create_element("div")
15
+ # # => <div></div>
16
+ #
17
+ # @example An element with contents
18
+ # doc.create_element("div", "contents")
19
+ # # => <div>contents</div>
20
+ #
21
+ # @example An element with attributes
22
+ # doc.create_element("div", {"class" => "container"})
23
+ # # => <div class='container'></div>
24
+ #
25
+ # @example An element with contents and attributes
26
+ # doc.create_element("div", "contents", {"class" => "container"})
27
+ # # => <div class='container'>contents</div>
28
+ #
29
+ # @example Passing a block to mutate the element
30
+ # doc.create_element("div") { |node| node["class"] = "blue" }
31
+ # # => <div class='blue'></div>
5
32
  def create_element(name, *contents_or_attrs, &block)
6
33
  elm = Nokolexbor::Element.new(name, self, &block)
7
34
  contents_or_attrs.each do |arg|
@@ -11,32 +38,43 @@ module Nokolexbor
11
38
  elm[k.to_s] = v.to_s
12
39
  end
13
40
  else
14
- elm.content = arg
41
+ elm.content = arg.to_s
15
42
  end
16
43
  end
17
44
  elm
18
45
  end
19
46
 
20
- # Create a Text Node with +string+
47
+ # Create a {Text} with +string+.
48
+ #
49
+ # @return [Text]
21
50
  def create_text_node(string, &block)
22
51
  Nokolexbor::Text.new(string.to_s, self, &block)
23
52
  end
24
53
 
25
- # Create a CDATA Node containing +string+
54
+ # Create a {CDATA} containing +string+.
55
+ #
56
+ # @return [CDATA]
26
57
  def create_cdata(string, &block)
27
58
  Nokolexbor::CDATA.new(string.to_s, self, &block)
28
59
  end
29
60
 
30
- # Create a Comment Node containing +string+
61
+ # Create a {Comment} containing +string+.
62
+ #
63
+ # @return [Comment]
31
64
  def create_comment(string, &block)
32
65
  Nokolexbor::Comment.new(string.to_s, self, &block)
33
66
  end
34
67
 
35
- # A reference to +self+
68
+ # A reference to +self+.
69
+ #
70
+ # @return [Document]
36
71
  def document
37
72
  self
38
73
  end
39
74
 
75
+ # Get the meta tag encoding for this document. If there is no meta tag, nil is returned.
76
+ #
77
+ # @return [String]
40
78
  def meta_encoding
41
79
  if (meta = at_css("meta[charset]"))
42
80
  meta[:charset]
@@ -45,6 +83,15 @@ module Nokolexbor
45
83
  end
46
84
  end
47
85
 
86
+ # Set the meta tag encoding for this document.
87
+ #
88
+ # If an meta encoding tag is already present, its content is
89
+ # replaced with the given text.
90
+ #
91
+ # Otherwise, this method tries to create one at an appropriate
92
+ # place supplying head and/or html elements as necessary, which
93
+ # is inside a head element if any, and before any text node or
94
+ # content element (typically <body>) if any.
48
95
  def meta_encoding=(encoding)
49
96
  if (meta = meta_content_type)
50
97
  meta["content"] = format("text/html; charset=%s", encoding)
@@ -2,10 +2,17 @@
2
2
 
3
3
  module Nokolexbor
4
4
  class DocumentFragment < Nokolexbor::Node
5
+ # Create a {DocumentFragment} from +tags+.
6
+ #
7
+ # @return [DocumentFragment]
5
8
  def self.parse(tags)
6
9
  new(Nokolexbor::Document.new, tags, nil)
7
10
  end
8
11
 
12
+ # Create a new {DocumentFragment} from +tags+.
13
+ #
14
+ # If +ctx+ is present, it is used as a context node for the
15
+ # subtree created.
9
16
  def initialize(document, tags = nil, ctx = nil)
10
17
  return self unless tags
11
18
 
@@ -15,6 +22,7 @@ module Nokolexbor
15
22
  nil
16
23
  end
17
24
 
25
+ # @return [String] The name of {DocumentFragment}
18
26
  def name
19
27
  "#document-fragment"
20
28
  end
@@ -24,6 +32,9 @@ module Nokolexbor
24
32
  alias_method :to_s, :outer_html
25
33
  alias_method :serialize, :outer_html
26
34
 
35
+ # Create a {DocumentFragment} from +data+.
36
+ #
37
+ # @return [DocumentFragment]
27
38
  def fragment(data)
28
39
  document.fragment(data)
29
40
  end
@@ -17,38 +17,51 @@ module Nokolexbor
17
17
  DOCUMENT_FRAG_NODE = 11
18
18
  NOTATION_NODE = 12
19
19
 
20
+ # @return [Document] The associated {Document} of this node
20
21
  attr_reader :document
21
22
 
22
23
  LOOKS_LIKE_XPATH = %r{^(\./|/|\.\.|\.$)}
23
24
 
25
+ # @return true if this is a {Comment}
24
26
  def comment?
25
27
  type == COMMENT_NODE
26
28
  end
27
29
 
30
+ # @return true if this is a {CDATA}
28
31
  def cdata?
29
32
  type == CDATA_SECTION_NODE
30
33
  end
31
34
 
35
+ # @return true if this is a {ProcessingInstruction}
32
36
  def processing_instruction?
33
37
  type == PI_NODE
34
38
  end
35
39
 
40
+ # @return true if this is a {Text}
36
41
  def text?
37
42
  type == TEXT_NODE
38
43
  end
39
44
 
45
+ # @return true if this is a {DocumentFragment}
40
46
  def fragment?
41
47
  type == DOCUMENT_FRAG_NODE
42
48
  end
43
49
 
50
+ # @return true if this is an {Element}
44
51
  def element?
45
52
  type == ELEMENT_NODE
46
53
  end
47
54
 
55
+ # @return true if this is a {Document}
48
56
  def document?
49
57
  is_a?(Nokolexbor::Document)
50
58
  end
51
59
 
60
+ # Get a list of ancestor Node of this Node
61
+ #
62
+ # @param [String, nil] selector The selector to match ancestors
63
+ #
64
+ # @return [NodeSet] A set of matched ancestor nodes
52
65
  def ancestors(selector = nil)
53
66
  return NodeSet.new(@document) unless respond_to?(:parent)
54
67
  return NodeSet.new(@document) unless parent
@@ -71,10 +84,39 @@ module Nokolexbor
71
84
  end)
72
85
  end
73
86
 
87
+ # Wrap this Node with another node.
88
+ #
89
+ # @param node [String, Node] A string or a node
90
+ # - when {String}:
91
+ # The markup that is parsed and used as the wrapper. If the parsed
92
+ # fragment has multiple roots, the first root node is used as the wrapper.
93
+ # - when {Node}:
94
+ # An element that is cloned and used as the wrapper.
95
+ #
96
+ # @return [Node] +self+, to support chaining of calls.
97
+ #
98
+ # @see NodeSet#wrap
99
+ #
100
+ # @example with a {String} argument:
101
+ #
102
+ # doc = Nokolexbor::HTML('<body><a>123</a></body>')
103
+ # doc.at_css('a').wrap('<div></div>')
104
+ # doc.at_css('body').inner_html
105
+ # # => "<div><a>123</a></div>"
106
+ #
107
+ # @example with a {Node} argument:
108
+ #
109
+ # doc = Nokolexbor::HTML('<body><a>123</a></body>')
110
+ # doc.at_css('a').wrap(doc.create_element('div'))
111
+ # doc.at_css('body').inner_html
112
+ # # => "<div><a>123</a></div>"
113
+ #
74
114
  def wrap(node)
75
115
  case node
76
116
  when String
77
117
  new_parent = fragment(node).child
118
+ when DocumentFragment
119
+ new_parent = node.child
78
120
  when Node
79
121
  new_parent = node.dup
80
122
  else
@@ -91,6 +133,13 @@ module Nokolexbor
91
133
  self
92
134
  end
93
135
 
136
+ # Insert +node_or_tags+ before this Node (as a sibling).
137
+ #
138
+ # @param node_or_tags [Node, DocumentFragment, NodeSet, String] The node to be added.
139
+ #
140
+ # @return [Node,NodeSet] The reparented {Node} (if +node_or_tags+ is a {Node}), or {NodeSet} (if +node_or_tags+ is a {DocumentFragment}, {NodeSet}, or {String}).
141
+ #
142
+ # @see #before
94
143
  def add_previous_sibling(node_or_tags)
95
144
  raise ArgumentError,
96
145
  "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
@@ -98,6 +147,13 @@ module Nokolexbor
98
147
  add_sibling(:previous, node_or_tags)
99
148
  end
100
149
 
150
+ # Insert +node_or_tags+ after this Node (as a sibling).
151
+ #
152
+ # @param node_or_tags [Node, DocumentFragment, NodeSet, String] The node to be added.
153
+ #
154
+ # @return [Node,NodeSet] The reparented {Node} (if +node_or_tags+ is a {Node}), or {NodeSet} (if +node_or_tags+ is a {DocumentFragment}, {NodeSet}, or {String}).
155
+ #
156
+ # @see #after
101
157
  def add_next_sibling(node_or_tags)
102
158
  raise ArgumentError,
103
159
  "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
@@ -105,11 +161,25 @@ module Nokolexbor
105
161
  add_sibling(:next, node_or_tags)
106
162
  end
107
163
 
164
+ # Insert +node_or_tags+ before this Node (as a sibling).
165
+ #
166
+ # @param node_or_tags [Node, DocumentFragment, NodeSet, String] The node to be added.
167
+ #
168
+ # @return [Node] +self+, to support chaining of calls.
169
+ #
170
+ # @see #add_previous_sibling
108
171
  def before(node_or_tags)
109
172
  add_previous_sibling(node_or_tags)
110
173
  self
111
174
  end
112
175
 
176
+ # Insert +node_or_tags+ after this Node (as a sibling).
177
+ #
178
+ # @param node_or_tags [Node, DocumentFragment, NodeSet, String] The node to be added.
179
+ #
180
+ # @return [Node] +self+, to support chaining of calls.
181
+ #
182
+ # @see #add_next_sibling
113
183
  def after(node_or_tags)
114
184
  add_next_sibling(node_or_tags)
115
185
  self
@@ -120,11 +190,25 @@ module Nokolexbor
120
190
  alias_method :next=, :add_next_sibling
121
191
  alias_method :previous=, :add_previous_sibling
122
192
 
193
+ # Add +node_or_tags+ as a child of this Node.
194
+ #
195
+ # @param node_or_tags [Node, DocumentFragment, NodeSet, String] The node to be added.
196
+ #
197
+ # @return [Node] +self+, to support chaining of calls.
198
+ #
199
+ # @see #add_child
123
200
  def <<(node_or_tags)
124
201
  add_child(node_or_tags)
125
202
  self
126
203
  end
127
204
 
205
+ # Add +node+ as the first child of this Node.
206
+ #
207
+ # @param node [Node, DocumentFragment, NodeSet, String] The node to be added.
208
+ #
209
+ # @return [Node,NodeSet] The reparented {Node} (if +node+ is a {Node}), or {NodeSet} (if +node+ is a {DocumentFragment}, {NodeSet}, or {String}).
210
+ #
211
+ # @see #add_child
128
212
  def prepend_child(node)
129
213
  if (first = children.first)
130
214
  # Mimic the error add_child would raise.
@@ -136,86 +220,175 @@ module Nokolexbor
136
220
  end
137
221
  end
138
222
 
223
+ # Traverse self and all children.
224
+ # @yield self and all children to +block+ recursively.
139
225
  def traverse(&block)
140
226
  children.each { |j| j.traverse(&block) }
141
227
  yield(self)
142
228
  end
143
229
 
230
+ # @param selector [String] The selector to match
231
+ #
232
+ # @return true if this Node matches +selector+
144
233
  def matches?(selector)
145
234
  ancestors.last.css(selector).any? { |node| node == self }
146
235
  end
147
236
 
148
- def attribute(name)
149
- return nil unless key?(name)
150
- Attribute.new(name, attr(name))
151
- end
152
-
237
+ # Fetch this node's attributes.
238
+ #
239
+ # @return [Hash{String => Attribute}] Hash containing attributes belonging to +self+. The hash keys are String attribute names, and the hash values are {Nokolexbor::Attribute}.
153
240
  def attributes
154
- attrs.map { |k, v| [k, Attribute.new(k, v)] }.to_h
241
+ attribute_nodes.each_with_object({}) do |node, hash|
242
+ hash[node.name] = node
243
+ end
155
244
  end
156
245
 
246
+ # Replace this Node with +node+.
247
+ #
248
+ # @param node [Node, DocumentFragment, NodeSet, String]
249
+ #
250
+ # @return [Node,NodeSet] The reparented {Node} (if +node+ is a {Node}), or {NodeSet} (if +node+ is a {DocumentFragment}, {NodeSet}, or {String}).
251
+ #
252
+ # @see #swap
157
253
  def replace(node)
158
- if node.is_a?(NodeSet)
159
- node.each { |n| add_sibling(:previous, n) }
160
- else
161
- add_sibling(:previous, node)
162
- end
254
+ ret = add_sibling(:previous, node)
163
255
  remove
256
+ ret
257
+ end
258
+
259
+ # Swap this Node for +node+.
260
+ #
261
+ # @param node [Node, DocumentFragment, NodeSet, String]
262
+ #
263
+ # @return [Node] +self+, to support chaining of calls.
264
+ #
265
+ # @see #replace
266
+ def swap(node)
267
+ replace(node)
268
+ self
164
269
  end
165
270
 
271
+ # Set the content of this Node.
272
+ #
273
+ # @param node [Node, DocumentFragment, NodeSet, String] The node to be added.
274
+ #
275
+ # @see #inner_html=
166
276
  def children=(node)
167
277
  children.remove
168
- if node.is_a?(NodeSet)
169
- node.each { |n| add_child(n) }
170
- else
171
- add_child(node)
172
- end
278
+ add_child(node)
173
279
  end
174
280
 
281
+ # Set the parent Node of this Node.
282
+ #
283
+ # @param parent_node [Node] The parent node.
175
284
  def parent=(parent_node)
176
285
  parent_node.add_child(self)
177
286
  end
178
287
 
288
+ # Iterate over each attribute name and value pair of this Node.
289
+ #
290
+ # @yield [String,String] The name and value of the current attribute.
179
291
  def each
180
292
  attributes.each do |name, node|
181
293
  yield [name, node.value]
182
294
  end
183
295
  end
184
296
 
297
+ # Create a {DocumentFragment} containing +tags+ that is relative to _this_
298
+ # context node.
299
+ #
300
+ # @return [DocumentFragment]
185
301
  def fragment(tags)
186
302
  Nokolexbor::DocumentFragment.new(document, tags, self)
187
303
  end
188
304
 
189
305
  alias_method :inner_html=, :children=
190
306
 
307
+ # Search this object for CSS +rules+. +rules+ must be one or more CSS
308
+ # selectors.
309
+ #
310
+ # This method uses Lexbor as the selector engine. Its performance is much higher than {#xpath} or {#nokogiri_css}.
311
+ #
312
+ # @example
313
+ # node.css('title')
314
+ # node.css('body h1.bold')
315
+ # node.css('div + p.green', 'div#one')
316
+ #
317
+ # @return [NodeSet] The matched set of Nodes.
318
+ #
319
+ # @see #xpath
320
+ # @see #nokogiri_css
191
321
  def css(*args)
192
322
  css_impl(args.join(', '))
193
323
  end
194
324
 
325
+ # Like {#css}, but returns the first match.
326
+ #
327
+ # This method uses Lexbor as the selector engine. Its performance is much higher than {#at_xpath} or {#nokogiri_at_css}.
328
+ #
329
+ # @return [Node, nil] The first matched Node.
330
+ #
331
+ # @see #css
332
+ # @see #nokogiri_at_css
195
333
  def at_css(*args)
196
334
  at_css_impl(args.join(', '))
197
335
  end
198
336
 
337
+ # Search this object for CSS +rules+. +rules+ must be one or more CSS
338
+ # selectors. It supports a mixed syntax of CSS selectors and XPath.
339
+ #
340
+ # This method uses libxml2 as the selector engine. It works the same way as {Nokogiri::Node#css}.
341
+ #
342
+ # @return [NodeSet] The matched set of Nodes.
343
+ #
344
+ # @see #css
199
345
  def nokogiri_css(*args)
200
346
  rules, handler, ns, _ = extract_params(args)
201
347
 
202
348
  nokogiri_css_internal(self, rules, handler, ns)
203
349
  end
204
350
 
351
+ # Like {#nokogiri_css}, but returns the first match.
352
+ #
353
+ # This method uses libxml2 as the selector engine. It works the same way as {Nokogiri::Node#at_css}.
354
+ #
355
+ # @return [Node, nil] The first matched Node.
356
+ #
357
+ # @see #nokogiri_at_css
358
+ # @see #at_css
205
359
  def nokogiri_at_css(*args)
206
360
  nokogiri_css(*args).first
207
361
  end
208
362
 
363
+ # Search this node for XPath +paths+. +paths+ must be one or more XPath
364
+ # queries.
365
+ #
366
+ # It works the same way as {Nokogiri::Node#xpath}.
367
+ #
368
+ # @example
369
+ # node.xpath('.//title')
370
+ #
371
+ # @return [NodeSet] The matched set of Nodes.
209
372
  def xpath(*args)
210
373
  paths, handler, ns, binds = extract_params(args)
211
374
 
212
375
  xpath_internal(self, paths, handler, ns, binds)
213
376
  end
214
377
 
378
+ # Like {#xpath}, but returns the first match.
379
+ #
380
+ # It works the same way as {Nokogiri::Node#at_xpath}.
381
+ #
382
+ # @return [Node, nil] The first matched Node.
383
+ #
384
+ # @see #xpath
215
385
  def at_xpath(*args)
216
386
  xpath(*args).first
217
387
  end
218
388
 
389
+ # Search this object for +paths+. +paths+ must be one or more XPath or CSS selectors.
390
+ #
391
+ # @return [NodeSet] The matched set of Nodes.
219
392
  def search(*args)
220
393
  paths, handler, ns, binds = extract_params(args)
221
394
 
@@ -228,6 +401,11 @@ module Nokolexbor
228
401
 
229
402
  alias_method :/, :search
230
403
 
404
+ # Like {#search}, but returns the first match.
405
+ #
406
+ # @return [Node, nil] The first matched Node.
407
+ #
408
+ # @see #search
231
409
  def at(*args)
232
410
  paths, handler, ns, binds = extract_params(args)
233
411
 
@@ -240,26 +418,148 @@ module Nokolexbor
240
418
 
241
419
  alias_method :%, :at
242
420
 
421
+ # Fetch CSS class names of a Node.
422
+ #
423
+ # This is a convenience function and is equivalent to:
424
+ #
425
+ # node.kwattr_values("class")
426
+ #
427
+ # @see #kwattr_values
428
+ # @see #add_class
429
+ # @see #append_class
430
+ # @see #remove_class
431
+ #
432
+ # @return [Array]
433
+ # The CSS classes present in the Node's "class" attribute. If the
434
+ # attribute is empty or non-existent, the return value is an empty array.
435
+ #
436
+ # @example
437
+ # node.classes # => ["section", "title", "header"]
243
438
  def classes
244
439
  kwattr_values("class")
245
440
  end
246
441
 
442
+ # Ensure CSS classes are present on +self+. Any CSS classes in +names+ that already exist
443
+ # in the "class" attribute are _not_ added. Note that any existing duplicates in the
444
+ # "class" attribute are not removed. Compare with {#append_class}.
445
+ #
446
+ # This is a convenience function and is equivalent to:
447
+ #
448
+ # node.kwattr_add("class", names)
449
+ #
450
+ # @see #kwattr_add
451
+ # @see #classes
452
+ # @see #append_class
453
+ # @see #remove_class
454
+ #
455
+ # @param [String, Array<String>] names
456
+ # CSS class names to be added to the Node's "class" attribute. May be a string containing
457
+ # whitespace-delimited names, or an Array of String names. Any class names already present
458
+ # will not be added. Any class names not present will be added. If no "class" attribute
459
+ # exists, one is created.
460
+ #
461
+ # @return [Node] +self+, to support chaining of calls.
462
+ #
463
+ # @example
464
+ # node.add_class("section") # => <div class="section"></div>
465
+ # node.add_class("section") # => <div class="section"></div> # duplicate not added
466
+ # node.add_class("section header") # => <div class="section header"></div>
467
+ # node.add_class(["section", "header"]) # => <div class="section header"></div>
247
468
  def add_class(names)
248
469
  kwattr_add("class", names)
249
470
  end
250
471
 
472
+ # Add CSS classes to +self+, regardless of duplication. Compare with {#add_class}.
473
+ #
474
+ # This is a convenience function and is equivalent to:
475
+ #
476
+ # node.kwattr_append("class", names)
477
+ #
478
+ # @see #kwattr_append
479
+ # @see #classes
480
+ # @see #add_class
481
+ # @see #remove_class
482
+ #
483
+ # @return [Node] +self+, to support chaining of calls.
251
484
  def append_class(names)
252
485
  kwattr_append("class", names)
253
486
  end
254
487
 
488
+ # Remove CSS classes from this node. Any CSS class names in +css_classes+ that exist in
489
+ # this node's "class" attribute are removed, including any multiple entries.
490
+ #
491
+ # If no CSS classes remain after this operation, or if +css_classes+ is +nil+, the "class"
492
+ # attribute is deleted from the node.
493
+ #
494
+ # This is a convenience function and is equivalent to:
495
+ #
496
+ # node.kwattr_remove("class", css_classes)
497
+ #
498
+ # @see #kwattr_remove
499
+ # @see #classes
500
+ # @see #add_class
501
+ # @see #append_class
502
+ #
503
+ # @param names [String, Array<String>]
504
+ # CSS class names to be removed from the Node's
505
+ # "class" attribute. May be a string containing whitespace-delimited names, or an Array of
506
+ # String names. Any class names already present will be removed. If no CSS classes remain,
507
+ # the "class" attribute is deleted.
508
+ #
509
+ # @return [Node] +self+, to support chaining of calls.
510
+ #
511
+ # @example
512
+ # node.remove_class("section")
513
+ # node.remove_class(["section", "float"])
255
514
  def remove_class(names = nil)
256
515
  kwattr_remove("class", names)
257
516
  end
258
517
 
518
+ # Fetch values from a keyword attribute of a Node.
519
+ #
520
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
521
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
522
+ # contain CSS classes. But other keyword attributes exist, for instance
523
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
524
+ #
525
+ # @see #kwattr_add
526
+ # @#kwattr_append
527
+ # @#kwattr_remove
528
+ #
529
+ # @param attribute_name [String]
530
+ # The name of the keyword attribute to be inspected.
531
+ #
532
+ # @return [Array<String>]
533
+ # The values present in the Node's +attribute_name+ attribute. If the
534
+ # attribute is empty or non-existent, the return value is an empty array.
259
535
  def kwattr_values(attribute_name)
260
536
  keywordify(attr(attribute_name) || [])
261
537
  end
262
538
 
539
+ # Ensure that values are present in a keyword attribute.
540
+ #
541
+ # Any values in +keywords+ that already exist in the Node's attribute values are _not_
542
+ # added. Note that any existing duplicates in the attribute values are not removed. Compare
543
+ # with {#kwattr_append}.
544
+ #
545
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
546
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
547
+ # contain CSS classes. But other keyword attributes exist, for instance
548
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
549
+ #
550
+ # @see #add_class
551
+ # @see #kwattr_values
552
+ # @see #kwattr_append
553
+ # @see #kwattr_remove
554
+ #
555
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
556
+ # @param keywords [String, Array<String>]
557
+ # Keywords to be added to the attribute named +attribute_name+. May be a string containing
558
+ # whitespace-delimited values, or an Array of String values. Any values already present will
559
+ # not be added. Any values not present will be added. If the named attribute does not exist,
560
+ # it is created.
561
+ #
562
+ # @return [Node] +self+, to support chaining of calls.
263
563
  def kwattr_add(attribute_name, keywords)
264
564
  keywords = keywordify(keywords)
265
565
  current_kws = kwattr_values(attribute_name)
@@ -268,6 +568,27 @@ module Nokolexbor
268
568
  self
269
569
  end
270
570
 
571
+ # Add keywords to a Node's keyword attribute, regardless of duplication. Compare with
572
+ # {#kwattr_add}.
573
+ #
574
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
575
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
576
+ # contain CSS classes. But other keyword attributes exist, for instance
577
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
578
+ #
579
+ # @see #add_class
580
+ # @see #kwattr_values
581
+ # @see #kwattr_add
582
+ # @see #kwattr_remove
583
+ #
584
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
585
+ # @param keywords [String, Array<String>]
586
+ # Keywords to be added to the attribute named +attribute_name+. May be a string containing
587
+ # whitespace-delimited values, or an Array of String values. Any values already present will
588
+ # not be added. Any values not present will be added. If the named attribute does not exist,
589
+ # it is created.
590
+ #
591
+ # @return [Node] +self+, to support chaining of calls.
271
592
  def kwattr_append(attribute_name, keywords)
272
593
  keywords = keywordify(keywords)
273
594
  current_kws = kwattr_values(attribute_name)
@@ -276,6 +597,30 @@ module Nokolexbor
276
597
  self
277
598
  end
278
599
 
600
+ # Remove keywords from a keyword attribute. Any matching keywords that exist in the named
601
+ # attribute are removed, including any multiple entries.
602
+ #
603
+ # If no keywords remain after this operation, or if +keywords+ is +nil+, the attribute is
604
+ # deleted from the node.
605
+ #
606
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
607
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
608
+ # contain CSS classes. But other keyword attributes exist, for instance
609
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
610
+ #
611
+ # @see #remove_class
612
+ # @see #kwattr_values
613
+ # @see #kwattr_add
614
+ # @see #kwattr_append
615
+ #
616
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
617
+ # @param keywords [String, Array<String>]
618
+ # Keywords to be added to the attribute named +attribute_name+. May be a string containing
619
+ # whitespace-delimited values, or an Array of String values. Any values already present will
620
+ # not be added. Any values not present will be added. If the named attribute does not exist,
621
+ # it is created.
622
+ #
623
+ # @return [Node] +self+, to support chaining of calls.
279
624
  def kwattr_remove(attribute_name, keywords)
280
625
  if keywords.nil?
281
626
  remove_attr(attribute_name)
@@ -293,6 +638,15 @@ module Nokolexbor
293
638
  self
294
639
  end
295
640
 
641
+ # Serialize Node and write to +io+.
642
+ def write_to(io, *options)
643
+ io.write(to_html(*options))
644
+ end
645
+
646
+ alias_method :write_html_to, :write_to
647
+
648
+ private
649
+
296
650
  def keywordify(keywords)
297
651
  case keywords
298
652
  when Enumerable
@@ -305,14 +659,6 @@ module Nokolexbor
305
659
  end
306
660
  end
307
661
 
308
- def write_to(io, *options)
309
- io.write(to_html(*options))
310
- end
311
-
312
- alias_method :write_html_to, :write_to
313
-
314
- private
315
-
316
662
  def nokogiri_css_internal(node, rules, handler, ns)
317
663
  xpath_internal(node, css_rules_to_xpath(rules, ns), handler, ns, nil)
318
664
  end
@@ -4,6 +4,11 @@ module Nokolexbor
4
4
  class NodeSet < Nokolexbor::Node
5
5
  include Enumerable
6
6
 
7
+ # Create a NodeSet with +document+ defaulting to +list+.
8
+ #
9
+ # @yield [Document]
10
+ #
11
+ # @return [Document]
7
12
  def self.new(document, list = [])
8
13
  obj = allocate
9
14
  obj.instance_variable_set(:@document, document)
@@ -12,6 +17,9 @@ module Nokolexbor
12
17
  obj
13
18
  end
14
19
 
20
+ # Iterate over each node.
21
+ #
22
+ # @yield [Node]
15
23
  def each
16
24
  return to_enum unless block_given?
17
25
 
@@ -21,6 +29,11 @@ module Nokolexbor
21
29
  self
22
30
  end
23
31
 
32
+ # Get the first +n+ elements of the NodeSet.
33
+ #
34
+ # @param n [Numeric,nil]
35
+ #
36
+ # @return [Node,Array<Node>] {Node} if +n+ is nil, otherwise {Array<Node>}
24
37
  def first(n = nil)
25
38
  return self[0] unless n
26
39
 
@@ -29,14 +42,19 @@ module Nokolexbor
29
42
  list
30
43
  end
31
44
 
45
+ # Get the last element of the NodeSet.
46
+ #
47
+ # @return [Node,nil]
32
48
  def last
33
49
  self[-1]
34
50
  end
35
51
 
52
+ # @return [Boolean] true if this NodeSet is empty.
36
53
  def empty?
37
54
  length == 0
38
55
  end
39
56
 
57
+ # @return [Integer] The index of the first node in this NodeSet that is equal to +node+ or meets the given block. Returns nil if no match is found.
40
58
  def index(node = nil)
41
59
  if node
42
60
  each_with_index { |member, j| return j if member == node }
@@ -46,6 +64,9 @@ module Nokolexbor
46
64
  nil
47
65
  end
48
66
 
67
+ # Get the content of all contained Nodes.
68
+ #
69
+ # @return [String]
49
70
  def content
50
71
  self.map(&:content).join
51
72
  end
@@ -54,10 +75,16 @@ module Nokolexbor
54
75
  alias_method :inner_text, :content
55
76
  alias_method :to_str, :content
56
77
 
78
+ # Get the inner html of all contained Nodes.
79
+ #
80
+ # @return [String]
57
81
  def inner_html(*args)
58
82
  self.map { |n| n.inner_html(*args) }.join
59
83
  end
60
84
 
85
+ # Convert this NodeSet to HTML.
86
+ #
87
+ # @return [String]
61
88
  def outer_html(*args)
62
89
  self.map { |n| n.outer_html(*args) }.join
63
90
  end
@@ -66,6 +93,9 @@ module Nokolexbor
66
93
  alias_method :to_html, :outer_html
67
94
  alias_method :serialize, :outer_html
68
95
 
96
+ # Remove all nodes in this NodeSet.
97
+ #
98
+ # @see Node#remove
69
99
  def remove
70
100
  self.each(&:remove)
71
101
  end
@@ -73,22 +103,32 @@ module Nokolexbor
73
103
  alias_method :unlink, :remove
74
104
  alias_method :to_ary, :to_a
75
105
 
106
+ # Destroy all nodes in the NodeSet.
107
+ #
108
+ # @see Node#destroy
76
109
  def destroy
77
110
  self.each(&:destroy)
78
111
  end
79
112
 
113
+ # @return [Node,nil] The last element of this NodeSet and removes it. Returns
114
+ # +nil+ if the set is empty.
80
115
  def pop
81
116
  return nil if length == 0
82
117
 
83
118
  delete(last)
84
119
  end
85
120
 
121
+ # @return [Node,nil] The first element of this NodeSet and removes it. Returns
122
+ # +nil+ if the set is empty.
86
123
  def shift
87
124
  return nil if length == 0
88
125
 
89
126
  delete(first)
90
127
  end
91
128
 
129
+ # @return [Boolean] true if two NodeSets contain the same number
130
+ # of elements and each element is equal to the corresponding
131
+ # element in the other NodeSet.
92
132
  def ==(other)
93
133
  return false unless other.is_a?(NodeSet)
94
134
  return false unless length == other.length
@@ -99,6 +139,8 @@ module Nokolexbor
99
139
  true
100
140
  end
101
141
 
142
+ # @return [NodeSet] A new NodeSet containing all the children of all the nodes in
143
+ # the NodeSet.
102
144
  def children
103
145
  node_set = NodeSet.new(@document)
104
146
  each do |node|
@@ -107,6 +149,8 @@ module Nokolexbor
107
149
  node_set
108
150
  end
109
151
 
152
+ # @return [NodeSet] A new NodeSet containing all the nodes in the NodeSet
153
+ # in reverse order.
110
154
  def reverse
111
155
  node_set = NodeSet.new(@document)
112
156
  (length - 1).downto(0) do |x|
@@ -115,6 +159,17 @@ module Nokolexbor
115
159
  node_set
116
160
  end
117
161
 
162
+ # Wrap all nodes of this NodeSet with +node_or_tags+.
163
+ #
164
+ # @see Node#wrap
165
+ #
166
+ # @return [NodeSet] +self+, to support chaining.
167
+ def wrap(node_or_tags)
168
+ map { |node| node.wrap(node_or_tags) }
169
+ self
170
+ end
171
+
172
+ # (see Node#xpath)
118
173
  def xpath(*args)
119
174
  paths, handler, ns, binds = extract_params(args)
120
175
 
@@ -127,6 +182,7 @@ module Nokolexbor
127
182
  end
128
183
  end
129
184
 
185
+ # (see Node#nokogiri_css)
130
186
  def nokogiri_css(*args)
131
187
  rules, handler, ns, _ = extract_params(args)
132
188
  paths = css_rules_to_xpath(rules, ns)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nokolexbor
4
- VERSION = '0.3.3'
4
+ VERSION = '0.3.5'
5
5
  end
data/lib/nokolexbor.rb CHANGED
@@ -26,7 +26,6 @@ require 'nokolexbor/node'
26
26
  require 'nokolexbor/document'
27
27
  require 'nokolexbor/node_set'
28
28
  require 'nokolexbor/document_fragment'
29
- require 'nokolexbor/attribute'
30
29
  require 'nokolexbor/xpath'
31
30
  require 'nokolexbor/xpath_context'
32
31
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nokolexbor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.5
5
5
  platform: x86_64-darwin
6
6
  authors:
7
7
  - Yicheng Zhou
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-09 00:00:00.000000000 Z
11
+ date: 2023-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -50,7 +50,6 @@ files:
50
50
  - lib/nokolexbor/2.7/nokolexbor.bundle
51
51
  - lib/nokolexbor/3.0/nokolexbor.bundle
52
52
  - lib/nokolexbor/3.1/nokolexbor.bundle
53
- - lib/nokolexbor/attribute.rb
54
53
  - lib/nokolexbor/document.rb
55
54
  - lib/nokolexbor/document_fragment.rb
56
55
  - lib/nokolexbor/node.rb
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nokolexbor
4
- class Attribute
5
- attr_accessor :name
6
- attr_accessor :value
7
-
8
- def initialize(name, value)
9
- @name = name
10
- @value = value
11
- end
12
-
13
- alias_method :text, :value
14
- alias_method :content, :value
15
- alias_method :to_s, :value
16
- alias_method :to_str, :value
17
- end
18
- end