nokogiri 1.13.6 → 1.14.2

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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +39 -0
  3. data/LICENSE-DEPENDENCIES.md +830 -509
  4. data/LICENSE.md +1 -1
  5. data/README.md +18 -11
  6. data/dependencies.yml +33 -15
  7. data/ext/nokogiri/extconf.rb +100 -24
  8. data/ext/nokogiri/gumbo.c +21 -11
  9. data/ext/nokogiri/html4_document.c +2 -2
  10. data/ext/nokogiri/html4_element_description.c +1 -1
  11. data/ext/nokogiri/html4_entity_lookup.c +2 -2
  12. data/ext/nokogiri/html4_sax_parser_context.c +1 -6
  13. data/ext/nokogiri/html4_sax_push_parser.c +1 -1
  14. data/ext/nokogiri/nokogiri.c +38 -51
  15. data/ext/nokogiri/nokogiri.h +26 -14
  16. data/ext/nokogiri/test_global_handlers.c +1 -1
  17. data/ext/nokogiri/xml_attr.c +3 -3
  18. data/ext/nokogiri/xml_attribute_decl.c +5 -5
  19. data/ext/nokogiri/xml_cdata.c +3 -3
  20. data/ext/nokogiri/xml_comment.c +1 -1
  21. data/ext/nokogiri/xml_document.c +23 -14
  22. data/ext/nokogiri/xml_document_fragment.c +1 -1
  23. data/ext/nokogiri/xml_dtd.c +9 -9
  24. data/ext/nokogiri/xml_element_content.c +3 -3
  25. data/ext/nokogiri/xml_element_decl.c +5 -5
  26. data/ext/nokogiri/xml_encoding_handler.c +3 -3
  27. data/ext/nokogiri/xml_entity_decl.c +6 -6
  28. data/ext/nokogiri/xml_entity_reference.c +1 -1
  29. data/ext/nokogiri/xml_namespace.c +80 -14
  30. data/ext/nokogiri/xml_node.c +363 -82
  31. data/ext/nokogiri/xml_node_set.c +4 -6
  32. data/ext/nokogiri/xml_processing_instruction.c +1 -1
  33. data/ext/nokogiri/xml_reader.c +97 -22
  34. data/ext/nokogiri/xml_relax_ng.c +1 -3
  35. data/ext/nokogiri/xml_sax_parser.c +23 -17
  36. data/ext/nokogiri/xml_sax_parser_context.c +1 -6
  37. data/ext/nokogiri/xml_sax_push_parser.c +1 -3
  38. data/ext/nokogiri/xml_schema.c +4 -6
  39. data/ext/nokogiri/xml_syntax_error.c +1 -1
  40. data/ext/nokogiri/xml_text.c +2 -2
  41. data/ext/nokogiri/xml_xpath_context.c +91 -84
  42. data/ext/nokogiri/xslt_stylesheet.c +15 -14
  43. data/gumbo-parser/Makefile +10 -0
  44. data/gumbo-parser/src/attribute.h +1 -1
  45. data/gumbo-parser/src/error.c +2 -2
  46. data/gumbo-parser/src/error.h +1 -1
  47. data/gumbo-parser/src/foreign_attrs.c +2 -2
  48. data/gumbo-parser/src/{gumbo.h → nokogiri_gumbo.h} +1 -0
  49. data/gumbo-parser/src/parser.c +8 -5
  50. data/gumbo-parser/src/replacement.h +1 -1
  51. data/gumbo-parser/src/string_buffer.h +1 -1
  52. data/gumbo-parser/src/string_piece.c +1 -1
  53. data/gumbo-parser/src/svg_attrs.c +2 -2
  54. data/gumbo-parser/src/svg_tags.c +2 -2
  55. data/gumbo-parser/src/tag.c +2 -1
  56. data/gumbo-parser/src/tag_lookup.c +7 -7
  57. data/gumbo-parser/src/tag_lookup.gperf +1 -0
  58. data/gumbo-parser/src/tag_lookup.h +1 -1
  59. data/gumbo-parser/src/token_buffer.h +1 -1
  60. data/gumbo-parser/src/tokenizer.c +1 -1
  61. data/gumbo-parser/src/tokenizer.h +1 -1
  62. data/gumbo-parser/src/utf8.c +1 -1
  63. data/gumbo-parser/src/utf8.h +1 -1
  64. data/gumbo-parser/src/util.c +1 -3
  65. data/gumbo-parser/src/util.h +4 -0
  66. data/gumbo-parser/src/vector.h +1 -1
  67. data/lib/nokogiri/css/node.rb +2 -2
  68. data/lib/nokogiri/css/xpath_visitor.rb +5 -3
  69. data/lib/nokogiri/css.rb +6 -0
  70. data/lib/nokogiri/decorators/slop.rb +1 -1
  71. data/lib/nokogiri/encoding_handler.rb +57 -0
  72. data/lib/nokogiri/extension.rb +3 -2
  73. data/lib/nokogiri/html4/document.rb +2 -121
  74. data/lib/nokogiri/html4/element_description_defaults.rb +6 -12
  75. data/lib/nokogiri/html4/encoding_reader.rb +121 -0
  76. data/lib/nokogiri/html4.rb +1 -0
  77. data/lib/nokogiri/html5/document.rb +113 -36
  78. data/lib/nokogiri/html5/document_fragment.rb +9 -2
  79. data/lib/nokogiri/html5/node.rb +3 -5
  80. data/lib/nokogiri/html5.rb +127 -216
  81. data/lib/nokogiri/jruby/dependencies.rb +1 -19
  82. data/lib/nokogiri/jruby/nokogiri_jars.rb +43 -0
  83. data/lib/nokogiri/version/constant.rb +1 -1
  84. data/lib/nokogiri/version/info.rb +11 -10
  85. data/lib/nokogiri/xml/attr.rb +49 -0
  86. data/lib/nokogiri/xml/builder.rb +1 -1
  87. data/lib/nokogiri/xml/document.rb +103 -55
  88. data/lib/nokogiri/xml/document_fragment.rb +49 -6
  89. data/lib/nokogiri/xml/namespace.rb +42 -0
  90. data/lib/nokogiri/xml/node/save_options.rb +6 -4
  91. data/lib/nokogiri/xml/node.rb +190 -35
  92. data/lib/nokogiri/xml/node_set.rb +88 -9
  93. data/lib/nokogiri/xml/parse_options.rb +129 -50
  94. data/lib/nokogiri/xml/pp/node.rb +6 -4
  95. data/lib/nokogiri/xml/processing_instruction.rb +2 -1
  96. data/lib/nokogiri/xml/reader.rb +6 -8
  97. data/lib/nokogiri/xml/sax/parser.rb +2 -3
  98. data/lib/nokogiri/xslt.rb +1 -1
  99. data/lib/nokogiri.rb +3 -11
  100. data/lib/xsd/xmlparser/nokogiri.rb +3 -1
  101. data/ports/archives/libxml2-2.10.3.tar.xz +0 -0
  102. data/ports/archives/libxslt-1.1.37.tar.xz +0 -0
  103. metadata +11 -242
  104. data/patches/libxml2/0004-use-glibc-strlen.patch +0 -53
  105. data/patches/libxml2/0005-avoid-isnan-isinf.patch +0 -81
  106. data/patches/libxml2/0006-update-automake-files-for-arm64.patch +0 -3040
  107. data/patches/libxml2/0008-htmlParseComment-handle-abruptly-closed-comments.patch +0 -61
  108. data/ports/archives/libxml2-2.9.14.tar.xz +0 -0
  109. data/ports/archives/libxslt-1.1.35.tar.xz +0 -0
@@ -137,9 +137,12 @@ module Nokogiri
137
137
 
138
138
  ###
139
139
  # Add +node_or_tags+ as a child of this Node.
140
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
141
140
  #
142
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
141
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
142
+ # containing markup.
143
+ #
144
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
145
+ # a DocumentFragment, NodeSet, or String).
143
146
  #
144
147
  # Also see related method +<<+.
145
148
  def add_child(node_or_tags)
@@ -154,9 +157,12 @@ module Nokogiri
154
157
 
155
158
  ###
156
159
  # Add +node_or_tags+ as the first child of this Node.
157
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
158
160
  #
159
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
161
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
162
+ # containing markup.
163
+ #
164
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
165
+ # a DocumentFragment, NodeSet, or String).
160
166
  #
161
167
  # Also see related method +add_child+.
162
168
  def prepend_child(node_or_tags)
@@ -170,22 +176,81 @@ module Nokogiri
170
176
  end
171
177
  end
172
178
 
173
- ###
174
- # Add html around this node
179
+ # :call-seq:
180
+ # wrap(markup) -> self
181
+ # wrap(node) -> self
182
+ #
183
+ # Wrap this Node with the node parsed from +markup+ or a dup of the +node+.
184
+ #
185
+ # [Parameters]
186
+ # - *markup* (String)
187
+ # Markup that is parsed and used as the wrapper. This node's parent, if it exists, is used
188
+ # as the context node for parsing; otherwise the associated document is used. If the parsed
189
+ # fragment has multiple roots, the first root node is used as the wrapper.
190
+ # - *node* (Nokogiri::XML::Node)
191
+ # An element that is `#dup`ed and used as the wrapper.
192
+ #
193
+ # [Returns] +self+, to support chaining.
194
+ #
195
+ # Also see NodeSet#wrap
196
+ #
197
+ # *Example* with a +String+ argument:
175
198
  #
176
- # Returns self
177
- def wrap(html)
178
- new_parent = document.parse(html).first
179
- add_next_sibling(new_parent)
199
+ # doc = Nokogiri::HTML5(<<~HTML)
200
+ # <html><body>
201
+ # <a>asdf</a>
202
+ # </body></html>
203
+ # HTML
204
+ # doc.at_css("a").wrap("<div></div>")
205
+ # doc.to_html
206
+ # # => <html><head></head><body>
207
+ # # <div><a>asdf</a></div>
208
+ # # </body></html>
209
+ #
210
+ # *Example* with a +Node+ argument:
211
+ #
212
+ # doc = Nokogiri::HTML5(<<~HTML)
213
+ # <html><body>
214
+ # <a>asdf</a>
215
+ # </body></html>
216
+ # HTML
217
+ # doc.at_css("a").wrap(doc.create_element("div"))
218
+ # doc.to_html
219
+ # # <html><head></head><body>
220
+ # # <div><a>asdf</a></div>
221
+ # # </body></html>
222
+ #
223
+ def wrap(node_or_tags)
224
+ case node_or_tags
225
+ when String
226
+ context_node = parent || document
227
+ new_parent = context_node.coerce(node_or_tags).first
228
+ if new_parent.nil?
229
+ raise "Failed to parse '#{node_or_tags}' in the context of a '#{context_node.name}' element"
230
+ end
231
+ when XML::Node
232
+ new_parent = node_or_tags.dup
233
+ else
234
+ raise ArgumentError, "Requires a String or Node argument, and cannot accept a #{node_or_tags.class}"
235
+ end
236
+
237
+ if parent
238
+ add_next_sibling(new_parent)
239
+ else
240
+ new_parent.unlink
241
+ end
180
242
  new_parent.add_child(self)
243
+
181
244
  self
182
245
  end
183
246
 
184
247
  ###
185
248
  # Add +node_or_tags+ as a child of this Node.
186
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
187
249
  #
188
- # Returns self, to support chaining of calls (e.g., root << child1 << child2)
250
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
251
+ # containing markup.
252
+ #
253
+ # Returns +self+, to support chaining of calls (e.g., root << child1 << child2)
189
254
  #
190
255
  # Also see related method +add_child+.
191
256
  def <<(node_or_tags)
@@ -195,9 +260,12 @@ module Nokogiri
195
260
 
196
261
  ###
197
262
  # Insert +node_or_tags+ before this Node (as a sibling).
198
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
199
263
  #
200
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
264
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
265
+ # containing markup.
266
+ #
267
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
268
+ # a DocumentFragment, NodeSet, or String).
201
269
  #
202
270
  # Also see related method +before+.
203
271
  def add_previous_sibling(node_or_tags)
@@ -209,9 +277,12 @@ module Nokogiri
209
277
 
210
278
  ###
211
279
  # Insert +node_or_tags+ after this Node (as a sibling).
212
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
213
280
  #
214
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
281
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
282
+ # containing markup.
283
+ #
284
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
285
+ # a DocumentFragment, NodeSet, or String).
215
286
  #
216
287
  # Also see related method +after+.
217
288
  def add_next_sibling(node_or_tags)
@@ -223,9 +294,11 @@ module Nokogiri
223
294
 
224
295
  ####
225
296
  # Insert +node_or_tags+ before this node (as a sibling).
226
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
227
297
  #
228
- # Returns self, to support chaining of calls.
298
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
299
+ # containing markup.
300
+ #
301
+ # Returns +self+, to support chaining of calls.
229
302
  #
230
303
  # Also see related method +add_previous_sibling+.
231
304
  def before(node_or_tags)
@@ -235,9 +308,11 @@ module Nokogiri
235
308
 
236
309
  ####
237
310
  # Insert +node_or_tags+ after this node (as a sibling).
238
- # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a string containing markup.
239
311
  #
240
- # Returns self, to support chaining of calls.
312
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a String
313
+ # containing markup.
314
+ #
315
+ # Returns +self+, to support chaining of calls.
241
316
  #
242
317
  # Also see related method +add_next_sibling+.
243
318
  def after(node_or_tags)
@@ -246,8 +321,18 @@ module Nokogiri
246
321
  end
247
322
 
248
323
  ####
249
- # Set the inner html for this Node to +node_or_tags+
250
- # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a string containing markup.
324
+ # Set the content for this Node to +node_or_tags+.
325
+ #
326
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a String
327
+ # containing markup.
328
+ #
329
+ # ⚠ Please note that despite the name, this method will *not* always parse a String argument
330
+ # as HTML. A String argument will be parsed with the +DocumentFragment+ parser related to this
331
+ # node's document.
332
+ #
333
+ # For example, if the document is an HTML4::Document then the string will be parsed as HTML4
334
+ # using HTML4::DocumentFragment; but if the document is an XML::Document then it will
335
+ # parse the string as XML using XML::DocumentFragment.
251
336
  #
252
337
  # Also see related method +children=+
253
338
  def inner_html=(node_or_tags)
@@ -255,8 +340,10 @@ module Nokogiri
255
340
  end
256
341
 
257
342
  ####
258
- # Set the inner html for this Node +node_or_tags+
259
- # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a string containing markup.
343
+ # Set the content for this Node +node_or_tags+
344
+ #
345
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a String
346
+ # containing markup.
260
347
  #
261
348
  # Also see related method +inner_html=+
262
349
  def children=(node_or_tags)
@@ -271,9 +358,12 @@ module Nokogiri
271
358
 
272
359
  ####
273
360
  # Replace this Node with +node_or_tags+.
274
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
275
361
  #
276
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
362
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
363
+ # containing markup.
364
+ #
365
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
366
+ # a DocumentFragment, NodeSet, or String).
277
367
  #
278
368
  # Also see related method +swap+.
279
369
  def replace(node_or_tags)
@@ -303,7 +393,9 @@ module Nokogiri
303
393
 
304
394
  ####
305
395
  # Swap this Node for +node_or_tags+
306
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
396
+ #
397
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
398
+ # Containing markup.
307
399
  #
308
400
  # Returns self, to support chaining of calls.
309
401
  #
@@ -314,7 +406,8 @@ module Nokogiri
314
406
  end
315
407
 
316
408
  ####
317
- # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not interpreted as markup.
409
+ # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not
410
+ # interpreted as markup.
318
411
  def content=(string)
319
412
  self.native_content = encode_special_chars(string.to_s)
320
413
  end
@@ -1105,9 +1198,9 @@ module Nokogiri
1105
1198
 
1106
1199
  # Get the path to this node as a CSS expression
1107
1200
  def css_path
1108
- path.split(%r{/}).map do |part|
1201
+ path.split(%r{/}).filter_map do |part|
1109
1202
  part.empty? ? nil : part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
1110
- end.compact.join(" > ")
1203
+ end.join(" > ")
1111
1204
  end
1112
1205
 
1113
1206
  ###
@@ -1194,12 +1287,11 @@ module Nokogiri
1194
1287
  }
1195
1288
  end
1196
1289
 
1197
- encoding = options[:encoding] || document.encoding
1198
- options[:encoding] = encoding
1290
+ options[:encoding] ||= document.encoding
1291
+ encoding = Encoding.find(options[:encoding] || "UTF-8")
1292
+
1293
+ io = StringIO.new(String.new(encoding: encoding))
1199
1294
 
1200
- outstring = +""
1201
- outstring.force_encoding(Encoding.find(encoding || "utf-8"))
1202
- io = StringIO.new(outstring)
1203
1295
  write_to(io, options, &block)
1204
1296
  io.string
1205
1297
  end
@@ -1311,6 +1403,69 @@ module Nokogiri
1311
1403
  end
1312
1404
  end
1313
1405
 
1406
+ DECONSTRUCT_KEYS = [:name, :attributes, :children, :namespace, :content, :elements, :inner_html].freeze # :nodoc:
1407
+ DECONSTRUCT_METHODS = { attributes: :attribute_nodes }.freeze # :nodoc:
1408
+
1409
+ #
1410
+ # :call-seq: deconstruct_keys(array_of_names) → Hash
1411
+ #
1412
+ # Returns a hash describing the Node, to use in pattern matching.
1413
+ #
1414
+ # Valid keys and their values:
1415
+ # - +name+ → (String) The name of this node, or "text" if it is a Text node.
1416
+ # - +namespace+ → (Namespace, nil) The namespace of this node, or nil if there is no namespace.
1417
+ # - +attributes+ → (Array<Attr>) The attributes of this node.
1418
+ # - +children+ → (Array<Node>) The children of this node. 💡 Note this includes text nodes.
1419
+ # - +elements+ → (Array<Node>) The child elements of this node. 💡 Note this does not include text nodes.
1420
+ # - +content+ → (String) The contents of all the text nodes in this node's subtree. See #content.
1421
+ # - +inner_html+ → (String) The inner markup for the children of this node. See #inner_html.
1422
+ #
1423
+ # ⚡ This is an experimental feature, available since v1.14.0
1424
+ #
1425
+ # *Example*
1426
+ #
1427
+ # doc = Nokogiri::XML.parse(<<~XML)
1428
+ # <?xml version="1.0"?>
1429
+ # <parent xmlns="http://nokogiri.org/ns/default" xmlns:noko="http://nokogiri.org/ns/noko">
1430
+ # <child1 foo="abc" noko:bar="def">First</child1>
1431
+ # <noko:child2 foo="qwe" noko:bar="rty">Second</noko:child2>
1432
+ # </parent>
1433
+ # XML
1434
+ #
1435
+ # doc.root.deconstruct_keys([:name, :namespace])
1436
+ # # => {:name=>"parent",
1437
+ # # :namespace=>
1438
+ # # #(Namespace:0x35c { href = "http://nokogiri.org/ns/default" })}
1439
+ #
1440
+ # doc.root.deconstruct_keys([:inner_html, :content])
1441
+ # # => {:content=>"\n" + " First\n" + " Second\n",
1442
+ # # :inner_html=>
1443
+ # # "\n" +
1444
+ # # " <child1 foo=\"abc\" noko:bar=\"def\">First</child1>\n" +
1445
+ # # " <noko:child2 foo=\"qwe\" noko:bar=\"rty\">Second</noko:child2>\n"}
1446
+ #
1447
+ # doc.root.elements.first.deconstruct_keys([:attributes])
1448
+ # # => {:attributes=>
1449
+ # # [#(Attr:0x370 { name = "foo", value = "abc" }),
1450
+ # # #(Attr:0x384 {
1451
+ # # name = "bar",
1452
+ # # namespace = #(Namespace:0x398 {
1453
+ # # prefix = "noko",
1454
+ # # href = "http://nokogiri.org/ns/noko"
1455
+ # # }),
1456
+ # # value = "def"
1457
+ # # })]}
1458
+ #
1459
+ def deconstruct_keys(keys)
1460
+ requested_keys = DECONSTRUCT_KEYS & keys
1461
+ {}.tap do |values|
1462
+ requested_keys.each do |key|
1463
+ method = DECONSTRUCT_METHODS[key] || key
1464
+ values[key] = send(method)
1465
+ end
1466
+ end
1467
+ end
1468
+
1314
1469
  # :section:
1315
1470
 
1316
1471
  protected
@@ -1,3 +1,4 @@
1
+ # coding: utf-8
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Nokogiri
@@ -201,7 +202,7 @@ module Nokogiri
201
202
  #
202
203
  def attr(key, value = nil, &block)
203
204
  unless key.is_a?(Hash) || (key && (value || block))
204
- return first ? first.attribute(key) : nil
205
+ return first&.attribute(key)
205
206
  end
206
207
 
207
208
  hash = key.is_a?(Hash) ? key : { key => value }
@@ -260,10 +261,73 @@ module Nokogiri
260
261
  collect { |j| j.inner_html(*args) }.join("")
261
262
  end
262
263
 
263
- ###
264
- # Wrap this NodeSet with +html+
265
- def wrap(html)
266
- map { |node| node.wrap(html) }
264
+ # :call-seq:
265
+ # wrap(markup) -> self
266
+ # wrap(node) -> self
267
+ #
268
+ # Wrap each member of this NodeSet with the node parsed from +markup+ or a dup of the +node+.
269
+ #
270
+ # [Parameters]
271
+ # - *markup* (String)
272
+ # Markup that is parsed, once per member of the NodeSet, and used as the wrapper. Each
273
+ # node's parent, if it exists, is used as the context node for parsing; otherwise the
274
+ # associated document is used. If the parsed fragment has multiple roots, the first root
275
+ # node is used as the wrapper.
276
+ # - *node* (Nokogiri::XML::Node)
277
+ # An element that is `#dup`ed and used as the wrapper.
278
+ #
279
+ # [Returns] +self+, to support chaining.
280
+ #
281
+ # ⚠ Note that if a +String+ is passed, the markup will be parsed <b>once per node</b> in the
282
+ # NodeSet. You can avoid this overhead in cases where you know exactly the wrapper you wish to
283
+ # use by passing a +Node+ instead.
284
+ #
285
+ # Also see Node#wrap
286
+ #
287
+ # *Example* with a +String+ argument:
288
+ #
289
+ # doc = Nokogiri::HTML5(<<~HTML)
290
+ # <html><body>
291
+ # <a>a</a>
292
+ # <a>b</a>
293
+ # <a>c</a>
294
+ # <a>d</a>
295
+ # </body></html>
296
+ # HTML
297
+ # doc.css("a").wrap("<div></div>")
298
+ # doc.to_html
299
+ # # => <html><head></head><body>
300
+ # # <div><a>a</a></div>
301
+ # # <div><a>b</a></div>
302
+ # # <div><a>c</a></div>
303
+ # # <div><a>d</a></div>
304
+ # # </body></html>
305
+ #
306
+ # *Example* with a +Node+ argument
307
+ #
308
+ # 💡 Note that this is faster than the equivalent call passing a +String+ because it avoids
309
+ # having to reparse the wrapper markup for each node.
310
+ #
311
+ # doc = Nokogiri::HTML5(<<~HTML)
312
+ # <html><body>
313
+ # <a>a</a>
314
+ # <a>b</a>
315
+ # <a>c</a>
316
+ # <a>d</a>
317
+ # </body></html>
318
+ # HTML
319
+ # doc.css("a").wrap(doc.create_element("div"))
320
+ # doc.to_html
321
+ # # => <html><head></head><body>
322
+ # # <div><a>a</a></div>
323
+ # # <div><a>b</a></div>
324
+ # # <div><a>c</a></div>
325
+ # # <div><a>d</a></div>
326
+ # # </body></html>
327
+ #
328
+ def wrap(node_or_tags)
329
+ map { |node| node.wrap(node_or_tags) }
330
+ self
267
331
  end
268
332
 
269
333
  ###
@@ -277,12 +341,16 @@ module Nokogiri
277
341
  def to_html(*args)
278
342
  if Nokogiri.jruby?
279
343
  options = args.first.is_a?(Hash) ? args.shift : {}
280
- unless options[:save_with]
281
- options[:save_with] = Node::SaveOptions::NO_DECLARATION | Node::SaveOptions::NO_EMPTY_TAGS | Node::SaveOptions::AS_HTML
282
- end
344
+ options[:save_with] ||= Node::SaveOptions::DEFAULT_HTML
283
345
  args.insert(0, options)
284
346
  end
285
- map { |x| x.to_html(*args) }.join
347
+ if empty?
348
+ encoding = (args.first.is_a?(Hash) ? args.first[:encoding] : nil)
349
+ encoding ||= document.encoding
350
+ encoding.nil? ? "" : "".encode(encoding)
351
+ else
352
+ map { |x| x.to_html(*args) }.join
353
+ end
286
354
  end
287
355
 
288
356
  ###
@@ -362,6 +430,17 @@ module Nokogiri
362
430
 
363
431
  alias_method :+, :|
364
432
 
433
+ #
434
+ # :call-seq: deconstruct() → Array
435
+ #
436
+ # Returns the members of this NodeSet as an array, to use in pattern matching.
437
+ #
438
+ # ⚡ This is an experimental feature, available since v1.14.0
439
+ #
440
+ def deconstruct
441
+ to_a
442
+ end
443
+
365
444
  IMPLIED_XPATH_CONTEXTS = [".//", "self::"].freeze # :nodoc:
366
445
  end
367
446
  end