nokogiri 1.12.5 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of nokogiri might be problematic. Click here for more details.

Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/README.md +9 -7
  4. data/bin/nokogiri +63 -50
  5. data/dependencies.yml +5 -6
  6. data/ext/nokogiri/extconf.rb +47 -35
  7. data/ext/nokogiri/xml_document.c +35 -35
  8. data/ext/nokogiri/xml_document_fragment.c +0 -2
  9. data/ext/nokogiri/xml_dtd.c +2 -2
  10. data/ext/nokogiri/xml_encoding_handler.c +25 -11
  11. data/ext/nokogiri/xml_node.c +638 -333
  12. data/ext/nokogiri/xml_reader.c +37 -11
  13. data/ext/nokogiri/xml_xpath_context.c +72 -49
  14. data/gumbo-parser/src/parser.c +0 -11
  15. data/lib/nokogiri/class_resolver.rb +67 -0
  16. data/lib/nokogiri/css/node.rb +9 -8
  17. data/lib/nokogiri/css/parser.rb +11 -3
  18. data/lib/nokogiri/css/parser.y +10 -2
  19. data/lib/nokogiri/css/parser_extras.rb +20 -20
  20. data/lib/nokogiri/css/syntax_error.rb +1 -0
  21. data/lib/nokogiri/css/tokenizer.rb +2 -1
  22. data/lib/nokogiri/css/tokenizer.rex +2 -1
  23. data/lib/nokogiri/css/xpath_visitor.rb +174 -75
  24. data/lib/nokogiri/css.rb +38 -6
  25. data/lib/nokogiri/decorators/slop.rb +8 -7
  26. data/lib/nokogiri/extension.rb +1 -1
  27. data/lib/nokogiri/gumbo.rb +1 -0
  28. data/lib/nokogiri/html.rb +16 -10
  29. data/lib/nokogiri/html4/builder.rb +1 -0
  30. data/lib/nokogiri/html4/document.rb +84 -75
  31. data/lib/nokogiri/html4/document_fragment.rb +11 -7
  32. data/lib/nokogiri/html4/element_description.rb +1 -0
  33. data/lib/nokogiri/html4/element_description_defaults.rb +426 -520
  34. data/lib/nokogiri/html4/entity_lookup.rb +2 -1
  35. data/lib/nokogiri/html4/sax/parser.rb +2 -1
  36. data/lib/nokogiri/html4/sax/parser_context.rb +1 -0
  37. data/lib/nokogiri/html4/sax/push_parser.rb +7 -7
  38. data/lib/nokogiri/html4.rb +11 -5
  39. data/lib/nokogiri/html5/document.rb +24 -10
  40. data/lib/nokogiri/html5/document_fragment.rb +5 -2
  41. data/lib/nokogiri/html5/node.rb +6 -3
  42. data/lib/nokogiri/html5.rb +68 -64
  43. data/lib/nokogiri/jruby/dependencies.rb +10 -9
  44. data/lib/nokogiri/syntax_error.rb +1 -0
  45. data/lib/nokogiri/version/constant.rb +2 -1
  46. data/lib/nokogiri/version/info.rb +19 -13
  47. data/lib/nokogiri/version.rb +1 -0
  48. data/lib/nokogiri/xml/attr.rb +5 -3
  49. data/lib/nokogiri/xml/attribute_decl.rb +2 -1
  50. data/lib/nokogiri/xml/builder.rb +32 -32
  51. data/lib/nokogiri/xml/cdata.rb +2 -1
  52. data/lib/nokogiri/xml/character_data.rb +1 -0
  53. data/lib/nokogiri/xml/document.rb +139 -103
  54. data/lib/nokogiri/xml/document_fragment.rb +41 -38
  55. data/lib/nokogiri/xml/dtd.rb +3 -2
  56. data/lib/nokogiri/xml/element_content.rb +1 -0
  57. data/lib/nokogiri/xml/element_decl.rb +2 -1
  58. data/lib/nokogiri/xml/entity_decl.rb +3 -2
  59. data/lib/nokogiri/xml/entity_reference.rb +1 -0
  60. data/lib/nokogiri/xml/namespace.rb +2 -0
  61. data/lib/nokogiri/xml/node/save_options.rb +6 -3
  62. data/lib/nokogiri/xml/node.rb +512 -348
  63. data/lib/nokogiri/xml/node_set.rb +46 -54
  64. data/lib/nokogiri/xml/notation.rb +12 -0
  65. data/lib/nokogiri/xml/parse_options.rb +11 -7
  66. data/lib/nokogiri/xml/pp/character_data.rb +8 -6
  67. data/lib/nokogiri/xml/pp/node.rb +24 -26
  68. data/lib/nokogiri/xml/pp.rb +1 -0
  69. data/lib/nokogiri/xml/processing_instruction.rb +2 -1
  70. data/lib/nokogiri/xml/reader.rb +17 -19
  71. data/lib/nokogiri/xml/relax_ng.rb +1 -0
  72. data/lib/nokogiri/xml/sax/document.rb +20 -19
  73. data/lib/nokogiri/xml/sax/parser.rb +36 -34
  74. data/lib/nokogiri/xml/sax/parser_context.rb +7 -3
  75. data/lib/nokogiri/xml/sax/push_parser.rb +5 -5
  76. data/lib/nokogiri/xml/sax.rb +1 -0
  77. data/lib/nokogiri/xml/schema.rb +7 -6
  78. data/lib/nokogiri/xml/searchable.rb +42 -22
  79. data/lib/nokogiri/xml/syntax_error.rb +4 -4
  80. data/lib/nokogiri/xml/text.rb +1 -0
  81. data/lib/nokogiri/xml/xpath/syntax_error.rb +2 -1
  82. data/lib/nokogiri/xml/xpath.rb +12 -0
  83. data/lib/nokogiri/xml/xpath_context.rb +2 -3
  84. data/lib/nokogiri/xml.rb +3 -3
  85. data/lib/nokogiri/xslt/stylesheet.rb +1 -0
  86. data/lib/nokogiri/xslt.rb +3 -2
  87. data/lib/nokogiri.rb +19 -16
  88. data/lib/xsd/xmlparser/nokogiri.rb +25 -24
  89. data/patches/libxml2/0008-htmlParseComment-handle-abruptly-closed-comments.patch +61 -0
  90. data/patches/libxml2/0009-allow-wildcard-namespaces.patch +77 -0
  91. metadata +101 -27
@@ -1,12 +1,15 @@
1
- # encoding: UTF-8
1
+ # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+
3
4
  require "stringio"
4
5
 
5
6
  module Nokogiri
6
7
  module XML
7
- ##
8
- # {Nokogiri::XML::Node} is your window to the fun filled world of dealing with XML and HTML
9
- # tags. A {Nokogiri::XML::Node} may be treated similarly to a hash with regard to attributes. For
8
+ # Nokogiri::XML::Node is the primary API you'll use to interact with your Document.
9
+ #
10
+ # == Attributes
11
+ #
12
+ # A Nokogiri::XML::Node may be treated similarly to a hash with regard to attributes. For
10
13
  # example:
11
14
  #
12
15
  # node = Nokogiri::XML::DocumentFragment.parse("<a href='#foo' id='link'>link</a>").at_css("a")
@@ -17,41 +20,52 @@ module Nokogiri
17
20
  # node['class'] = 'green' # => "green"
18
21
  # node.to_html # => "<a href=\"#foo\" id=\"link\" class=\"green\">link</a>"
19
22
  #
20
- # See the method group entitled "Working With Node Attributes" for the full set of methods.
23
+ # See the method group entitled Node@Working+With+Node+Attributes for the full set of methods.
24
+ #
25
+ # == Navigation
21
26
  #
22
- # {Nokogiri::XML::Node} also has methods that let you move around your
23
- # tree. For navigating your tree, see:
27
+ # Nokogiri::XML::Node also has methods that let you move around your tree:
24
28
  #
25
- # * {#parent}
26
- # * {#children}
27
- # * {#next}
28
- # * {#previous}
29
+ # [#parent, #children, #next, #previous]
30
+ # Navigate up, down, or through siblings.
29
31
  #
30
- # When printing or otherwise emitting a document or a node (and
31
- # its subtree), there are a few methods you might want to use:
32
+ # See the method group entitled Node@Traversing+Document+Structure for the full set of methods.
32
33
  #
33
- # * {#content}, {#text}, {#inner_text}, {#to_str}: These methods will all <b>emit plaintext</b>,
34
- # meaning that entities will be replaced (e.g., "&lt;" will be replaced with "<"), meaning
34
+ # == Serialization
35
+ #
36
+ # When printing or otherwise emitting a document or a node (and its subtree), there are a few
37
+ # methods you might want to use:
38
+ #
39
+ # [#content, #text, #inner_text, #to_str]
40
+ # These methods will all **emit plaintext**,
41
+ # meaning that entities will be replaced (e.g., +&lt;+ will be replaced with +<+), meaning
35
42
  # that any sanitizing will likely be un-done in the output.
36
43
  #
37
- # * {#to_s}, {#to_xml}, {#to_html}, {#inner_html}: These methods will all <b>emit
38
- # properly-escaped markup</b>, meaning that it's suitable for consumption by browsers,
39
- # parsers, etc.
44
+ # [#to_s, #to_xml, #to_html, #inner_html]
45
+ # These methods will all **emit properly-escaped markup**, meaning that it's suitable for
46
+ # consumption by browsers, parsers, etc.
47
+ #
48
+ # See the method group entitled Node@Serialization+and+Generating+Output for the full set of methods.
49
+ #
50
+ # == Searching
40
51
  #
41
- # You may search this node's subtree using {#xpath} and {#css}
52
+ # You may search this node's subtree using methods like #xpath and #css.
53
+ #
54
+ # See the method group entitled Node@Searching+via+XPath+or+CSS+Queries for the full set of methods.
42
55
  #
43
56
  class Node
44
57
  include Nokogiri::XML::PP::Node
45
58
  include Nokogiri::XML::Searchable
59
+ include Nokogiri::ClassResolver
46
60
  include Enumerable
47
61
 
48
- # Element node type, see {Nokogiri::XML::Node#element?}
62
+ # Element node type, see Nokogiri::XML::Node#element?
49
63
  ELEMENT_NODE = 1
50
64
  # Attribute node type
51
65
  ATTRIBUTE_NODE = 2
52
- # Text node type, see {Nokogiri::XML::Node#text?}
66
+ # Text node type, see Nokogiri::XML::Node#text?
53
67
  TEXT_NODE = 3
54
- # CDATA node type, see {Nokogiri::XML::Node#cdata?}
68
+ # CDATA node type, see Nokogiri::XML::Node#cdata?
55
69
  CDATA_SECTION_NODE = 4
56
70
  # Entity reference node type
57
71
  ENTITY_REF_NODE = 5
@@ -59,9 +73,9 @@ module Nokogiri
59
73
  ENTITY_NODE = 6
60
74
  # PI node type
61
75
  PI_NODE = 7
62
- # Comment node type, see {Nokogiri::XML::Node#comment?}
76
+ # Comment node type, see Nokogiri::XML::Node#comment?
63
77
  COMMENT_NODE = 8
64
- # Document node type, see {Nokogiri::XML::Node#xml?}
78
+ # Document node type, see Nokogiri::XML::Node#xml?
65
79
  DOCUMENT_NODE = 9
66
80
  # Document type node type
67
81
  DOCUMENT_TYPE_NODE = 10
@@ -69,7 +83,7 @@ module Nokogiri
69
83
  DOCUMENT_FRAG_NODE = 11
70
84
  # Notation node type
71
85
  NOTATION_NODE = 12
72
- # HTML document node type, see {Nokogiri::XML::Node#html?}
86
+ # HTML document node type, see Nokogiri::XML::Node#html?
73
87
  HTML_DOCUMENT_NODE = 13
74
88
  # DTD node type
75
89
  DTD_NODE = 14
@@ -88,13 +102,27 @@ module Nokogiri
88
102
  # DOCB document node type
89
103
  DOCB_DOCUMENT_NODE = 21
90
104
 
91
- ##
92
- # Create a new node with +name+ sharing GC lifecycle with +document+.
93
- # @param name [String]
94
- # @param document [Nokogiri::XML::Document]
95
- # @yieldparam node [Nokogiri::XML::Node]
96
- # @return [Nokogiri::XML::Node]
97
- # @see Nokogiri::XML::Node.new
105
+ #
106
+ # :call-seq:
107
+ # new(name, document) -> Nokogiri::XML::Node
108
+ # new(name, document) { |node| ... } -> Nokogiri::XML::Node
109
+ #
110
+ # Create a new node with +name+ that belongs to +document+.
111
+ #
112
+ # If you intend to add a node to a document tree, it's likely that you will prefer one of the
113
+ # Nokogiri::XML::Node methods like #add_child, #add_next_sibling, #replace, etc. which will
114
+ # both create an element (or subtree) and place it in the document tree.
115
+ #
116
+ # Another alternative, if you are concerned about performance, is
117
+ # Nokogiri::XML::Document#create_element which accepts additional arguments for contents or
118
+ # attributes but (like this method) avoids parsing markup.
119
+ #
120
+ # [Parameters]
121
+ # - +name+ (String)
122
+ # - +document+ (Nokogiri::XML::Document) The document to which the the returned node will belong.
123
+ # [Yields] Nokogiri::XML::Node
124
+ # [Returns] Nokogiri::XML::Node
125
+ #
98
126
  def initialize(name, document)
99
127
  # This is intentionally empty.
100
128
  end
@@ -105,18 +133,7 @@ module Nokogiri
105
133
  document.decorate(self)
106
134
  end
107
135
 
108
- # @!group Searching via XPath or CSS Queries
109
-
110
- ###
111
- # Search this node's immediate children using CSS selector +selector+
112
- def >(selector)
113
- ns = document.root.namespaces
114
- xpath CSS.xpath_for(selector, :prefix => "./", :ns => ns).first
115
- end
116
-
117
- # @!endgroup
118
-
119
- # @!group Manipulating Document Structure
136
+ # :section: Manipulating Document Structure
120
137
 
121
138
  ###
122
139
  # Add +node_or_tags+ as a child of this Node.
@@ -128,9 +145,9 @@ module Nokogiri
128
145
  def add_child(node_or_tags)
129
146
  node_or_tags = coerce(node_or_tags)
130
147
  if node_or_tags.is_a?(XML::NodeSet)
131
- node_or_tags.each { |n| add_child_node_and_reparent_attrs n }
148
+ node_or_tags.each { |n| add_child_node_and_reparent_attrs(n) }
132
149
  else
133
- add_child_node_and_reparent_attrs node_or_tags
150
+ add_child_node_and_reparent_attrs(node_or_tags)
134
151
  end
135
152
  node_or_tags
136
153
  end
@@ -143,9 +160,9 @@ module Nokogiri
143
160
  #
144
161
  # Also see related method +add_child+.
145
162
  def prepend_child(node_or_tags)
146
- if first = children.first
163
+ if (first = children.first)
147
164
  # Mimic the error add_child would raise.
148
- raise RuntimeError, "Document already has a root node" if document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
165
+ raise "Document already has a root node" if document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
149
166
  first.__send__(:add_sibling, :previous, node_or_tags)
150
167
  else
151
168
  add_child(node_or_tags)
@@ -171,7 +188,7 @@ module Nokogiri
171
188
  #
172
189
  # Also see related method +add_child+.
173
190
  def <<(node_or_tags)
174
- add_child node_or_tags
191
+ add_child(node_or_tags)
175
192
  self
176
193
  end
177
194
 
@@ -183,9 +200,10 @@ module Nokogiri
183
200
  #
184
201
  # Also see related method +before+.
185
202
  def add_previous_sibling(node_or_tags)
186
- raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
203
+ raise ArgumentError,
204
+ "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
187
205
 
188
- add_sibling :previous, node_or_tags
206
+ add_sibling(:previous, node_or_tags)
189
207
  end
190
208
 
191
209
  ###
@@ -196,9 +214,10 @@ module Nokogiri
196
214
  #
197
215
  # Also see related method +after+.
198
216
  def add_next_sibling(node_or_tags)
199
- raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
217
+ raise ArgumentError,
218
+ "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
200
219
 
201
- add_sibling :next, node_or_tags
220
+ add_sibling(:next, node_or_tags)
202
221
  end
203
222
 
204
223
  ####
@@ -209,7 +228,7 @@ module Nokogiri
209
228
  #
210
229
  # Also see related method +add_previous_sibling+.
211
230
  def before(node_or_tags)
212
- add_previous_sibling node_or_tags
231
+ add_previous_sibling(node_or_tags)
213
232
  self
214
233
  end
215
234
 
@@ -221,7 +240,7 @@ module Nokogiri
221
240
  #
222
241
  # Also see related method +add_next_sibling+.
223
242
  def after(node_or_tags)
224
- add_next_sibling node_or_tags
243
+ add_next_sibling(node_or_tags)
225
244
  self
226
245
  end
227
246
 
@@ -229,30 +248,24 @@ module Nokogiri
229
248
  # Set the inner html for this Node to +node_or_tags+
230
249
  # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a string containing markup.
231
250
  #
232
- # Returns self.
233
- #
234
251
  # Also see related method +children=+
235
252
  def inner_html=(node_or_tags)
236
253
  self.children = node_or_tags
237
- self
238
254
  end
239
255
 
240
256
  ####
241
257
  # Set the inner html for this Node +node_or_tags+
242
258
  # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a string containing markup.
243
259
  #
244
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
245
- #
246
260
  # Also see related method +inner_html=+
247
261
  def children=(node_or_tags)
248
262
  node_or_tags = coerce(node_or_tags)
249
263
  children.unlink
250
264
  if node_or_tags.is_a?(XML::NodeSet)
251
- node_or_tags.each { |n| add_child_node_and_reparent_attrs n }
265
+ node_or_tags.each { |n| add_child_node_and_reparent_attrs(n) }
252
266
  else
253
- add_child_node_and_reparent_attrs node_or_tags
267
+ add_child_node_and_reparent_attrs(node_or_tags)
254
268
  end
255
- node_or_tags
256
269
  end
257
270
 
258
271
  ####
@@ -270,19 +283,19 @@ module Nokogiri
270
283
  # libxml is trying to find a parent node that is an element or document
271
284
  # so I can't tell if this is bug in libxml or not. issue #775.
272
285
  if text?
273
- replacee = Nokogiri::XML::Node.new "dummy", document
274
- add_previous_sibling_node replacee
286
+ replacee = Nokogiri::XML::Node.new("dummy", document)
287
+ add_previous_sibling_node(replacee)
275
288
  unlink
276
- return replacee.replace node_or_tags
289
+ return replacee.replace(node_or_tags)
277
290
  end
278
291
 
279
292
  node_or_tags = parent.coerce(node_or_tags)
280
293
 
281
294
  if node_or_tags.is_a?(XML::NodeSet)
282
- node_or_tags.each { |n| add_previous_sibling n }
295
+ node_or_tags.each { |n| add_previous_sibling(n) }
283
296
  unlink
284
297
  else
285
- replace_node node_or_tags
298
+ replace_node(node_or_tags)
286
299
  end
287
300
  node_or_tags
288
301
  end
@@ -295,7 +308,7 @@ module Nokogiri
295
308
  #
296
309
  # Also see related method +replace+.
297
310
  def swap(node_or_tags)
298
- replace node_or_tags
311
+ replace(node_or_tags)
299
312
  self
300
313
  end
301
314
 
@@ -309,7 +322,6 @@ module Nokogiri
309
322
  # Set the parent Node for this Node
310
323
  def parent=(parent_node)
311
324
  parent_node.add_child(self)
312
- parent_node
313
325
  end
314
326
 
315
327
  ###
@@ -338,7 +350,7 @@ module Nokogiri
338
350
  raise ArgumentError, "namespace must be declared on the same document"
339
351
  end
340
352
 
341
- set_namespace ns
353
+ set_namespace(ns)
342
354
  end
343
355
 
344
356
  ###
@@ -347,52 +359,159 @@ module Nokogiri
347
359
  # passed to it, allowing more convenient modification of the parser options.
348
360
  def do_xinclude(options = XML::ParseOptions::DEFAULT_XML)
349
361
  options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
350
-
351
- # give options to user
352
362
  yield options if block_given?
353
363
 
354
364
  # call c extension
355
365
  process_xincludes(options.to_i)
356
366
  end
357
367
 
358
- alias :next :next_sibling
359
- alias :previous :previous_sibling
360
- alias :next= :add_next_sibling
361
- alias :previous= :add_previous_sibling
362
- alias :remove :unlink
363
- alias :name= :node_name=
364
- alias :add_namespace :add_namespace_definition
368
+ alias_method :next, :next_sibling
369
+ alias_method :previous, :previous_sibling
370
+ alias_method :next=, :add_next_sibling
371
+ alias_method :previous=, :add_previous_sibling
372
+ alias_method :remove, :unlink
373
+ alias_method :name=, :node_name=
374
+ alias_method :add_namespace, :add_namespace_definition
365
375
 
366
- # @!endgroup
376
+ # :section:
367
377
 
368
- alias :text :content
369
- alias :inner_text :content
370
- alias :name :node_name
371
- alias :type :node_type
372
- alias :to_str :text
373
- alias :clone :dup
374
- alias :elements :element_children
378
+ alias_method :inner_text, :content
379
+ alias_method :text, :content
380
+ alias_method :to_str, :content
381
+ alias_method :name, :node_name
382
+ alias_method :type, :node_type
383
+ alias_method :clone, :dup
384
+ alias_method :elements, :element_children
375
385
 
376
- # @!group Working With Node Attributes
386
+ # :section: Working With Node Attributes
377
387
 
378
- ###
379
- # Get the attribute value for the attribute +name+
388
+ # :call-seq: [](name) → (String, nil)
389
+ #
390
+ # Fetch an attribute from this node.
391
+ #
392
+ # ⚠ Note that attributes with namespaces cannot be accessed with this method. To access
393
+ # namespaced attributes, use #attribute_with_ns.
394
+ #
395
+ # [Returns] (String, nil) value of the attribute +name+, or +nil+ if no matching attribute exists
396
+ #
397
+ # *Example*
398
+ #
399
+ # doc = Nokogiri::XML("<root><child size='large' class='big wide tall'/></root>")
400
+ # child = doc.at_css("child")
401
+ # child["size"] # => "large"
402
+ # child["class"] # => "big wide tall"
403
+ #
404
+ # *Example:* Namespaced attributes will not be returned.
405
+ #
406
+ # ⚠ Note namespaced attributes may be accessed with #attribute or #attribute_with_ns
407
+ #
408
+ # doc = Nokogiri::XML(<<~EOF)
409
+ # <root xmlns:width='http://example.com/widths'>
410
+ # <child width:size='broad'/>
411
+ # </root>
412
+ # EOF
413
+ # doc.at_css("child")["size"] # => nil
414
+ # doc.at_css("child").attribute("size").value # => "broad"
415
+ # doc.at_css("child").attribute_with_ns("size", "http://example.com/widths").value
416
+ # # => "broad"
417
+ #
380
418
  def [](name)
381
419
  get(name.to_s)
382
420
  end
383
421
 
384
- ###
385
- # Set the attribute value for the attribute +name+ to +value+
422
+ # :call-seq: []=(name, value) → value
423
+ #
424
+ # Update the attribute +name+ to +value+, or create the attribute if it does not exist.
425
+ #
426
+ # ⚠ Note that attributes with namespaces cannot be accessed with this method. To access
427
+ # namespaced attributes for update, use #attribute_with_ns. To add a namespaced attribute,
428
+ # see the example below.
429
+ #
430
+ # [Returns] +value+
431
+ #
432
+ # *Example*
433
+ #
434
+ # doc = Nokogiri::XML("<root><child/></root>")
435
+ # child = doc.at_css("child")
436
+ # child["size"] = "broad"
437
+ # child.to_html
438
+ # # => "<child size=\"broad\"></child>"
439
+ #
440
+ # *Example:* Add a namespaced attribute.
441
+ #
442
+ # doc = Nokogiri::XML(<<~EOF)
443
+ # <root xmlns:width='http://example.com/widths'>
444
+ # <child/>
445
+ # </root>
446
+ # EOF
447
+ # child = doc.at_css("child")
448
+ # child["size"] = "broad"
449
+ # ns = doc.root.namespace_definitions.find { |ns| ns.prefix == "width" }
450
+ # child.attribute("size").namespace = ns
451
+ # doc.to_html
452
+ # # => "<root xmlns:width=\"http://example.com/widths\">\n" +
453
+ # # " <child width:size=\"broad\"></child>\n" +
454
+ # # "</root>\n"
455
+ #
386
456
  def []=(name, value)
387
- set name.to_s, value.to_s
457
+ set(name.to_s, value.to_s)
388
458
  end
389
459
 
390
- ####
391
- # Returns a hash containing the node's attributes. The key is
392
- # the attribute name without any namespace, the value is a Nokogiri::XML::Attr
393
- # representing the attribute.
394
- # If you need to distinguish attributes with the same name, with different namespaces
395
- # use #attribute_nodes instead.
460
+ #
461
+ # :call-seq: attributes() Hash<String Nokogiri::XML::Attr>
462
+ #
463
+ # Fetch this node's attributes.
464
+ #
465
+ # Because the keys do not include any namespace information for the attribute, in case of a
466
+ # simple name collision, not all attributes will be returned. In this case, you will need to
467
+ # use #attribute_nodes.
468
+ #
469
+ # [Returns]
470
+ # Hash containing attributes belonging to +self+. The hash keys are String attribute
471
+ # names (without the namespace), and the hash values are Nokogiri::XML::Attr.
472
+ #
473
+ # *Example* with no namespaces:
474
+ #
475
+ # doc = Nokogiri::XML("<root><child size='large' class='big wide tall'/></root>")
476
+ # doc.at_css("child").attributes
477
+ # # => {"size"=>#(Attr:0x550 { name = "size", value = "large" }),
478
+ # # "class"=>#(Attr:0x564 { name = "class", value = "big wide tall" })}
479
+ #
480
+ # *Example* with a namespace:
481
+ #
482
+ # doc = Nokogiri::XML("<root xmlns:desc='http://example.com/sizes'><child desc:size='large'/></root>")
483
+ # doc.at_css("child").attributes
484
+ # # => {"size"=>
485
+ # # #(Attr:0x550 {
486
+ # # name = "size",
487
+ # # namespace = #(Namespace:0x564 {
488
+ # # prefix = "desc",
489
+ # # href = "http://example.com/sizes"
490
+ # # }),
491
+ # # value = "large"
492
+ # # })}
493
+ #
494
+ # *Example* with an attribute name collision:
495
+ #
496
+ # ⚠ Note that only one of the attributes is returned in the Hash.
497
+ #
498
+ # doc = Nokogiri::XML(<<~EOF)
499
+ # <root xmlns:width='http://example.com/widths'
500
+ # xmlns:height='http://example.com/heights'>
501
+ # <child width:size='broad' height:size='tall'/>
502
+ # </root>
503
+ # EOF
504
+ # doc.at_css("child").attributes
505
+ # # => {"size"=>
506
+ # # #(Attr:0x550 {
507
+ # # name = "size",
508
+ # # namespace = #(Namespace:0x564 {
509
+ # # prefix = "height",
510
+ # # href = "http://example.com/heights"
511
+ # # }),
512
+ # # value = "tall"
513
+ # # })}
514
+ #
396
515
  def attributes
397
516
  attribute_nodes.each_with_object({}) do |node, hash|
398
517
  hash[node.node_name] = node
@@ -408,7 +527,7 @@ module Nokogiri
408
527
  ###
409
528
  # Does this Node's attributes include <value>
410
529
  def value?(value)
411
- values.include? value
530
+ values.include?(value)
412
531
  end
413
532
 
414
533
  ###
@@ -420,36 +539,36 @@ module Nokogiri
420
539
  ###
421
540
  # Iterate over each attribute name and value pair for this Node.
422
541
  def each
423
- attribute_nodes.each { |node|
542
+ attribute_nodes.each do |node|
424
543
  yield [node.node_name, node.value]
425
- }
544
+ end
426
545
  end
427
546
 
428
547
  ###
429
548
  # Remove the attribute named +name+
430
549
  def remove_attribute(name)
431
- attr = attributes[name].remove if key? name
550
+ attr = attributes[name].remove if key?(name)
432
551
  clear_xpath_context if Nokogiri.jruby?
433
552
  attr
434
553
  end
435
554
 
436
- # Get the CSS class names of a Node.
555
+ #
556
+ # :call-seq: classes() → Array<String>
557
+ #
558
+ # Fetch CSS class names of a Node.
437
559
  #
438
560
  # This is a convenience function and is equivalent to:
561
+ #
439
562
  # node.kwattr_values("class")
440
563
  #
441
- # @see #kwattr_values
442
- # @see #add_class
443
- # @see #append_class
444
- # @see #remove_class
564
+ # See related: #kwattr_values, #add_class, #append_class, #remove_class
445
565
  #
446
- # @return [Array<String>]
566
+ # [Returns]
567
+ # The CSS classes (Array of String) present in the Node's "class" attribute. If the
568
+ # attribute is empty or non-existent, the return value is an empty array.
447
569
  #
448
- # The CSS classes present in the Node's +class+ attribute. If
449
- # the attribute is empty or non-existent, the return value is
450
- # an empty array.
570
+ # *Example*
451
571
  #
452
- # @example
453
572
  # node # => <div class="section title header"></div>
454
573
  # node.classes # => ["section", "title", "header"]
455
574
  #
@@ -457,42 +576,45 @@ module Nokogiri
457
576
  kwattr_values("class")
458
577
  end
459
578
 
460
- # Ensure HTML CSS classes are present on a +Node+. Any CSS
461
- # classes in +names+ that already exist in the +Node+'s +class+
462
- # attribute are _not_ added. Note that any existing duplicates
463
- # in the +class+ attribute are not removed. Compare with
464
- # {#append_class}.
579
+ #
580
+ # :call-seq: add_class(names) self
581
+ #
582
+ # Ensure HTML CSS classes are present on +self+. Any CSS classes in +names+ that already exist
583
+ # in the "class" attribute are _not_ added. Note that any existing duplicates in the
584
+ # "class" attribute are not removed. Compare with #append_class.
465
585
  #
466
586
  # This is a convenience function and is equivalent to:
587
+ #
467
588
  # node.kwattr_add("class", names)
468
589
  #
469
- # @see #kwattr_add
470
- # @see #classes
471
- # @see #append_class
472
- # @see #remove_class
590
+ # See related: #kwattr_add, #classes, #append_class, #remove_class
591
+ #
592
+ # [Parameters]
593
+ # - +names+ (String, Array<String>)
473
594
  #
474
- # @param names [String, Array<String>]
595
+ # CSS class names to be added to the Node's "class" attribute. May be a string containing
596
+ # whitespace-delimited names, or an Array of String names. Any class names already present
597
+ # will not be added. Any class names not present will be added. If no "class" attribute
598
+ # exists, one is created.
475
599
  #
476
- # CSS class names to be added to the Node's +class+
477
- # attribute. May be a string containing whitespace-delimited
478
- # names, or an Array of String names. Any class names already
479
- # present will not be added. Any class names not present will
480
- # be added. If no +class+ attribute exists, one is created.
600
+ # [Returns] +self+ (Node) for ease of chaining method calls.
481
601
  #
482
- # @return [Node] Returns +self+ for ease of chaining method calls.
602
+ # *Example:* Ensure that the node has CSS class "section"
483
603
  #
484
- # @example Ensure that a +Node+ has CSS class "section"
485
604
  # node # => <div></div>
486
605
  # node.add_class("section") # => <div class="section"></div>
487
606
  # node.add_class("section") # => <div class="section"></div> # duplicate not added
488
607
  #
489
- # @example Ensure that a +Node+ has CSS classes "section" and "header", via a String argument.
608
+ # *Example:* Ensure that the node has CSS classes "section" and "header", via a String argument
609
+ #
610
+ # Note that the CSS class "section" is not added because it is already present.
611
+ # Note also that the pre-existing duplicate CSS class "section" is not removed.
612
+ #
490
613
  # node # => <div class="section section"></div>
491
614
  # node.add_class("section header") # => <div class="section section header"></div>
492
- # # Note that the CSS class "section" is not added because it is already present.
493
- # # Note also that the pre-existing duplicate CSS class "section" is not removed.
494
615
  #
495
- # @example Ensure that a +Node+ has CSS classes "section" and "header", via an Array argument.
616
+ # *Example:* Ensure that the node has CSS classes "section" and "header", via an Array argument
617
+ #
496
618
  # node # => <div></div>
497
619
  # node.add_class(["section", "header"]) # => <div class="section header"></div>
498
620
  #
@@ -500,39 +622,42 @@ module Nokogiri
500
622
  kwattr_add("class", names)
501
623
  end
502
624
 
503
- # Add HTML CSS classes to a +Node+, regardless of
504
- # duplication. Compare with {#add_class}.
625
+ #
626
+ # :call-seq: append_class(names) self
627
+ #
628
+ # Add HTML CSS classes to +self+, regardless of duplication. Compare with #add_class.
505
629
  #
506
630
  # This is a convenience function and is equivalent to:
631
+ #
507
632
  # node.kwattr_append("class", names)
508
633
  #
509
- # @see #kwattr_append
510
- # @see #classes
511
- # @see #add_class
512
- # @see #remove_class
634
+ # See related: #kwattr_append, #classes, #add_class, #remove_class
635
+ #
636
+ # [Parameters]
637
+ # - +names+ (String, Array<String>)
513
638
  #
514
- # @param names [String, Array<String>]
639
+ # CSS class names to be appended to the Node's "class" attribute. May be a string containing
640
+ # whitespace-delimited names, or an Array of String names. All class names passed in will be
641
+ # appended to the "class" attribute even if they are already present in the attribute
642
+ # value. If no "class" attribute exists, one is created.
515
643
  #
516
- # CSS class names to be appended to the Node's +class+
517
- # attribute. May be a string containing whitespace-delimited
518
- # names, or an Array of String names. All class names passed
519
- # in will be appended to the +class+ attribute even if they
520
- # are already present in the attribute value. If no +class+
521
- # attribute exists, one is created.
644
+ # [Returns] +self+ (Node) for ease of chaining method calls.
522
645
  #
523
- # @return [Node] Returns +self+ for ease of chaining method calls.
646
+ # *Example:* Append "section" to the node's CSS "class" attribute
524
647
  #
525
- # @example Append "section" to a +Node+'s CSS +class+ attriubute
526
648
  # node # => <div></div>
527
649
  # node.append_class("section") # => <div class="section"></div>
528
650
  # node.append_class("section") # => <div class="section section"></div> # duplicate added!
529
651
  #
530
- # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via a String argument.
652
+ # *Example:* Append "section" and "header" to the noded's CSS "class" attribute, via a String argument
653
+ #
654
+ # Note that the CSS class "section" is appended even though it is already present
655
+ #
531
656
  # node # => <div class="section section"></div>
532
657
  # node.append_class("section header") # => <div class="section section section header"></div>
533
- # # Note that the CSS class "section" is appended even though it is already present.
534
658
  #
535
- # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via an Array argument.
659
+ # *Example:* Append "section" and "header" to the node's CSS "class" attribute, via an Array argument
660
+ #
536
661
  # node # => <div></div>
537
662
  # node.append_class(["section", "header"]) # => <div class="section header"></div>
538
663
  # node.append_class(["section", "header"]) # => <div class="section header section header"></div>
@@ -541,118 +666,135 @@ module Nokogiri
541
666
  kwattr_append("class", names)
542
667
  end
543
668
 
544
- # Remove HTML CSS classes from a +Node+. Any CSS classes in +names+ that
545
- # exist in the +Node+'s +class+ attribute are removed, including any
546
- # multiple entries.
669
+ # :call-seq:
670
+ # remove_class(css_classes) self
671
+ #
672
+ # Remove HTML CSS classes from this node. Any CSS class names in +css_classes+ that exist in
673
+ # this node's "class" attribute are removed, including any multiple entries.
547
674
  #
548
- # If no CSS classes remain after this operation, or if +names+ is
549
- # +nil+, the +class+ attribute is deleted from the node.
675
+ # If no CSS classes remain after this operation, or if +css_classes+ is +nil+, the "class"
676
+ # attribute is deleted from the node.
550
677
  #
551
678
  # This is a convenience function and is equivalent to:
552
- # node.kwattr_remove("class", names)
553
679
  #
554
- # @see #kwattr_remove
555
- # @see #classes
556
- # @see #add_class
557
- # @see #append_class
680
+ # node.kwattr_remove("class", css_classes)
558
681
  #
559
- # @param names [String, Array<String>]
682
+ # Also see #kwattr_remove, #classes, #add_class, #append_class
560
683
  #
561
- # CSS class names to be removed from the Node's +class+ attribute. May
562
- # be a string containing whitespace-delimited names, or an Array of
563
- # String names. Any class names already present will be removed. If no
564
- # CSS classes remain, the +class+ attribute is deleted.
684
+ # [Parameters]
685
+ # - +css_classes+ (String, Array<String>)
565
686
  #
566
- # @return [Node] Returns +self+ for ease of chaining method calls.
687
+ # CSS class names to be removed from the Node's
688
+ # "class" attribute. May be a string containing whitespace-delimited names, or an Array of
689
+ # String names. Any class names already present will be removed. If no CSS classes remain,
690
+ # the "class" attribute is deleted.
567
691
  #
568
- # @example
569
- # node # => <div class="section header"></div>
692
+ # [Returns] +self+ (Nokogiri::XML::Node) for ease of chaining method calls.
693
+ #
694
+ # *Example*: Deleting a CSS class
695
+ #
696
+ # Note that all instances of the class "section" are removed from the "class" attribute.
697
+ #
698
+ # node # => <div class="section header section"></div>
570
699
  # node.remove_class("section") # => <div class="header"></div>
571
- # node.remove_class("header") # => <div></div> # attribute is deleted when empty
700
+ #
701
+ # *Example*: Deleting the only remaining CSS class
702
+ #
703
+ # Note that the attribute is removed once there are no remaining classes.
704
+ #
705
+ # node # => <div class="section"></div>
706
+ # node.remove_class("section") # => <div></div>
707
+ #
708
+ # *Example*: Deleting multiple CSS classes
709
+ #
710
+ # Note that the "class" attribute is deleted once it's empty.
711
+ #
712
+ # node # => <div class="section header float"></div>
713
+ # node.remove_class(["section", "float"]) # => <div class="header"></div>
572
714
  #
573
715
  def remove_class(names = nil)
574
716
  kwattr_remove("class", names)
575
717
  end
576
718
 
577
- # Retrieve values from a keyword attribute of a Node.
719
+ # :call-seq:
720
+ # kwattr_values(attribute_name) → Array<String>
721
+ #
722
+ # Fetch values from a keyword attribute of a Node.
578
723
  #
579
- # A "keyword attribute" is a node attribute that contains a set
580
- # of space-delimited values. Perhaps the most familiar example
581
- # of this is the HTML +class+ attribute used to contain CSS
582
- # classes. But other keyword attributes exist, for instance
583
- # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
724
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
725
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
726
+ # contain CSS classes. But other keyword attributes exist, for instance
727
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
584
728
  #
585
- # @see #classes
586
- # @see #kwattr_add
587
- # @see #kwattr_append
588
- # @see #kwattr_remove
729
+ # See also #classes, #kwattr_add, #kwattr_append, #kwattr_remove
589
730
  #
590
- # @param attribute_name [String] The name of the keyword attribute to be inspected.
731
+ # [Parameters]
732
+ # - +attribute_name+ (String) The name of the keyword attribute to be inspected.
591
733
  #
592
- # @return [Array<String>]
734
+ # [Returns]
735
+ # (Array<String>) The values present in the Node's +attribute_name+ attribute. If the
736
+ # attribute is empty or non-existent, the return value is an empty array.
593
737
  #
594
- # The values present in the Node's +attribute_name+
595
- # attribute. If the attribute is empty or non-existent, the
596
- # return value is an empty array.
738
+ # *Example:*
597
739
  #
598
- # @example
599
740
  # node # => <a rel="nofollow noopener external">link</a>
600
741
  # node.kwattr_values("rel") # => ["nofollow", "noopener", "external"]
601
742
  #
602
- # @since v1.11.0
603
- #
743
+ # Since v1.11.0
604
744
  def kwattr_values(attribute_name)
605
745
  keywordify(get_attribute(attribute_name) || [])
606
746
  end
607
747
 
748
+ # :call-seq:
749
+ # kwattr_add(attribute_name, keywords) → self
750
+ #
608
751
  # Ensure that values are present in a keyword attribute.
609
752
  #
610
- # Any values in +keywords+ that already exist in the +Node+'s
611
- # attribute values are _not_ added. Note that any existing
612
- # duplicates in the attribute values are not removed. Compare
613
- # with {#kwattr_append}.
753
+ # Any values in +keywords+ that already exist in the Node's attribute values are _not_
754
+ # added. Note that any existing duplicates in the attribute values are not removed. Compare
755
+ # with #kwattr_append.
614
756
  #
615
- # A "keyword attribute" is a node attribute that contains a set
616
- # of space-delimited values. Perhaps the most familiar example
617
- # of this is the HTML +class+ attribute used to contain CSS
618
- # classes. But other keyword attributes exist, for instance
619
- # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
757
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
758
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
759
+ # contain CSS classes. But other keyword attributes exist, for instance
760
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
620
761
  #
621
- # @see #add_class
622
- # @see #kwattr_values
623
- # @see #kwattr_append
624
- # @see #kwattr_remove
762
+ # See also #add_class, #kwattr_values, #kwattr_append, #kwattr_remove
625
763
  #
626
- # @param attribute_name [String] The name of the keyword attribute to be modified.
764
+ # [Parameters]
765
+ # - +attribute_name+ (String) The name of the keyword attribute to be modified.
766
+ # - +keywords+ (String, Array<String>)
767
+ # Keywords to be added to the attribute named +attribute_name+. May be a string containing
768
+ # whitespace-delimited values, or an Array of String values. Any values already present will
769
+ # not be added. Any values not present will be added. If the named attribute does not exist,
770
+ # it is created.
627
771
  #
628
- # @param keywords [String, Array<String>]
772
+ # [Returns] +self+ (Nokogiri::XML::Node) for ease of chaining method calls.
629
773
  #
630
- # Keywords to be added to the attribute named
631
- # +attribute_name+. May be a string containing
632
- # whitespace-delimited values, or an Array of String
633
- # values. Any values already present will not be added. Any
634
- # values not present will be added. If the named attribute
635
- # does not exist, it is created.
774
+ # *Example:* Ensure that a +Node+ has "nofollow" in its +rel+ attribute.
636
775
  #
637
- # @return [Node] Returns +self+ for ease of chaining method calls.
776
+ # Note that duplicates are not added.
638
777
  #
639
- # @example Ensure that a +Node+ has "nofollow" in its +rel+ attribute.
640
778
  # node # => <a></a>
641
779
  # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a>
642
- # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a> # duplicate not added
780
+ # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a>
781
+ #
782
+ # *Example:* Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via a
783
+ # String argument.
784
+ #
785
+ # Note that "nofollow" is not added because it is already present. Note also that the
786
+ # pre-existing duplicate "nofollow" is not removed.
643
787
  #
644
- # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via a String argument.
645
788
  # node # => <a rel="nofollow nofollow"></a>
646
789
  # node.kwattr_add("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
647
- # # Note that "nofollow" is not added because it is already present.
648
- # # Note also that the pre-existing duplicate "nofollow" is not removed.
649
790
  #
650
- # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via an Array argument.
791
+ # *Example:* Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via
792
+ # an Array argument.
793
+ #
651
794
  # node # => <a></a>
652
795
  # node.kwattr_add("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
653
796
  #
654
- # @since v1.11.0
655
- #
797
+ # Since v1.11.0
656
798
  def kwattr_add(attribute_name, keywords)
657
799
  keywords = keywordify(keywords)
658
800
  current_kws = kwattr_values(attribute_name)
@@ -661,50 +803,51 @@ module Nokogiri
661
803
  self
662
804
  end
663
805
 
664
- # Add keywords to a Node's keyword attribute, regardless of
665
- # duplication. Compare with {#kwattr_add}.
806
+ # :call-seq:
807
+ # kwattr_append(attribute_name, keywords) self
666
808
  #
667
- # A "keyword attribute" is a node attribute that contains a set
668
- # of space-delimited values. Perhaps the most familiar example
669
- # of this is the HTML +class+ attribute used to contain CSS
670
- # classes. But other keyword attributes exist, for instance
671
- # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
809
+ # Add keywords to a Node's keyword attribute, regardless of duplication. Compare with
810
+ # #kwattr_add.
672
811
  #
673
- # @see #append_class
674
- # @see #kwattr_values
675
- # @see #kwattr_add
676
- # @see #kwattr_remove
812
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
813
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
814
+ # contain CSS classes. But other keyword attributes exist, for instance
815
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
677
816
  #
678
- # @param attribute_name [String] The name of the keyword attribute to be modified.
817
+ # See also #append_class, #kwattr_values, #kwattr_add, #kwattr_remove
679
818
  #
680
- # @param keywords [String, Array<String>]
819
+ # [Parameters]
820
+ # - +attribute_name+ (String) The name of the keyword attribute to be modified.
821
+ # - +keywords+ (String, Array<String>)
822
+ # Keywords to be added to the attribute named +attribute_name+. May be a string containing
823
+ # whitespace-delimited values, or an Array of String values. All values passed in will be
824
+ # appended to the named attribute even if they are already present in the attribute. If the
825
+ # named attribute does not exist, it is created.
681
826
  #
682
- # Keywords to be added to the attribute named
683
- # +attribute_name+. May be a string containing
684
- # whitespace-delimited values, or an Array of String
685
- # values. All values passed in will be appended to the named
686
- # attribute even if they are already present in the
687
- # attribute. If the named attribute does not exist, it is
688
- # created.
827
+ # [Returns] +self+ (Node) for ease of chaining method calls.
689
828
  #
690
- # @return [Node] Returns +self+ for ease of chaining method calls.
829
+ # *Example:* Append "nofollow" to the +rel+ attribute.
830
+ #
831
+ # Note that duplicates are added.
691
832
  #
692
- # @example Append "nofollow" to the +rel+ attribute.
693
833
  # node # => <a></a>
694
834
  # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow"></a>
695
- # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow nofollow"></a> # duplicate added!
835
+ # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow nofollow"></a>
836
+ #
837
+ # *Example:* Append "nofollow" and "noreferrer" to the +rel+ attribute, via a String argument.
838
+ #
839
+ # Note that "nofollow" is appended even though it is already present.
696
840
  #
697
- # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via a String argument.
698
841
  # node # => <a rel="nofollow"></a>
699
842
  # node.kwattr_append("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
700
- # # Note that "nofollow" is appended even though it is already present.
701
843
  #
702
- # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via an Array argument.
844
+ #
845
+ # *Example:* Append "nofollow" and "noreferrer" to the +rel+ attribute, via an Array argument.
846
+ #
703
847
  # node # => <a></a>
704
848
  # node.kwattr_append("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
705
849
  #
706
- # @since v1.11.0
707
- #
850
+ # Since v1.11.0
708
851
  def kwattr_append(attribute_name, keywords)
709
852
  keywords = keywordify(keywords)
710
853
  current_kws = kwattr_values(attribute_name)
@@ -713,44 +856,41 @@ module Nokogiri
713
856
  self
714
857
  end
715
858
 
716
- # Remove keywords from a keyword attribute. Any matching
717
- # keywords that exist in the named attribute are removed,
718
- # including any multiple entries.
859
+ # :call-seq:
860
+ # kwattr_remove(attribute_name, keywords) self
719
861
  #
720
- # If no keywords remain after this operation, or if +keywords+
721
- # is +nil+, the attribute is deleted from the node.
862
+ # Remove keywords from a keyword attribute. Any matching keywords that exist in the named
863
+ # attribute are removed, including any multiple entries.
722
864
  #
723
- # A "keyword attribute" is a node attribute that contains a set
724
- # of space-delimited values. Perhaps the most familiar example
725
- # of this is the HTML +class+ attribute used to contain CSS
726
- # classes. But other keyword attributes exist, for instance
727
- # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
865
+ # If no keywords remain after this operation, or if +keywords+ is +nil+, the attribute is
866
+ # deleted from the node.
728
867
  #
729
- # @see #remove_class
730
- # @see #kwattr_values
731
- # @see #kwattr_add
732
- # @see #kwattr_append
868
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
869
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
870
+ # contain CSS classes. But other keyword attributes exist, for instance
871
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
733
872
  #
734
- # @param attribute_name [String] The name of the keyword attribute to be modified.
873
+ # See also #remove_class, #kwattr_values, #kwattr_add, #kwattr_append
735
874
  #
736
- # @param keywords [String, Array<String>]
875
+ # [Parameters]
876
+ # - +attribute_name+ (String) The name of the keyword attribute to be modified.
877
+ # - +keywords+ (String, Array<String>)
878
+ # Keywords to be removed from the attribute named +attribute_name+. May be a string
879
+ # containing whitespace-delimited values, or an Array of String values. Any keywords present
880
+ # in the named attribute will be removed. If no keywords remain, or if +keywords+ is nil,
881
+ # the attribute is deleted.
737
882
  #
738
- # Keywords to be removed from the attribute named
739
- # +attribute_name+. May be a string containing
740
- # whitespace-delimited values, or an Array of String
741
- # values. Any keywords present in the named attribute will be
742
- # removed. If no keywords remain, or if +keywords+ is nil, the
743
- # attribute is deleted.
883
+ # [Returns] +self+ (Node) for ease of chaining method calls.
744
884
  #
745
- # @return [Node] Returns +self+ for ease of chaining method calls.
885
+ # *Example:*
886
+ #
887
+ # Note that the +rel+ attribute is deleted when empty.
746
888
  #
747
- # @example
748
889
  # node # => <a rel="nofollow noreferrer">link</a>
749
890
  # node.kwattr_remove("rel", "nofollow") # => <a rel="noreferrer">link</a>
750
- # node.kwattr_remove("rel", "noreferrer") # => <a>link</a> # attribute is deleted when empty
751
- #
752
- # @since v1.11.0
891
+ # node.kwattr_remove("rel", "noreferrer") # => <a>link</a>
753
892
  #
893
+ # Since v1.11.0
754
894
  def kwattr_remove(attribute_name, keywords)
755
895
  if keywords.nil?
756
896
  remove_attribute(attribute_name)
@@ -768,13 +908,13 @@ module Nokogiri
768
908
  self
769
909
  end
770
910
 
771
- alias :delete :remove_attribute
772
- alias :get_attribute :[]
773
- alias :attr :[]
774
- alias :set_attribute :[]=
775
- alias :has_attribute? :key?
911
+ alias_method :delete, :remove_attribute
912
+ alias_method :get_attribute, :[]
913
+ alias_method :attr, :[]
914
+ alias_method :set_attribute, :[]=
915
+ alias_method :has_attribute?, :key?
776
916
 
777
- # @!endgroup
917
+ # :section:
778
918
 
779
919
  ###
780
920
  # Returns true if this Node matches +selector+
@@ -786,8 +926,7 @@ module Nokogiri
786
926
  # Create a DocumentFragment containing +tags+ that is relative to _this_
787
927
  # context node.
788
928
  def fragment(tags)
789
- type = document.html? ? Nokogiri::HTML : Nokogiri::XML
790
- type::DocumentFragment.new(document, tags, self)
929
+ document.related_class("DocumentFragment").new(document, tags, self)
791
930
  end
792
931
 
793
932
  ###
@@ -805,19 +944,18 @@ module Nokogiri
805
944
  end
806
945
 
807
946
  options ||= (document.html? ? ParseOptions::DEFAULT_HTML : ParseOptions::DEFAULT_XML)
808
- if Integer === options
809
- options = Nokogiri::XML::ParseOptions.new(options)
810
- end
811
- # Give the options to the user
947
+ options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
812
948
  yield options if block_given?
813
949
 
814
- contents = string_or_io.respond_to?(:read) ?
815
- string_or_io.read :
950
+ contents = if string_or_io.respond_to?(:read)
951
+ string_or_io.read
952
+ else
816
953
  string_or_io
954
+ end
817
955
 
818
956
  return Nokogiri::XML::NodeSet.new(document) if contents.empty?
819
957
 
820
- # libxml2 does not obey the `recover` option after encountering errors during `in_context`
958
+ # libxml2 does not obey the +recover+ option after encountering errors during +in_context+
821
959
  # parsing, and so this horrible hack is here to try to emulate recovery behavior.
822
960
  #
823
961
  # Unfortunately, this means we're no longer parsing "in context" and so namespaces that
@@ -827,16 +965,16 @@ module Nokogiri
827
965
  #
828
966
  # I think preferable behavior would be to either:
829
967
  #
830
- # a. add an error noting that we "fell back" and pointing the user to turning off the `recover` option
968
+ # a. add an error noting that we "fell back" and pointing the user to turning off the +recover+ option
831
969
  # b. don't recover, but raise a sensible exception
832
970
  #
833
971
  # For context and background: https://github.com/sparklemotion/nokogiri/issues/313
834
972
  # FIXME bug report: https://github.com/sparklemotion/nokogiri/issues/2092
835
973
  error_count = document.errors.length
836
974
  node_set = in_context(contents, options.to_i)
837
- if (node_set.empty? && (document.errors.length > error_count))
975
+ if node_set.empty? && (document.errors.length > error_count)
838
976
  if options.recover?
839
- fragment = Nokogiri::HTML4::DocumentFragment.parse contents
977
+ fragment = document.related_class("DocumentFragment").parse(contents)
840
978
  node_set = fragment.children
841
979
  else
842
980
  raise document.errors[error_count]
@@ -845,20 +983,42 @@ module Nokogiri
845
983
  node_set
846
984
  end
847
985
 
848
- ###
849
- # Returns a Hash of +{prefix => value}+ for all namespaces on this
850
- # node and its ancestors.
851
- #
852
- # This method returns the same namespaces as #namespace_scopes.
853
- #
854
- # Returns namespaces in scope for self -- those defined on self
855
- # element directly or any ancestor node -- as a Hash of
856
- # attribute-name/value pairs. Note that the keys in this hash
857
- # XML attributes that would be used to define this namespace,
858
- # such as "xmlns:prefix", not just the prefix. Default namespace
859
- # set on self will be included with key "xmlns". However,
860
- # default namespaces set on ancestor will NOT be, even if self
861
- # has no explicit default namespace.
986
+ # :call-seq:
987
+ # namespaces() Hash<String(Namespace#prefix) String(Namespace#href)>
988
+ #
989
+ # Fetch all the namespaces on this node and its ancestors.
990
+ #
991
+ # Note that the keys in this hash XML attributes that would be used to define this namespace,
992
+ # such as "xmlns:prefix", not just the prefix.
993
+ #
994
+ # The default namespace for this node will be included with key "xmlns".
995
+ #
996
+ # See also #namespace_scopes
997
+ #
998
+ # [Returns]
999
+ # Hash containing all the namespaces on this node and its ancestors. The hash keys are the
1000
+ # namespace prefix, and the hash value for each key is the namespace URI.
1001
+ #
1002
+ # *Example:*
1003
+ #
1004
+ # doc = Nokogiri::XML(<<~EOF)
1005
+ # <root xmlns="http://example.com/root" xmlns:in_scope="http://example.com/in_scope">
1006
+ # <first/>
1007
+ # <second xmlns="http://example.com/child"/>
1008
+ # <third xmlns:foo="http://example.com/foo"/>
1009
+ # </root>
1010
+ # EOF
1011
+ # doc.at_xpath("//root:first", "root" => "http://example.com/root").namespaces
1012
+ # # => {"xmlns"=>"http://example.com/root",
1013
+ # # "xmlns:in_scope"=>"http://example.com/in_scope"}
1014
+ # doc.at_xpath("//child:second", "child" => "http://example.com/child").namespaces
1015
+ # # => {"xmlns"=>"http://example.com/child",
1016
+ # # "xmlns:in_scope"=>"http://example.com/in_scope"}
1017
+ # doc.at_xpath("//root:third", "root" => "http://example.com/root").namespaces
1018
+ # # => {"xmlns:foo"=>"http://example.com/foo",
1019
+ # # "xmlns"=>"http://example.com/root",
1020
+ # # "xmlns:in_scope"=>"http://example.com/in_scope"}
1021
+ #
862
1022
  def namespaces
863
1023
  namespace_scopes.each_with_object({}) do |ns, hash|
864
1024
  prefix = ns.prefix
@@ -882,14 +1042,14 @@ module Nokogiri
882
1042
  type == DOCUMENT_NODE
883
1043
  end
884
1044
 
885
- # Returns true if this is an HTML4::Document node
1045
+ # Returns true if this is an HTML4::Document or HTML5::Document node
886
1046
  def html?
887
1047
  type == HTML_DOCUMENT_NODE
888
1048
  end
889
1049
 
890
1050
  # Returns true if this is a Document
891
1051
  def document?
892
- is_a? XML::Document
1052
+ is_a?(XML::Document)
893
1053
  end
894
1054
 
895
1055
  # Returns true if this is a ProcessingInstruction node
@@ -927,7 +1087,7 @@ module Nokogiri
927
1087
  type == ELEMENT_NODE
928
1088
  end
929
1089
 
930
- alias :elem? :element?
1090
+ alias_method :elem?, :element?
931
1091
 
932
1092
  ###
933
1093
  # Turn this node in to a string. If the document is HTML, this method
@@ -943,9 +1103,9 @@ module Nokogiri
943
1103
 
944
1104
  # Get the path to this node as a CSS expression
945
1105
  def css_path
946
- path.split(/\//).map { |part|
947
- part.length == 0 ? nil : part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
948
- }.compact.join(" > ")
1106
+ path.split(%r{/}).map do |part|
1107
+ part.empty? ? nil : part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
1108
+ end.compact.join(" > ")
949
1109
  end
950
1110
 
951
1111
  ###
@@ -958,7 +1118,7 @@ module Nokogiri
958
1118
  parents = [parent]
959
1119
 
960
1120
  while parents.last.respond_to?(:parent)
961
- break unless ctx_parent = parents.last.parent
1121
+ break unless (ctx_parent = parents.last.parent)
962
1122
  parents << ctx_parent
963
1123
  end
964
1124
 
@@ -967,16 +1127,16 @@ module Nokogiri
967
1127
  root = parents.last
968
1128
  search_results = root.search(selector)
969
1129
 
970
- NodeSet.new(document, parents.find_all { |parent|
1130
+ NodeSet.new(document, parents.find_all do |parent|
971
1131
  search_results.include?(parent)
972
- })
1132
+ end)
973
1133
  end
974
1134
 
975
1135
  ####
976
1136
  # Yields self and all children to +block+ recursively.
977
1137
  def traverse(&block)
978
1138
  children.each { |j| j.traverse(&block) }
979
- block.call(self)
1139
+ yield(self)
980
1140
  end
981
1141
 
982
1142
  ###
@@ -999,10 +1159,10 @@ module Nokogiri
999
1159
  def <=>(other)
1000
1160
  return nil unless other.is_a?(Nokogiri::XML::Node)
1001
1161
  return nil unless document == other.document
1002
- compare other
1162
+ compare(other)
1003
1163
  end
1004
1164
 
1005
- # @!group Serialization and Generating Output
1165
+ # :section: Serialization and Generating Output
1006
1166
 
1007
1167
  ###
1008
1168
  # Serialize Node using +options+. Save options can also be set using a
@@ -1019,18 +1179,22 @@ module Nokogiri
1019
1179
  # end
1020
1180
  #
1021
1181
  def serialize(*args, &block)
1022
- options = args.first.is_a?(Hash) ? args.shift : {
1023
- :encoding => args[0],
1024
- :save_with => args[1],
1025
- }
1182
+ options = if args.first.is_a?(Hash)
1183
+ args.shift
1184
+ else
1185
+ {
1186
+ encoding: args[0],
1187
+ save_with: args[1],
1188
+ }
1189
+ end
1026
1190
 
1027
1191
  encoding = options[:encoding] || document.encoding
1028
1192
  options[:encoding] = encoding
1029
1193
 
1030
- outstring = String.new
1194
+ outstring = +""
1031
1195
  outstring.force_encoding(Encoding.find(encoding || "utf-8"))
1032
1196
  io = StringIO.new(outstring)
1033
- write_to io, options, &block
1197
+ write_to(io, options, &block)
1034
1198
  io.string
1035
1199
  end
1036
1200
 
@@ -1042,7 +1206,7 @@ module Nokogiri
1042
1206
  # See Node#write_to for a list of +options+. For formatted output,
1043
1207
  # use Node#to_xhtml instead.
1044
1208
  def to_html(options = {})
1045
- to_format SaveOptions::DEFAULT_HTML, options
1209
+ to_format(SaveOptions::DEFAULT_HTML, options)
1046
1210
  end
1047
1211
 
1048
1212
  ###
@@ -1063,7 +1227,7 @@ module Nokogiri
1063
1227
  #
1064
1228
  # See Node#write_to for a list of +options+
1065
1229
  def to_xhtml(options = {})
1066
- to_format SaveOptions::DEFAULT_XHTML, options
1230
+ to_format(SaveOptions::DEFAULT_XHTML, options)
1067
1231
  end
1068
1232
 
1069
1233
  ###
@@ -1111,7 +1275,7 @@ module Nokogiri
1111
1275
  #
1112
1276
  # See Node#write_to for a list of +options+
1113
1277
  def write_html_to(io, options = {})
1114
- write_format_to SaveOptions::DEFAULT_HTML, io, options
1278
+ write_format_to(SaveOptions::DEFAULT_HTML, io, options)
1115
1279
  end
1116
1280
 
1117
1281
  ###
@@ -1119,7 +1283,7 @@ module Nokogiri
1119
1283
  #
1120
1284
  # See Node#write_to for a list of +options+
1121
1285
  def write_xhtml_to(io, options = {})
1122
- write_format_to SaveOptions::DEFAULT_XHTML, io, options
1286
+ write_format_to(SaveOptions::DEFAULT_XHTML, io, options)
1123
1287
  end
1124
1288
 
1125
1289
  ###
@@ -1130,7 +1294,7 @@ module Nokogiri
1130
1294
  # See Node#write_to for a list of options
1131
1295
  def write_xml_to(io, options = {})
1132
1296
  options[:save_with] ||= SaveOptions::DEFAULT_XML
1133
- write_to io, options
1297
+ write_to(io, options)
1134
1298
  end
1135
1299
 
1136
1300
  def canonicalize(mode = XML::XML_C14N_1_0, inclusive_namespaces = nil, with_comments = false)
@@ -1141,7 +1305,7 @@ module Nokogiri
1141
1305
  end
1142
1306
  end
1143
1307
 
1144
- # @!endgroup
1308
+ # :section:
1145
1309
 
1146
1310
  protected
1147
1311
 
@@ -1159,9 +1323,9 @@ module Nokogiri
1159
1323
  return data
1160
1324
  end
1161
1325
 
1162
- raise ArgumentError, <<-EOERR
1163
- Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
1164
- (You probably want to select a node from the Document with at() or search(), or create a new Node via Node.new().)
1326
+ raise ArgumentError, <<~EOERR
1327
+ Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
1328
+ (You probably want to select a node from the Document with at() or search(), or create a new Node via Node.new().)
1165
1329
  EOERR
1166
1330
  end
1167
1331
 
@@ -1170,32 +1334,33 @@ Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
1170
1334
  def keywordify(keywords)
1171
1335
  case keywords
1172
1336
  when Enumerable
1173
- return keywords
1337
+ keywords
1174
1338
  when String
1175
- return keywords.scan(/\S+/)
1339
+ keywords.scan(/\S+/)
1176
1340
  else
1177
- raise ArgumentError.new("Keyword attributes must be passed as either a String or an Enumerable, but received #{keywords.class}")
1341
+ raise ArgumentError,
1342
+ "Keyword attributes must be passed as either a String or an Enumerable, but received #{keywords.class}"
1178
1343
  end
1179
1344
  end
1180
1345
 
1181
1346
  def add_sibling(next_or_previous, node_or_tags)
1182
1347
  raise("Cannot add sibling to a node with no parent") unless parent
1183
1348
 
1184
- impl = (next_or_previous == :next) ? :add_next_sibling_node : :add_previous_sibling_node
1185
- iter = (next_or_previous == :next) ? :reverse_each : :each
1349
+ impl = next_or_previous == :next ? :add_next_sibling_node : :add_previous_sibling_node
1350
+ iter = next_or_previous == :next ? :reverse_each : :each
1186
1351
 
1187
1352
  node_or_tags = parent.coerce(node_or_tags)
1188
1353
  if node_or_tags.is_a?(XML::NodeSet)
1189
1354
  if text?
1190
- pivot = Nokogiri::XML::Node.new "dummy", document
1191
- send impl, pivot
1355
+ pivot = Nokogiri::XML::Node.new("dummy", document)
1356
+ send(impl, pivot)
1192
1357
  else
1193
1358
  pivot = self
1194
1359
  end
1195
- node_or_tags.send(iter) { |n| pivot.send impl, n }
1360
+ node_or_tags.send(iter) { |n| pivot.send(impl, n) }
1196
1361
  pivot.unlink if text?
1197
1362
  else
1198
- send impl, node_or_tags
1363
+ send(impl, node_or_tags)
1199
1364
  end
1200
1365
  node_or_tags
1201
1366
  end
@@ -1214,19 +1379,18 @@ Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
1214
1379
  return (io << dump_html) if USING_LIBXML_WITH_BROKEN_SERIALIZATION
1215
1380
 
1216
1381
  options[:save_with] ||= save_option
1217
- write_to io, options
1382
+ write_to(io, options)
1218
1383
  end
1219
1384
 
1220
1385
  def inspect_attributes
1221
1386
  [:name, :namespace, :attribute_nodes, :children]
1222
1387
  end
1223
1388
 
1224
- # @private
1225
- IMPLIED_XPATH_CONTEXTS = [".//".freeze].freeze
1389
+ IMPLIED_XPATH_CONTEXTS = [".//"].freeze
1226
1390
 
1227
1391
  def add_child_node_and_reparent_attrs(node)
1228
- add_child_node node
1229
- node.attribute_nodes.find_all { |a| a.name =~ /:/ }.each do |attr_node|
1392
+ add_child_node(node)
1393
+ node.attribute_nodes.find_all { |a| a.name.include?(":") }.each do |attr_node|
1230
1394
  attr_node.remove
1231
1395
  node[attr_node.name] = attr_node.value
1232
1396
  end