nokolexbor 0.3.3-arm64-darwin → 0.3.5-arm64-darwin

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d3990d88b4adf5ec56a011b36d842c8add5c45ada73978dec977ceaab7b6d47
4
- data.tar.gz: b9e84140c6263736043f34f4a3b46af5a1eac0f5bda23d26c4558cbd9849ee4c
3
+ metadata.gz: cb8e9edb98de9288e35a8803b8eedd026ec7b75f0ccc623ec77bee271f278727
4
+ data.tar.gz: 9b0f811b69c7f1e66a3c5197f79771776e8ed63a28239705d9b958523a0338fb
5
5
  SHA512:
6
- metadata.gz: a09f67d4f44889030a1d910ec1c5d2c8f6f6fb6d29e0892ca6e8f00de1d8ff8d0af1e46a3fe2c969c0d0d961a9d2a083acc772b7b51eafd4548000392e08beba
7
- data.tar.gz: 576bebf4dbc21f0b785d7cf5064c2424f96ef0613e3d8544530734e30db57d4fbaff7978fbec31d231cca4c436a3e270f5241c29129d63cd9a27eedaaa84044f
6
+ metadata.gz: 1b18ad440fede774efadb1abe314f4d7e62056396e66df8cf47e73b5d5a5935161706d4b3f94d017ad01e2767c8fa292cfb911b145321353226c44446afde080
7
+ data.tar.gz: 9548a69d16dac127567ab24dfddd0cb0b9174a26c0756a5347d20a26e4289a3b1325aabf4d38b6b1d7946dec8170a2721ccb5a5728b30d79b528828f56420977
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: arm64-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