nokogiri 1.19.4-java → 1.19.5-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE-DEPENDENCIES.md +29 -0
  3. data/ext/java/nokogiri/XmlNode.java +69 -38
  4. data/ext/java/nokogiri/XmlReader.java +4 -1
  5. data/ext/java/nokogiri/XmlSchema.java +5 -74
  6. data/ext/java/nokogiri/XmlXpathContext.java +5 -15
  7. data/ext/java/nokogiri/internals/NokogiriEntityResolver.java +5 -7
  8. data/ext/java/nokogiri/internals/NokogiriHelpers.java +68 -0
  9. data/ext/java/nokogiri/internals/NokogiriXPathContext.java +37 -0
  10. data/ext/nokogiri/extconf.rb +1 -0
  11. data/ext/nokogiri/gumbo.c +83 -6
  12. data/ext/nokogiri/html4_document.c +10 -3
  13. data/ext/nokogiri/libxml2_polyfill.c +12 -0
  14. data/ext/nokogiri/nokogiri.c +84 -2
  15. data/ext/nokogiri/xml_document.c +42 -16
  16. data/ext/nokogiri/xml_node.c +73 -38
  17. data/ext/nokogiri/xml_node_set.c +9 -9
  18. data/ext/nokogiri/xml_reader.c +198 -107
  19. data/ext/nokogiri/xml_sax_parser.c +3 -3
  20. data/ext/nokogiri/xml_sax_parser_context.c +38 -7
  21. data/ext/nokogiri/xml_sax_push_parser.c +12 -0
  22. data/ext/nokogiri/xml_xpath_context.c +124 -26
  23. data/ext/nokogiri/xslt_stylesheet.c +123 -27
  24. data/lib/nokogiri/css/parser.rb +35 -18
  25. data/lib/nokogiri/css/parser.y +35 -17
  26. data/lib/nokogiri/css/xpath_visitor.rb +71 -28
  27. data/lib/nokogiri/html5/document.rb +9 -0
  28. data/lib/nokogiri/html5/document_fragment.rb +16 -1
  29. data/lib/nokogiri/html5/node.rb +1 -1
  30. data/lib/nokogiri/html5.rb +16 -0
  31. data/lib/nokogiri/nokogiri.jar +0 -0
  32. data/lib/nokogiri/version/constant.rb +1 -1
  33. data/lib/nokogiri/xml/attr.rb +2 -0
  34. data/lib/nokogiri/xml/attribute_decl.rb +2 -1
  35. data/lib/nokogiri/xml/builder.rb +24 -32
  36. data/lib/nokogiri/xml/document.rb +1 -0
  37. data/lib/nokogiri/xml/dtd.rb +2 -1
  38. data/lib/nokogiri/xml/element_decl.rb +2 -1
  39. data/lib/nokogiri/xml/entity_decl.rb +2 -1
  40. data/lib/nokogiri/xml/node.rb +8 -6
  41. metadata +3 -2
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  #
3
3
  # DO NOT MODIFY!!!!
4
- # This file is automatically generated by Racc 1.8.0
4
+ # This file is automatically generated by Racc 1.8.1
5
5
  # from Racc grammar file "parser.y".
6
6
  #
7
7
 
@@ -23,19 +23,36 @@ module Nokogiri
23
23
  class Parser < Racc::Parser
24
24
 
25
25
 
26
- def unescape_css_identifier(identifier)
27
- identifier.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})\s?)/){ |m| $1 || [$2.hex].pack('U') }
26
+ def self.unescape_css_identifier(identifier)
27
+ identifier.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})(?:\r\n|[ \t\n\r\f])?)/){ |m| $1 || unescape_css_code_point($2) }
28
28
  end
29
29
 
30
- def unescape_css_string(str)
31
- str.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})\s?)/) do |m|
32
- if $1=="\n"
30
+ def self.unescape_css_string(str)
31
+ str.gsub(/\\(?:(\r\n|[^0-9a-fA-F])|([0-9a-fA-F]{1,6})(?:\r\n|[ \t\n\r\f])?)/) do |m|
32
+ if ["\n", "\r\n", "\r", "\f"].include?($1)
33
33
  ''
34
34
  else
35
- $1 || [$2.hex].pack('U')
35
+ $1 || unescape_css_code_point($2)
36
36
  end
37
37
  end
38
38
  end
39
+
40
+ # https://www.w3.org/TR/css-syntax-3/#consume-escaped-code-point
41
+ def self.unescape_css_code_point(hex)
42
+ code_point = hex.hex
43
+ if code_point == 0 || code_point.between?(0xD800, 0xDFFF) || code_point > 0x10FFFF
44
+ "\u{FFFD}"
45
+ else
46
+ [code_point].pack('U')
47
+ end
48
+ end
49
+
50
+ # A decoded "*" would turn into the universal selector.
51
+ def unescape_css_element_name(identifier)
52
+ name = Parser.unescape_css_identifier(identifier)
53
+ raise SyntaxError, "Invalid element name '#{identifier}'" if name == "*"
54
+ name
55
+ end
39
56
  ##### State transition tables begin ###
40
57
 
41
58
  racc_action_table = [
@@ -402,7 +419,7 @@ def _reduce_8(val, _values, result)
402
419
  end
403
420
 
404
421
  def _reduce_9(val, _values, result)
405
- result = val[1]
422
+ result = Parser.unescape_css_identifier(val[1])
406
423
  result
407
424
  end
408
425
 
@@ -458,7 +475,7 @@ end
458
475
  # reduce 20 omitted
459
476
 
460
477
  def _reduce_21(val, _values, result)
461
- result = Node.new(:CLASS_CONDITION, [unescape_css_identifier(val[1])])
478
+ result = Node.new(:CLASS_CONDITION, [Parser.unescape_css_identifier(val[1])])
462
479
  result
463
480
  end
464
481
 
@@ -470,14 +487,13 @@ def _reduce_23(val, _values, result)
470
487
  end
471
488
 
472
489
  def _reduce_24(val, _values, result)
473
- result = Node.new(:ELEMENT_NAME, [val[0], val[2]])
490
+ result = Node.new(:ELEMENT_NAME, [val[0] && unescape_css_element_name(val[0]), unescape_css_element_name(val[2])])
491
+
474
492
  result
475
493
  end
476
494
 
477
495
  def _reduce_25(val, _values, result)
478
- name = val[0]
479
- result = Node.new(:ELEMENT_NAME, [name])
480
-
496
+ result = Node.new(:ELEMENT_NAME, [unescape_css_element_name(val[0])])
481
497
  result
482
498
  end
483
499
 
@@ -507,12 +523,13 @@ def _reduce_30(val, _values, result)
507
523
  end
508
524
 
509
525
  def _reduce_31(val, _values, result)
510
- result = Node.new(:ATTRIB_NAME, [[val[0], val[2]].compact.join(':')])
526
+ result = Node.new(:ATTRIB_NAME, [val[0], val[2]].compact.map { |name| Parser.unescape_css_identifier(name) })
527
+
511
528
  result
512
529
  end
513
530
 
514
531
  def _reduce_32(val, _values, result)
515
- result = Node.new(:ATTRIB_NAME, [val[0]])
532
+ result = Node.new(:ATTRIB_NAME, [Parser.unescape_css_identifier(val[0])])
516
533
  result
517
534
  end
518
535
 
@@ -691,17 +708,17 @@ end
691
708
  # reduce 63 omitted
692
709
 
693
710
  def _reduce_64(val, _values, result)
694
- result = Node.new(:ID, [unescape_css_identifier(val[0])])
711
+ result = Node.new(:ID, [Parser.unescape_css_identifier(val[0])])
695
712
  result
696
713
  end
697
714
 
698
715
  def _reduce_65(val, _values, result)
699
- result = [val[0], unescape_css_identifier(val[1])]
716
+ result = [val[0], Parser.unescape_css_identifier(val[1])]
700
717
  result
701
718
  end
702
719
 
703
720
  def _reduce_66(val, _values, result)
704
- result = [val[0], unescape_css_string(val[1])]
721
+ result = [val[0], Parser.unescape_css_string(val[1][1..-2])]
705
722
  result
706
723
  end
707
724
 
@@ -22,7 +22,7 @@ rule
22
22
  ;
23
23
 
24
24
  xpath_attribute_name:
25
- '@' IDENT { result = val[1] }
25
+ '@' IDENT { result = Parser.unescape_css_identifier(val[1]) }
26
26
  ;
27
27
 
28
28
  xpath_attribute:
@@ -55,7 +55,7 @@ rule
55
55
  ;
56
56
 
57
57
  class:
58
- '.' IDENT { result = Node.new(:CLASS_CONDITION, [unescape_css_identifier(val[1])]) }
58
+ '.' IDENT { result = Node.new(:CLASS_CONDITION, [Parser.unescape_css_identifier(val[1])]) }
59
59
  ;
60
60
 
61
61
  element_name:
@@ -64,11 +64,10 @@ rule
64
64
  ;
65
65
 
66
66
  namespaced_ident:
67
- namespace '|' IDENT { result = Node.new(:ELEMENT_NAME, [val[0], val[2]]) }
68
- | IDENT {
69
- name = val[0]
70
- result = Node.new(:ELEMENT_NAME, [name])
67
+ namespace '|' IDENT {
68
+ result = Node.new(:ELEMENT_NAME, [val[0] && unescape_css_element_name(val[0]), unescape_css_element_name(val[2])])
71
69
  }
70
+ | IDENT { result = Node.new(:ELEMENT_NAME, [unescape_css_element_name(val[0])]) }
72
71
  ;
73
72
 
74
73
  namespace:
@@ -89,8 +88,10 @@ rule
89
88
  ;
90
89
 
91
90
  attrib_name:
92
- namespace '|' IDENT { result = Node.new(:ATTRIB_NAME, [[val[0], val[2]].compact.join(':')]) }
93
- | IDENT { result = Node.new(:ATTRIB_NAME, [val[0]]) }
91
+ namespace '|' IDENT {
92
+ result = Node.new(:ATTRIB_NAME, [val[0], val[2]].compact.map { |name| Parser.unescape_css_identifier(name) })
93
+ }
94
+ | IDENT { result = Node.new(:ATTRIB_NAME, [Parser.unescape_css_identifier(val[0])]) }
94
95
  | xpath_attribute
95
96
  ;
96
97
 
@@ -209,12 +210,12 @@ rule
209
210
  ;
210
211
 
211
212
  attribute_id:
212
- HASH { result = Node.new(:ID, [unescape_css_identifier(val[0])]) }
213
+ HASH { result = Node.new(:ID, [Parser.unescape_css_identifier(val[0])]) }
213
214
  ;
214
215
 
215
216
  attrib_val_0or1:
216
- eql_incl_dash IDENT { result = [val[0], unescape_css_identifier(val[1])] }
217
- | eql_incl_dash STRING { result = [val[0], unescape_css_string(val[1])] }
217
+ eql_incl_dash IDENT { result = [val[0], Parser.unescape_css_identifier(val[1])] }
218
+ | eql_incl_dash STRING { result = [val[0], Parser.unescape_css_string(val[1][1..-2])] }
218
219
  | eql_incl_dash NUMBER { result = [val[0], val[1]] }
219
220
  |
220
221
  ;
@@ -262,16 +263,33 @@ end
262
263
 
263
264
  ---- inner
264
265
 
265
- def unescape_css_identifier(identifier)
266
- identifier.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})\s?)/){ |m| $1 || [$2.hex].pack('U') }
266
+ def self.unescape_css_identifier(identifier)
267
+ identifier.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})(?:\r\n|[ \t\n\r\f])?)/){ |m| $1 || unescape_css_code_point($2) }
267
268
  end
268
269
 
269
- def unescape_css_string(str)
270
- str.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})\s?)/) do |m|
271
- if $1=="\n"
270
+ def self.unescape_css_string(str)
271
+ str.gsub(/\\(?:(\r\n|[^0-9a-fA-F])|([0-9a-fA-F]{1,6})(?:\r\n|[ \t\n\r\f])?)/) do |m|
272
+ if ["\n", "\r\n", "\r", "\f"].include?($1)
272
273
  ''
273
274
  else
274
- $1 || [$2.hex].pack('U')
275
+ $1 || unescape_css_code_point($2)
275
276
  end
276
277
  end
277
278
  end
279
+
280
+ # https://www.w3.org/TR/css-syntax-3/#consume-escaped-code-point
281
+ def self.unescape_css_code_point(hex)
282
+ code_point = hex.hex
283
+ if code_point == 0 || code_point.between?(0xD800, 0xDFFF) || code_point > 0x10FFFF
284
+ "\u{FFFD}"
285
+ else
286
+ [code_point].pack('U')
287
+ end
288
+ end
289
+
290
+ # A decoded "*" would turn into the universal selector.
291
+ def unescape_css_element_name(identifier)
292
+ name = Parser.unescape_css_identifier(identifier)
293
+ raise SyntaxError, "Invalid element name '#{identifier}'" if name == "*"
294
+ name
295
+ end
@@ -9,6 +9,11 @@ module Nokogiri
9
9
  class XPathVisitor
10
10
  WILDCARD_NAMESPACES = Nokogiri.libxml2_patches.include?("0009-allow-wildcard-namespaces.patch") # :nodoc:
11
11
 
12
+ # The ASCII characters allowed in an XML NCName. Every XPath delimiter and operator is ASCII,
13
+ # so non-ASCII characters are left for the XPath parser to accept or reject.
14
+ XPATH_NAME = /\A(?:[A-Za-z_]|[^\x00-\x7F])(?:[A-Za-z0-9_.-]|[^\x00-\x7F])*\z/
15
+ private_constant :XPATH_NAME
16
+
12
17
  # Enum to direct XPathVisitor when to use Nokogiri builtin XPath functions.
13
18
  module BuiltinsConfig
14
19
  # Never use Nokogiri builtin functions, always generate vanilla XPath 1.0 queries. This is
@@ -136,7 +141,7 @@ module Nokogiri
136
141
  when /^(last|last-of-type)\(/
137
142
  "position()=last()"
138
143
  when /^contains\(/
139
- "contains(.,#{node.value[1]})"
144
+ "contains(.,#{xpath_function_argument(node.value[1])})"
140
145
  when /^gt\(/
141
146
  "position()>#{node.value[1]}"
142
147
  when /^only-child\(/
@@ -151,9 +156,7 @@ module Nokogiri
151
156
 
152
157
  # xpath function call, let's marshal those arguments
153
158
  args = ["."]
154
- args += node.value[1..-1].map do |n|
155
- n.is_a?(Nokogiri::CSS::Node) ? n.accept(self) : n
156
- end
159
+ args += node.value[1..-1].map { |arg| xpath_function_argument(arg) }
157
160
  "nokogiri:#{node.value.first}#{args.join(",")})"
158
161
  end
159
162
  end
@@ -168,24 +171,15 @@ module Nokogiri
168
171
  end
169
172
 
170
173
  def visit_id(node)
171
- node.value.first =~ /^#(.*)$/
172
- "@id='#{Regexp.last_match(1)}'"
174
+ "@id=#{xpath_string_literal(node.value.first.delete_prefix("#"))}"
173
175
  end
174
176
 
175
177
  def visit_attribute_condition(node)
176
178
  attribute = node.value.first.accept(self)
177
179
  return attribute if node.value.length == 1
178
180
 
179
- value = node.value.last
180
- value = "'#{value}'" unless /^['"]/.match?(value)
181
-
182
- # quoted values - see test_attribute_value_with_quotes in test/css/test_parser.rb
183
- if (value[0] == value[-1]) && %q{"'}.include?(value[0])
184
- str_value = value[1..-2]
185
- if str_value.include?(value[0])
186
- value = 'concat("' + str_value.split('"', -1).join(%q{",'"',"}) + '","")'
187
- end
188
- end
181
+ str_value = node.value.last
182
+ value = xpath_string_literal(str_value)
189
183
 
190
184
  case node.value[1]
191
185
  when :equal
@@ -199,8 +193,7 @@ module Nokogiri
199
193
  when :dash_match
200
194
  "#{attribute}=#{value} or starts-with(#{attribute},concat(#{value},'-'))"
201
195
  when :includes
202
- value = value[1..-2] # strip quotes
203
- css_class(attribute, value)
196
+ css_class(attribute, str_value)
204
197
  when :suffix_match
205
198
  "substring(#{attribute},string-length(#{attribute})-string-length(#{value})+1,string-length(#{value}))=#{value}"
206
199
  else
@@ -265,17 +258,21 @@ module Nokogiri
265
258
  end
266
259
 
267
260
  def visit_element_name(node)
268
- if @doctype == DoctypeConfig::HTML5 && html5_element_name_needs_namespace_handling(node)
261
+ html5_local_name = @doctype == DoctypeConfig::HTML5 && html5_element_name_needs_namespace_handling(node)
262
+ node.value.each { |name| validate_xpath_element_name(name) } unless html5_local_name
263
+
264
+ if html5_local_name
269
265
  # HTML5 has namespaces that should be ignored in CSS queries
270
266
  # https://github.com/sparklemotion/nokogiri/issues/2376
267
+ name = node.value.first
271
268
  if @builtins == BuiltinsConfig::ALWAYS || (@builtins == BuiltinsConfig::OPTIMAL && Nokogiri.uses_libxml?)
272
- if WILDCARD_NAMESPACES
273
- "*:#{node.value.first}"
269
+ if WILDCARD_NAMESPACES && xpath_name?(name)
270
+ "*:#{name}"
274
271
  else
275
- "*[nokogiri-builtin:local-name-is('#{node.value.first}')]"
272
+ "*[nokogiri-builtin:local-name-is(#{xpath_string_literal(name)})]"
276
273
  end
277
274
  else
278
- "*[local-name()='#{node.value.first}']"
275
+ "*[local-name()=#{xpath_string_literal(name)}]"
279
276
  end
280
277
  elsif node.value.length == 2 # has a namespace prefix
281
278
  if node.value.first.nil? # namespace prefix is empty
@@ -292,7 +289,18 @@ module Nokogiri
292
289
  end
293
290
 
294
291
  def visit_attrib_name(node)
295
- "@#{node.value.first}"
292
+ if node.value.length == 2 # has a namespace prefix
293
+ unless node.value.all? { |name| xpath_name?(name) }
294
+ raise Nokogiri::CSS::SyntaxError, "Invalid attribute name '#{node.value.join("|")}'"
295
+ end
296
+
297
+ "@#{node.value.join(":")}"
298
+ elsif xpath_name?(node.value.first)
299
+ "@#{node.value.first}"
300
+ else
301
+ # HTML parsers allow attribute names like "@click" and ":class". On JRuby, local-name() of ":class" is "class".
302
+ "@*[namespace-uri()='' and name()=#{xpath_string_literal(node.value.first)}]"
303
+ end
296
304
  end
297
305
 
298
306
  def accept(node)
@@ -307,6 +315,27 @@ module Nokogiri
307
315
  end
308
316
  end
309
317
 
318
+ def validate_xpath_element_name(name)
319
+ unless name.nil? || name == "*" || xpath_name?(name)
320
+ raise Nokogiri::CSS::SyntaxError, "Invalid element name '#{name}'"
321
+ end
322
+ end
323
+
324
+ def xpath_name?(name)
325
+ XPATH_NAME.match?(name)
326
+ end
327
+
328
+ # Function arguments reach the visitor as raw tokens, and only a STRING token starts with a quote.
329
+ def xpath_function_argument(arg)
330
+ if arg.is_a?(Nokogiri::CSS::Node)
331
+ arg.accept(self)
332
+ elsif arg.is_a?(String) && arg.start_with?("'", '"')
333
+ xpath_string_literal(Parser.unescape_css_string(arg[1..-2]))
334
+ else
335
+ arg
336
+ end
337
+ end
338
+
310
339
  def html5_element_name_needs_namespace_handling(node)
311
340
  # if there is already a namespace (i.e., it is a prefixed QName), use it as normal
312
341
  node.value.length == 1 &&
@@ -326,7 +355,9 @@ module Nokogiri
326
355
  options[:last] ? "(last()-position()+1)" : "position()"
327
356
  end
328
357
 
329
- if b.zero?
358
+ if a.zero?
359
+ "#{position}=#{b}"
360
+ elsif b.zero? && a.positive?
330
361
  "(#{position} mod #{a})=0"
331
362
  else
332
363
  compare = a < 0 ? "<=" : ">="
@@ -345,7 +376,7 @@ module Nokogiri
345
376
  b = values[3].to_i
346
377
  elsif op == "-"
347
378
  a = values[0].to_i
348
- b = a - (values[3].to_i % a)
379
+ b = a.zero? ? -values[3].to_i : a - (values[3].to_i % a)
349
380
  else
350
381
  raise ArgumentError, "expected an+b node to have either + or - as the operator, but is #{op.inspect}"
351
382
  end
@@ -365,10 +396,22 @@ module Nokogiri
365
396
  def css_class(hay, needle)
366
397
  if @builtins == BuiltinsConfig::ALWAYS || (@builtins == BuiltinsConfig::OPTIMAL && Nokogiri.uses_libxml?)
367
398
  # use the builtin implementation
368
- "nokogiri-builtin:css-class(#{hay},'#{needle}')"
399
+ "nokogiri-builtin:css-class(#{hay},#{xpath_string_literal(needle)})"
369
400
  else
370
401
  # use only ordinary xpath functions
371
- "contains(concat(' ',normalize-space(#{hay}),' '),' #{needle} ')"
402
+ "contains(concat(' ',normalize-space(#{hay}),' '),#{xpath_string_literal(" #{needle} ")})"
403
+ end
404
+ end
405
+
406
+ # XPath 1.0 string literals have no escape mechanism, so a string containing both quote
407
+ # characters has to be assembled with concat().
408
+ def xpath_string_literal(string)
409
+ if !string.include?("'")
410
+ "'#{string}'"
411
+ elsif !string.include?('"')
412
+ %("#{string}")
413
+ else
414
+ "concat('" + string.split("'", -1).join(%q{',"'",'}) + "')"
372
415
  end
373
416
  end
374
417
  end
@@ -48,6 +48,11 @@ module Nokogiri
48
48
  # Since v1.14.0
49
49
  attr_reader :quirks_mode
50
50
 
51
+ # Whether the parser treated the content of +noscript+ elements as text, or +nil+ before the
52
+ # first parse. Fragments parsed into this document must match it, because the serializer emits
53
+ # +noscript+ text literally only when this is true.
54
+ attr_accessor :parse_noscript_content_as_text # :nodoc:
55
+
51
56
  class << self
52
57
  # :call-seq:
53
58
  # parse(input) { |options| ... } → HTML5::Document
@@ -150,8 +155,11 @@ module Nokogiri
150
155
  options[:max_errors] ||= options.delete(:max_parse_errors) || Nokogiri::Gumbo::DEFAULT_MAX_ERRORS
151
156
  options[:max_tree_depth] ||= Nokogiri::Gumbo::DEFAULT_MAX_TREE_DEPTH
152
157
 
158
+ noscript_content_as_text = options[:parse_noscript_content_as_text] ? true : false
159
+
153
160
  doc = Nokogiri::Gumbo.parse(string, url, self, **options)
154
161
  doc.encoding = "UTF-8"
162
+ doc.parse_noscript_content_as_text = noscript_content_as_text
155
163
  doc
156
164
  end
157
165
  end
@@ -160,6 +168,7 @@ module Nokogiri
160
168
  super
161
169
  @url = nil
162
170
  @quirks_mode = nil
171
+ @parse_noscript_content_as_text = nil
163
172
  end
164
173
 
165
174
  # :call-seq:
@@ -130,7 +130,9 @@ module Nokogiri
130
130
  # element. (default +Nokogiri::Gumbo::DEFAULT_MAX_ATTRIBUTES+)
131
131
  #
132
132
  # - +parse_noscript_content_as_text:+ (Boolean) Whether to parse the content of +noscript+
133
- # elements as text. (default +false+)
133
+ # elements as text. (default: the value +document+ was parsed with, or +false+) Raises
134
+ # ArgumentError if +document+ was parsed with a different value, because the serializer
135
+ # escapes +noscript+ text according to the document's value.
134
136
  #
135
137
  # See rdoc-ref:HTML5@Parsing+options for a complete description of these parsing options.
136
138
  #
@@ -162,8 +164,21 @@ module Nokogiri
162
164
  options[:max_attributes] ||= Nokogiri::Gumbo::DEFAULT_MAX_ATTRIBUTES
163
165
  options[:max_errors] ||= options.delete(:max_parse_errors) || Nokogiri::Gumbo::DEFAULT_MAX_ERRORS
164
166
  options[:max_tree_depth] ||= Nokogiri::Gumbo::DEFAULT_MAX_TREE_DEPTH
167
+ noscript_content_as_text = options[:parse_noscript_content_as_text] = noscript_parsing_mode(doc, options)
165
168
 
166
169
  Nokogiri::Gumbo.fragment(self, input, context, **options)
170
+ doc.parse_noscript_content_as_text = noscript_content_as_text if doc.parse_noscript_content_as_text.nil?
171
+ end
172
+
173
+ private def noscript_parsing_mode(doc, options)
174
+ doc_mode = doc.parse_noscript_content_as_text
175
+ mode = options.fetch(:parse_noscript_content_as_text, doc_mode) ? true : false
176
+
177
+ if !doc_mode.nil? && mode != doc_mode
178
+ raise ArgumentError, "parse_noscript_content_as_text: #{mode} does not match the document's value #{doc_mode}"
179
+ end
180
+
181
+ mode
167
182
  end
168
183
 
169
184
  def serialize(options = {}, &block) # :nodoc:
@@ -61,7 +61,7 @@ module Nokogiri
61
61
  native_write_to(io, encoding, indent_string, config_options)
62
62
  else
63
63
  # Serialize including the current node.
64
- html = html_standard_serialize(options[:preserve_newline] || false)
64
+ html = html_standard_serialize(options[:preserve_newline] || false, document.parse_noscript_content_as_text)
65
65
  encoding ||= document.encoding || Encoding::UTF_8
66
66
  io << html.encode(encoding, fallback: lambda { |c| "&#x#{c.ord.to_s(16)};" })
67
67
  end
@@ -181,6 +181,22 @@ module Nokogiri
181
181
  # # })]
182
182
  # # })
183
183
  #
184
+ # ⚠ HTML parsed with <tt>parse_noscript_content_as_text: false</tt> and then serialized may
185
+ # parse differently in a browser that supports scripting. A comment or a raw-text element (such
186
+ # as +style+ or +xmp+) inside +noscript+ may contain the text <tt></noscript></tt>. The
187
+ # serializer emits it unchanged, as the HTML standard requires, and the browser ends the
188
+ # +noscript+ element there and parses the rest as markup.
189
+ #
190
+ # frag = Nokogiri::HTML5.fragment("<noscript><!--</noscript><img src=x onerror=alert(1)>--></noscript>")
191
+ # frag.css("img") # => []
192
+ # puts frag.to_html
193
+ # # => <noscript><!--</noscript><img src=x onerror=alert(1)>--></noscript>
194
+ # # A browser that supports scripting parses this as a noscript element followed by an img element.
195
+ #
196
+ # Applications that sanitize HTML and allow +noscript+ elements should parse with
197
+ # <tt>parse_noscript_content_as_text: true</tt>, which exposes that +img+ element to the
198
+ # sanitizer, or should remove +noscript+ elements.
199
+ #
184
200
  # == HTML Serialization
185
201
  #
186
202
  # After parsing HTML, it may be serialized using any of the Nokogiri::XML::Node serialization
Binary file
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Nokogiri
4
4
  # The version of Nokogiri you are using
5
- VERSION = "1.19.4"
5
+ VERSION = "1.19.5"
6
6
  end
@@ -8,6 +8,8 @@ module Nokogiri
8
8
  alias_method :to_s, :content
9
9
  alias_method :content=, :value=
10
10
 
11
+ undef_method :line= if method_defined?(:line=)
12
+
11
13
  #
12
14
  # :call-seq: deconstruct_keys(array_of_names) → Hash
13
15
  #
@@ -10,7 +10,8 @@ module Nokogiri
10
10
  undef_method :content
11
11
  undef_method :namespace
12
12
  undef_method :namespace_definitions
13
- undef_method :line if method_defined?(:line)
13
+ undef_method :line
14
+ undef_method :line= if method_defined?(:line=)
14
15
 
15
16
  private
16
17
 
@@ -314,7 +314,7 @@ module Nokogiri
314
314
 
315
315
  @context = nil
316
316
  @arity = nil
317
- @ns = nil
317
+ @ns_prefix = nil
318
318
 
319
319
  options = DEFAULT_DOCUMENT_OPTIONS.merge(options)
320
320
  options.each do |k, v|
@@ -355,20 +355,8 @@ module Nokogiri
355
355
  ###
356
356
  # Build a tag that is associated with namespace +ns+. Raises an
357
357
  # ArgumentError if +ns+ has not been defined higher in the tree.
358
- def [](ns)
359
- if @parent != @doc
360
- @ns = @parent.namespace_definitions.find { |x| x.prefix == ns.to_s }
361
- end
362
- return self if @ns
363
-
364
- @parent.ancestors.each do |a|
365
- next if a == doc
366
-
367
- @ns = a.namespace_definitions.find { |x| x.prefix == ns.to_s }
368
- return self if @ns
369
- end
370
-
371
- @ns = { pending: ns.to_s }
358
+ def [](ns_prefix)
359
+ @ns_prefix = ns_prefix.to_s
372
360
  self
373
361
  end
374
362
 
@@ -395,29 +383,33 @@ module Nokogiri
395
383
  if @context&.respond_to?(method)
396
384
  @context.send(method, *args, &block)
397
385
  else
398
- node = @doc.create_element(method.to_s.sub(/[_!]$/, ""), *args) do |n|
399
- # Set up the namespace
400
- if @ns.is_a?(Nokogiri::XML::Namespace)
401
- n.namespace = @ns
402
- @ns = nil
403
- end
404
- end
405
-
406
- if @ns.is_a?(Hash)
407
- node.namespace = node.namespace_definitions.find { |x| x.prefix == @ns[:pending] }
408
- if node.namespace.nil?
409
- raise ArgumentError, "Namespace #{@ns[:pending]} has not been defined"
410
- end
411
-
412
- @ns = nil
413
- end
414
-
386
+ node = @doc.create_element(method.to_s.sub(/[_!]$/, ""), *args)
387
+ bind_ns(node)
415
388
  insert(node, &block)
416
389
  end
417
390
  end
418
391
 
419
392
  private
420
393
 
394
+ def bind_ns(node)
395
+ return if @ns_prefix.nil?
396
+
397
+ ancestors = [node, parent, parent.ancestors].flatten
398
+ ancestors.each do |ancestor|
399
+ break if ancestor.nil? || ancestor == @doc
400
+
401
+ if (ns = ancestor.namespace_definitions.find { |x| x.prefix == @ns_prefix })
402
+ @ns_prefix = nil
403
+ node.namespace = ns
404
+ break
405
+ end
406
+ end
407
+
408
+ return if @ns_prefix.nil?
409
+
410
+ raise ArgumentError, "Namespace prefix #{@ns_prefix.inspect} has not been defined"
411
+ end
412
+
421
413
  ###
422
414
  # Insert +node+ as a child of the current Node
423
415
  def insert(node, &block)
@@ -434,6 +434,7 @@ module Nokogiri
434
434
  undef_method :swap, :parent, :namespace, :default_namespace=
435
435
  undef_method :add_namespace_definition, :attributes
436
436
  undef_method :namespace_definitions, :line, :add_namespace
437
+ undef_method :line= if method_defined?(:line=)
437
438
 
438
439
  def add_child(node_or_tags)
439
440
  raise "A document may not have multiple root nodes." if (root && root.name != "nokogiri_text_wrapper") && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
@@ -8,7 +8,8 @@ module Nokogiri
8
8
  undef_method :content
9
9
  undef_method :namespace
10
10
  undef_method :namespace_definitions
11
- undef_method :line if method_defined?(:line)
11
+ undef_method :line
12
+ undef_method :line= if method_defined?(:line=)
12
13
 
13
14
  def keys
14
15
  attributes.keys
@@ -5,7 +5,8 @@ module Nokogiri
5
5
  class ElementDecl < Nokogiri::XML::Node
6
6
  undef_method :namespace
7
7
  undef_method :namespace_definitions
8
- undef_method :line if method_defined?(:line)
8
+ undef_method :line
9
+ undef_method :line= if method_defined?(:line=)
9
10
 
10
11
  private
11
12
 
@@ -7,7 +7,8 @@ module Nokogiri
7
7
  undef_method :attributes
8
8
  undef_method :namespace
9
9
  undef_method :namespace_definitions
10
- undef_method :line if method_defined?(:line)
10
+ undef_method :line
11
+ undef_method :line= if method_defined?(:line=)
11
12
 
12
13
  def self.new(name, doc, *args)
13
14
  doc.create_entity(name, *args)