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.
- checksums.yaml +4 -4
- data/LICENSE-DEPENDENCIES.md +29 -0
- data/ext/java/nokogiri/XmlNode.java +69 -38
- data/ext/java/nokogiri/XmlReader.java +4 -1
- data/ext/java/nokogiri/XmlSchema.java +5 -74
- data/ext/java/nokogiri/XmlXpathContext.java +5 -15
- data/ext/java/nokogiri/internals/NokogiriEntityResolver.java +5 -7
- data/ext/java/nokogiri/internals/NokogiriHelpers.java +68 -0
- data/ext/java/nokogiri/internals/NokogiriXPathContext.java +37 -0
- data/ext/nokogiri/extconf.rb +1 -0
- data/ext/nokogiri/gumbo.c +83 -6
- data/ext/nokogiri/html4_document.c +10 -3
- data/ext/nokogiri/libxml2_polyfill.c +12 -0
- data/ext/nokogiri/nokogiri.c +84 -2
- data/ext/nokogiri/xml_document.c +42 -16
- data/ext/nokogiri/xml_node.c +73 -38
- data/ext/nokogiri/xml_node_set.c +9 -9
- data/ext/nokogiri/xml_reader.c +198 -107
- data/ext/nokogiri/xml_sax_parser.c +3 -3
- data/ext/nokogiri/xml_sax_parser_context.c +38 -7
- data/ext/nokogiri/xml_sax_push_parser.c +12 -0
- data/ext/nokogiri/xml_xpath_context.c +124 -26
- data/ext/nokogiri/xslt_stylesheet.c +123 -27
- data/lib/nokogiri/css/parser.rb +35 -18
- data/lib/nokogiri/css/parser.y +35 -17
- data/lib/nokogiri/css/xpath_visitor.rb +71 -28
- data/lib/nokogiri/html5/document.rb +9 -0
- data/lib/nokogiri/html5/document_fragment.rb +16 -1
- data/lib/nokogiri/html5/node.rb +1 -1
- data/lib/nokogiri/html5.rb +16 -0
- data/lib/nokogiri/nokogiri.jar +0 -0
- data/lib/nokogiri/version/constant.rb +1 -1
- data/lib/nokogiri/xml/attr.rb +2 -0
- data/lib/nokogiri/xml/attribute_decl.rb +2 -1
- data/lib/nokogiri/xml/builder.rb +24 -32
- data/lib/nokogiri/xml/document.rb +1 -0
- data/lib/nokogiri/xml/dtd.rb +2 -1
- data/lib/nokogiri/xml/element_decl.rb +2 -1
- data/lib/nokogiri/xml/entity_decl.rb +2 -1
- data/lib/nokogiri/xml/node.rb +8 -6
- metadata +3 -2
data/lib/nokogiri/css/parser.rb
CHANGED
|
@@ -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.
|
|
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})\
|
|
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})\
|
|
32
|
-
if
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
data/lib/nokogiri/css/parser.y
CHANGED
|
@@ -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 {
|
|
68
|
-
|
|
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 {
|
|
93
|
-
|
|
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})\
|
|
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})\
|
|
271
|
-
if
|
|
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 ||
|
|
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
|
|
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
|
-
|
|
180
|
-
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
|
-
|
|
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
|
-
|
|
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
|
-
"*:#{
|
|
269
|
+
if WILDCARD_NAMESPACES && xpath_name?(name)
|
|
270
|
+
"*:#{name}"
|
|
274
271
|
else
|
|
275
|
-
"*[nokogiri-builtin:local-name-is(
|
|
272
|
+
"*[nokogiri-builtin:local-name-is(#{xpath_string_literal(name)})]"
|
|
276
273
|
end
|
|
277
274
|
else
|
|
278
|
-
"*[local-name()
|
|
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
|
-
|
|
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
|
|
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}
|
|
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}),' ')
|
|
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:
|
data/lib/nokogiri/html5/node.rb
CHANGED
|
@@ -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
|
data/lib/nokogiri/html5.rb
CHANGED
|
@@ -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
|
data/lib/nokogiri/nokogiri.jar
CHANGED
|
Binary file
|
data/lib/nokogiri/xml/attr.rb
CHANGED
data/lib/nokogiri/xml/builder.rb
CHANGED
|
@@ -314,7 +314,7 @@ module Nokogiri
|
|
|
314
314
|
|
|
315
315
|
@context = nil
|
|
316
316
|
@arity = nil
|
|
317
|
-
@
|
|
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 [](
|
|
359
|
-
|
|
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)
|
|
399
|
-
|
|
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?)
|
data/lib/nokogiri/xml/dtd.rb
CHANGED
|
@@ -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
|
|
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)
|