nokogiri 1.8.2 → 1.8.3
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.
- checksums.yaml +4 -4
- data/.travis.yml +14 -14
- data/CHANGELOG.md +43 -1
- data/LICENSE.md +2 -1
- data/Manifest.txt +3 -0
- data/README.md +20 -21
- data/Rakefile +2 -8
- data/SECURITY.md +19 -0
- data/build_all +2 -2
- data/dependencies.yml +11 -11
- data/ext/nokogiri/extconf.rb +1 -1
- data/ext/nokogiri/html_element_description.c +14 -14
- data/ext/nokogiri/xml_cdata.c +6 -4
- data/ext/nokogiri/xml_document.c +2 -3
- data/ext/nokogiri/xml_dtd.c +2 -2
- data/ext/nokogiri/xml_io.c +1 -0
- data/ext/nokogiri/xml_namespace.c +3 -9
- data/ext/nokogiri/xml_namespace.h +2 -0
- data/ext/nokogiri/xml_node.c +23 -15
- data/ext/nokogiri/xml_node_set.c +5 -4
- data/ext/nokogiri/xml_node_set.h +0 -1
- data/ext/nokogiri/xslt_stylesheet.c +2 -2
- data/lib/nokogiri/css/parser.rb +108 -90
- data/lib/nokogiri/css/parser.y +13 -2
- data/lib/nokogiri/css/tokenizer.rb +1 -1
- data/lib/nokogiri/css/tokenizer.rex +4 -4
- data/lib/nokogiri/css/xpath_visitor.rb +10 -3
- data/lib/nokogiri/html/document_fragment.rb +11 -1
- data/lib/nokogiri/version.rb +1 -1
- data/lib/nokogiri/xml/node.rb +58 -0
- data/lib/nokogiri/xml/node_set.rb +32 -18
- data/patches/libxml2/0001-Revert-Do-not-URI-escape-in-server-side-includes.patch +78 -0
- data/ports/archives/libxml2-2.9.8.tar.gz +0 -0
- data/test/css/test_nthiness.rb +21 -21
- data/test/css/test_parser.rb +17 -0
- data/test/html/test_attributes.rb +85 -0
- data/test/html/test_document_fragment.rb +7 -1
- data/test/test_css_cache.rb +5 -3
- data/test/xml/sax/test_parser.rb +9 -1
- data/test/xml/sax/test_push_parser.rb +60 -0
- data/test/xml/test_cdata.rb +1 -1
- data/test/xml/test_document.rb +5 -5
- data/test/xml/test_dtd.rb +4 -4
- data/test/xml/test_node.rb +89 -6
- data/test/xml/test_node_attributes.rb +3 -3
- data/test/xml/test_node_reparenting.rb +18 -0
- data/test/xml/test_node_set.rb +31 -4
- data/test/xml/test_reader.rb +13 -1
- data/test/xml/test_syntax_error.rb +3 -3
- data/test/xml/test_xpath.rb +8 -0
- metadata +25 -4
- data/ports/archives/libxml2-2.9.7.tar.gz +0 -0
data/lib/nokogiri/css/parser.y
CHANGED
@@ -221,8 +221,9 @@ rule
|
|
221
221
|
: HASH { result = Node.new(:ID, [unescape_css_identifier(val.first)]) }
|
222
222
|
;
|
223
223
|
attrib_val_0or1
|
224
|
-
: eql_incl_dash IDENT { result = [val.first, val[1]] }
|
225
|
-
| eql_incl_dash STRING { result = [val.first, val[1]] }
|
224
|
+
: eql_incl_dash IDENT { result = [val.first, unescape_css_identifier(val[1])] }
|
225
|
+
| eql_incl_dash STRING { result = [val.first, unescape_css_string(val[1])] }
|
226
|
+
| eql_incl_dash NUMBER { result = [val.first, val[1]] }
|
226
227
|
|
|
227
228
|
;
|
228
229
|
eql_incl_dash
|
@@ -259,3 +260,13 @@ require 'nokogiri/css/parser_extras'
|
|
259
260
|
def unescape_css_identifier(identifier)
|
260
261
|
identifier.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})\s?)/){ |m| $1 || [$2.hex].pack('U') }
|
261
262
|
end
|
263
|
+
|
264
|
+
def unescape_css_string(str)
|
265
|
+
str.gsub(/\\(?:([^0-9a-fA-F])|([0-9a-fA-F]{1,6})\s?)/) do |m|
|
266
|
+
if $1=="\n"
|
267
|
+
''
|
268
|
+
else
|
269
|
+
$1 || [$2.hex].pack('U')
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
@@ -130,7 +130,7 @@ class Tokenizer # :nodoc:
|
|
130
130
|
when (text = @ss.scan(/[\s]+/))
|
131
131
|
action { [:S, text] }
|
132
132
|
|
133
|
-
when (text = @ss.scan(/"([^\n\r\f"]|\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*"|'([^\n\r\f']|\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*'/))
|
133
|
+
when (text = @ss.scan(/"([^\n\r\f"]|\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*(?<!\\)(?:\\{2})*"|'([^\n\r\f']|\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*(?<!\\)(?:\\{2})*'/))
|
134
134
|
action { [:STRING, text] }
|
135
135
|
|
136
136
|
when (text = @ss.scan(/./))
|
@@ -14,8 +14,8 @@ macro
|
|
14
14
|
nmstart [_A-Za-z]|{nonascii}|{escape}
|
15
15
|
ident [-@]?({nmstart})({nmchar})*
|
16
16
|
name ({nmchar})+
|
17
|
-
string1 "([^\n\r\f"]|{nl}|{nonascii}|{escape})*"
|
18
|
-
string2 '([^\n\r\f']|{nl}|{nonascii}|{escape})*'
|
17
|
+
string1 "([^\n\r\f"]|{nl}|{nonascii}|{escape})*(?<!\\)(?:\\{2})*"
|
18
|
+
string2 '([^\n\r\f']|{nl}|{nonascii}|{escape})*(?<!\\)(?:\\{2})*'
|
19
19
|
string {string1}|{string2}
|
20
20
|
|
21
21
|
rule
|
@@ -44,9 +44,9 @@ rule
|
|
44
44
|
{num} { [:NUMBER, text] }
|
45
45
|
{w}\/\/{w} { [:DOUBLESLASH, text] }
|
46
46
|
{w}\/{w} { [:SLASH, text] }
|
47
|
-
|
47
|
+
|
48
48
|
U\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})? {[:UNICODE_RANGE, text] }
|
49
|
-
|
49
|
+
|
50
50
|
[\s]+ { [:S, text] }
|
51
51
|
{string} { [:STRING, text] }
|
52
52
|
. { [text, text] }
|
@@ -88,6 +88,13 @@ module Nokogiri
|
|
88
88
|
value = node.value.last
|
89
89
|
value = "'#{value}'" if value !~ /^['"]/
|
90
90
|
|
91
|
+
if (value[0]==value[-1]) && %q{"'}.include?(value[0])
|
92
|
+
str_value = value[1..-2]
|
93
|
+
if str_value.include?(value[0])
|
94
|
+
value = 'concat("' + str_value.split('"', -1).join(%q{", '"', "}) + '", "")'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
91
98
|
case node.value[1]
|
92
99
|
when :equal
|
93
100
|
attribute + " = " + "#{value}"
|
@@ -145,7 +152,7 @@ module Nokogiri
|
|
145
152
|
"#{node.value.first.accept(self) if node.value.first} and #{node.value.last.accept(self)}"
|
146
153
|
end
|
147
154
|
end
|
148
|
-
|
155
|
+
|
149
156
|
{
|
150
157
|
'direct_adjacent_selector' => "/following-sibling::*[1]/self::",
|
151
158
|
'following_selector' => "/following-sibling::",
|
@@ -208,7 +215,7 @@ module Nokogiri
|
|
208
215
|
end
|
209
216
|
[a, b]
|
210
217
|
end
|
211
|
-
|
218
|
+
|
212
219
|
def is_of_type_pseudo_class? node
|
213
220
|
if node.type==:PSEUDO_CLASS
|
214
221
|
if node.value[0].is_a?(Nokogiri::CSS::Node) and node.value[0].type == :FUNCTION
|
@@ -216,7 +223,7 @@ module Nokogiri
|
|
216
223
|
else
|
217
224
|
node.value[0]
|
218
225
|
end =~ /(nth|first|last|only)-of-type(\()?/
|
219
|
-
end
|
226
|
+
end
|
220
227
|
end
|
221
228
|
end
|
222
229
|
end
|
@@ -6,7 +6,17 @@ module Nokogiri
|
|
6
6
|
def self.parse tags, encoding = nil
|
7
7
|
doc = HTML::Document.new
|
8
8
|
|
9
|
-
encoding ||= tags.respond_to?(:encoding)
|
9
|
+
encoding ||= if tags.respond_to?(:encoding)
|
10
|
+
encoding = tags.encoding
|
11
|
+
if encoding == ::Encoding::ASCII_8BIT
|
12
|
+
'UTF-8'
|
13
|
+
else
|
14
|
+
encoding.name
|
15
|
+
end
|
16
|
+
else
|
17
|
+
'UTF-8'
|
18
|
+
end
|
19
|
+
|
10
20
|
doc.encoding = encoding
|
11
21
|
|
12
22
|
new(doc, tags)
|
data/lib/nokogiri/version.rb
CHANGED
data/lib/nokogiri/xml/node.rb
CHANGED
@@ -350,6 +350,64 @@ module Nokogiri
|
|
350
350
|
}
|
351
351
|
end
|
352
352
|
|
353
|
+
###
|
354
|
+
# Get the list of class names of this Node, without
|
355
|
+
# deduplication or sorting.
|
356
|
+
def classes
|
357
|
+
self['class'].to_s.scan(/\S+/)
|
358
|
+
end
|
359
|
+
|
360
|
+
###
|
361
|
+
# Add +name+ to the "class" attribute value of this Node and
|
362
|
+
# return self. If the value is already in the current value, it
|
363
|
+
# is not added. If no "class" attribute exists yet, one is
|
364
|
+
# created with the given value.
|
365
|
+
#
|
366
|
+
# More than one class may be added at a time, separated by a
|
367
|
+
# space.
|
368
|
+
def add_class name
|
369
|
+
names = classes
|
370
|
+
self['class'] = (names + (name.scan(/\S+/) - names)).join(' ')
|
371
|
+
self
|
372
|
+
end
|
373
|
+
|
374
|
+
###
|
375
|
+
# Append +name+ to the "class" attribute value of this Node and
|
376
|
+
# return self. The value is simply appended without checking if
|
377
|
+
# it is already in the current value. If no "class" attribute
|
378
|
+
# exists yet, one is created with the given value.
|
379
|
+
#
|
380
|
+
# More than one class may be appended at a time, separated by a
|
381
|
+
# space.
|
382
|
+
def append_class name
|
383
|
+
self['class'] = (classes + name.scan(/\S+/)).join(' ')
|
384
|
+
self
|
385
|
+
end
|
386
|
+
|
387
|
+
###
|
388
|
+
# Remove +name+ from the "class" attribute value of this Node
|
389
|
+
# and return self. If there are many occurrences of the name,
|
390
|
+
# they are all removed.
|
391
|
+
#
|
392
|
+
# More than one class may be removed at a time, separated by a
|
393
|
+
# space.
|
394
|
+
#
|
395
|
+
# If no class name is left after removal, or when +name+ is nil,
|
396
|
+
# the "class" attribute is removed from this Node.
|
397
|
+
def remove_class name = nil
|
398
|
+
if name
|
399
|
+
names = classes - name.scan(/\S+/)
|
400
|
+
if names.empty?
|
401
|
+
delete 'class'
|
402
|
+
else
|
403
|
+
self['class'] = names.join(' ')
|
404
|
+
end
|
405
|
+
else
|
406
|
+
delete "class"
|
407
|
+
end
|
408
|
+
self
|
409
|
+
end
|
410
|
+
|
353
411
|
###
|
354
412
|
# Remove the attribute named +name+
|
355
413
|
def remove_attribute name
|
@@ -43,9 +43,14 @@ module Nokogiri
|
|
43
43
|
end
|
44
44
|
|
45
45
|
###
|
46
|
-
# Returns the index of the first node in self that is == to +node
|
47
|
-
def index(node)
|
48
|
-
|
46
|
+
# Returns the index of the first node in self that is == to +node+ or meets the given block. Returns nil if no match is found.
|
47
|
+
def index(node = nil, &block)
|
48
|
+
if node
|
49
|
+
warn "given block not used" if block_given?
|
50
|
+
each_with_index { |member, j| return j if member == node }
|
51
|
+
elsif block_given?
|
52
|
+
each_with_index { |member, j| return j if yield(member) }
|
53
|
+
end
|
49
54
|
nil
|
50
55
|
end
|
51
56
|
|
@@ -130,31 +135,37 @@ module Nokogiri
|
|
130
135
|
end
|
131
136
|
|
132
137
|
###
|
133
|
-
#
|
138
|
+
# Add the class attribute +name+ to all Node objects in the
|
139
|
+
# NodeSet.
|
140
|
+
#
|
141
|
+
# See Nokogiri::XML::Node#add_class for more information.
|
134
142
|
def add_class name
|
135
143
|
each do |el|
|
136
|
-
|
137
|
-
|
144
|
+
el.add_class(name)
|
145
|
+
end
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
###
|
150
|
+
# Append the class attribute +name+ to all Node objects in the
|
151
|
+
# NodeSet.
|
152
|
+
#
|
153
|
+
# See Nokogiri::XML::Node#append_class for more information.
|
154
|
+
def append_class name
|
155
|
+
each do |el|
|
156
|
+
el.append_class(name)
|
138
157
|
end
|
139
158
|
self
|
140
159
|
end
|
141
160
|
|
142
161
|
###
|
143
|
-
# Remove the class attribute +name+ from all Node objects in the
|
144
|
-
# If +name+ is nil, remove the class attribute from all Nodes in the
|
162
|
+
# Remove the class attribute +name+ from all Node objects in the
|
145
163
|
# NodeSet.
|
164
|
+
#
|
165
|
+
# See Nokogiri::XML::Node#remove_class for more information.
|
146
166
|
def remove_class name = nil
|
147
167
|
each do |el|
|
148
|
-
|
149
|
-
classes = el['class'].to_s.split(/\s+/)
|
150
|
-
if classes.empty?
|
151
|
-
el.delete 'class'
|
152
|
-
else
|
153
|
-
el['class'] = (classes - [name]).uniq.join " "
|
154
|
-
end
|
155
|
-
else
|
156
|
-
el.delete "class"
|
157
|
-
end
|
168
|
+
el.remove_class(name)
|
158
169
|
end
|
159
170
|
self
|
160
171
|
end
|
@@ -182,10 +193,13 @@ module Nokogiri
|
|
182
193
|
each { |el| el.delete name }
|
183
194
|
self
|
184
195
|
end
|
196
|
+
alias remove_attribute remove_attr
|
185
197
|
|
186
198
|
###
|
187
199
|
# Iterate over each node, yielding to +block+
|
188
200
|
def each(&block)
|
201
|
+
return to_enum unless block_given?
|
202
|
+
|
189
203
|
0.upto(length - 1) do |x|
|
190
204
|
yield self[x]
|
191
205
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
From c5538465c08a8ea248a370bf55bc39cd3385e4af Mon Sep 17 00:00:00 2001
|
2
|
+
From: Mike Dalessio <mike.dalessio@gmail.com>
|
3
|
+
Date: Thu, 29 Mar 2018 14:09:00 -0400
|
4
|
+
Subject: [PATCH] Revert "Do not URI escape in server side includes"
|
5
|
+
|
6
|
+
This reverts commit 960f0e275616cadc29671a218d7fb9b69eb35588.
|
7
|
+
---
|
8
|
+
HTMLtree.c | 49 +++++++++++--------------------------------------
|
9
|
+
1 file changed, 11 insertions(+), 38 deletions(-)
|
10
|
+
|
11
|
+
diff --git a/HTMLtree.c b/HTMLtree.c
|
12
|
+
index 2fd0c9c..67160c5 100644
|
13
|
+
--- a/HTMLtree.c
|
14
|
+
+++ b/HTMLtree.c
|
15
|
+
@@ -717,49 +717,22 @@ htmlAttrDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc, xmlAttrPtr cur,
|
16
|
+
(!xmlStrcasecmp(cur->name, BAD_CAST "src")) ||
|
17
|
+
((!xmlStrcasecmp(cur->name, BAD_CAST "name")) &&
|
18
|
+
(!xmlStrcasecmp(cur->parent->name, BAD_CAST "a"))))) {
|
19
|
+
+ xmlChar *escaped;
|
20
|
+
xmlChar *tmp = value;
|
21
|
+
- /* xmlURIEscapeStr() escapes '"' so it can be safely used. */
|
22
|
+
- xmlBufCCat(buf->buffer, "\"");
|
23
|
+
|
24
|
+
while (IS_BLANK_CH(*tmp)) tmp++;
|
25
|
+
|
26
|
+
- /* URI Escape everything, except server side includes. */
|
27
|
+
- for ( ; ; ) {
|
28
|
+
- xmlChar *escaped;
|
29
|
+
- xmlChar endChar;
|
30
|
+
- xmlChar *end = NULL;
|
31
|
+
- xmlChar *start = (xmlChar *)xmlStrstr(tmp, BAD_CAST "<!--");
|
32
|
+
- if (start != NULL) {
|
33
|
+
- end = (xmlChar *)xmlStrstr(tmp, BAD_CAST "-->");
|
34
|
+
- if (end != NULL) {
|
35
|
+
- *start = '\0';
|
36
|
+
- }
|
37
|
+
- }
|
38
|
+
-
|
39
|
+
- /* Escape the whole string, or until start (set to '\0'). */
|
40
|
+
- escaped = xmlURIEscapeStr(tmp, BAD_CAST"@/:=?;#%&,+");
|
41
|
+
- if (escaped != NULL) {
|
42
|
+
- xmlBufCat(buf->buffer, escaped);
|
43
|
+
- xmlFree(escaped);
|
44
|
+
- } else {
|
45
|
+
- xmlBufCat(buf->buffer, tmp);
|
46
|
+
- }
|
47
|
+
-
|
48
|
+
- if (end == NULL) { /* Everything has been written. */
|
49
|
+
- break;
|
50
|
+
- }
|
51
|
+
-
|
52
|
+
- /* Do not escape anything within server side includes. */
|
53
|
+
- *start = '<'; /* Restore the first character of "<!--". */
|
54
|
+
- end += 3; /* strlen("-->") */
|
55
|
+
- endChar = *end;
|
56
|
+
- *end = '\0';
|
57
|
+
- xmlBufCat(buf->buffer, start);
|
58
|
+
- *end = endChar;
|
59
|
+
- tmp = end;
|
60
|
+
+ /*
|
61
|
+
+ * the < and > have already been escaped at the entity level
|
62
|
+
+ * And doing so here breaks server side includes
|
63
|
+
+ */
|
64
|
+
+ escaped = xmlURIEscapeStr(tmp, BAD_CAST"@/:=?;#%&,+<>");
|
65
|
+
+ if (escaped != NULL) {
|
66
|
+
+ xmlBufWriteQuotedString(buf->buffer, escaped);
|
67
|
+
+ xmlFree(escaped);
|
68
|
+
+ } else {
|
69
|
+
+ xmlBufWriteQuotedString(buf->buffer, value);
|
70
|
+
}
|
71
|
+
-
|
72
|
+
- xmlBufCCat(buf->buffer, "\"");
|
73
|
+
} else {
|
74
|
+
xmlBufWriteQuotedString(buf->buffer, value);
|
75
|
+
}
|
76
|
+
--
|
77
|
+
2.9.5
|
78
|
+
|
Binary file
|
data/test/css/test_nthiness.rb
CHANGED
@@ -71,83 +71,83 @@ EOF
|
|
71
71
|
|
72
72
|
|
73
73
|
def test_even
|
74
|
-
assert_result_rows [2,4,6,8,10,12,14], @parser.search("table
|
74
|
+
assert_result_rows [2,4,6,8,10,12,14], @parser.search("table//tr:nth(even)")
|
75
75
|
end
|
76
76
|
|
77
77
|
def test_odd
|
78
|
-
assert_result_rows [1,3,5,7,9,11,13], @parser.search("table
|
78
|
+
assert_result_rows [1,3,5,7,9,11,13], @parser.search("table//tr:nth(odd)")
|
79
79
|
end
|
80
80
|
|
81
81
|
def test_n
|
82
|
-
assert_result_rows((1..14).to_a, @parser.search("table
|
82
|
+
assert_result_rows((1..14).to_a, @parser.search("table//tr:nth(n)"))
|
83
83
|
end
|
84
84
|
|
85
85
|
def test_2n
|
86
|
-
assert_equal @parser.search("table
|
86
|
+
assert_equal @parser.search("table//tr:nth(even)").inner_text, @parser.search("table//tr:nth(2n)").inner_text
|
87
87
|
end
|
88
88
|
|
89
89
|
def test_2np1
|
90
|
-
assert_equal @parser.search("table
|
90
|
+
assert_equal @parser.search("table//tr:nth(odd)").inner_text, @parser.search("table//tr:nth(2n+1)").inner_text
|
91
91
|
end
|
92
92
|
|
93
93
|
def test_4np3
|
94
|
-
assert_result_rows [3,7,11], @parser.search("table
|
94
|
+
assert_result_rows [3,7,11], @parser.search("table//tr:nth(4n+3)")
|
95
95
|
end
|
96
96
|
|
97
97
|
def test_3np4
|
98
|
-
assert_result_rows [4,7,10,13], @parser.search("table
|
98
|
+
assert_result_rows [4,7,10,13], @parser.search("table//tr:nth(3n+4)")
|
99
99
|
end
|
100
100
|
|
101
101
|
def test_mnp3
|
102
|
-
assert_result_rows [1,2,3], @parser.search("table
|
102
|
+
assert_result_rows [1,2,3], @parser.search("table//tr:nth(-n+3)")
|
103
103
|
end
|
104
104
|
|
105
105
|
def test_4nm1
|
106
|
-
assert_result_rows [3,7,11], @parser.search("table
|
106
|
+
assert_result_rows [3,7,11], @parser.search("table//tr:nth(4n-1)")
|
107
107
|
end
|
108
108
|
|
109
109
|
def test_np3
|
110
|
-
assert_result_rows [3,4,5,6,7,8,9,10,11,12,13,14], @parser.search("table
|
110
|
+
assert_result_rows [3,4,5,6,7,8,9,10,11,12,13,14], @parser.search("table//tr:nth(n+3)")
|
111
111
|
end
|
112
112
|
|
113
113
|
def test_first
|
114
|
-
assert_result_rows [1], @parser.search("table
|
115
|
-
assert_result_rows [1], @parser.search("table
|
114
|
+
assert_result_rows [1], @parser.search("table//tr:first")
|
115
|
+
assert_result_rows [1], @parser.search("table//tr:first()")
|
116
116
|
end
|
117
117
|
|
118
118
|
def test_last
|
119
|
-
assert_result_rows [14], @parser.search("table
|
120
|
-
assert_result_rows [14], @parser.search("table
|
119
|
+
assert_result_rows [14], @parser.search("table//tr:last")
|
120
|
+
assert_result_rows [14], @parser.search("table//tr:last()")
|
121
121
|
end
|
122
122
|
|
123
123
|
def test_first_child
|
124
124
|
assert_result_rows [1], @parser.search("div/b:first-child"), "bold"
|
125
|
-
assert_result_rows [1], @parser.search("table
|
125
|
+
assert_result_rows [1], @parser.search("table//tr:first-child")
|
126
126
|
assert_result_rows [2,4], @parser.search("div/h1.c:first-child"), "header"
|
127
127
|
end
|
128
128
|
|
129
129
|
def test_last_child
|
130
130
|
assert_result_rows [3], @parser.search("div/b:last-child"), "bold"
|
131
|
-
assert_result_rows [14], @parser.search("table
|
131
|
+
assert_result_rows [14], @parser.search("table//tr:last-child")
|
132
132
|
assert_result_rows [3,4], @parser.search("div/h1.c:last-child"), "header"
|
133
133
|
end
|
134
134
|
|
135
135
|
def test_nth_child
|
136
136
|
assert_result_rows [2], @parser.search("div/b:nth-child(3)"), "bold"
|
137
|
-
assert_result_rows [5], @parser.search("table
|
137
|
+
assert_result_rows [5], @parser.search("table//tr:nth-child(5)")
|
138
138
|
assert_result_rows [1,3], @parser.search("div/h1.c:nth-child(2)"), "header"
|
139
139
|
assert_result_rows [3,4], @parser.search("div/i.b:nth-child(2n+1)"), "italic"
|
140
140
|
end
|
141
141
|
|
142
142
|
def test_first_of_type
|
143
|
-
assert_result_rows [1], @parser.search("table
|
143
|
+
assert_result_rows [1], @parser.search("table//tr:first-of-type")
|
144
144
|
assert_result_rows [1], @parser.search("div/b:first-of-type"), "bold"
|
145
145
|
assert_result_rows [2], @parser.search("div/b.a:first-of-type"), "bold"
|
146
146
|
assert_result_rows [3], @parser.search("div/i.b:first-of-type"), "italic"
|
147
147
|
end
|
148
148
|
|
149
149
|
def test_last_of_type
|
150
|
-
assert_result_rows [14], @parser.search("table
|
150
|
+
assert_result_rows [14], @parser.search("table//tr:last-of-type")
|
151
151
|
assert_result_rows [3], @parser.search("div/b:last-of-type"), "bold"
|
152
152
|
assert_result_rows [2,7], @parser.search("div/i:last-of-type"), "italic"
|
153
153
|
assert_result_rows [2,6,7], @parser.search("div i:last-of-type"), "italic"
|
@@ -165,8 +165,8 @@ EOF
|
|
165
165
|
end
|
166
166
|
|
167
167
|
def test_nth_last_of_type
|
168
|
-
assert_result_rows [14], @parser.search("table
|
169
|
-
assert_result_rows [12], @parser.search("table
|
168
|
+
assert_result_rows [14], @parser.search("table//tr:nth-last-of-type(1)")
|
169
|
+
assert_result_rows [12], @parser.search("table//tr:nth-last-of-type(3)")
|
170
170
|
assert_result_rows [2,6,7], @parser.search("div i:nth-last-of-type(1)"), "italic"
|
171
171
|
assert_result_rows [1,5], @parser.search("div i:nth-last-of-type(2)"), "italic"
|
172
172
|
assert_result_rows [4], @parser.search("div/i.b:nth-last-of-type(1)"), "italic"
|