nokogiri 1.12.5 → 1.13.8

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