nokogiri 1.1.1-java

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 (142) hide show
  1. data/History.ja.txt +99 -0
  2. data/History.txt +99 -0
  3. data/Manifest.txt +141 -0
  4. data/README.ja.txt +100 -0
  5. data/README.txt +109 -0
  6. data/Rakefile +354 -0
  7. data/ext/nokogiri/extconf.rb +93 -0
  8. data/ext/nokogiri/html_document.c +86 -0
  9. data/ext/nokogiri/html_document.h +10 -0
  10. data/ext/nokogiri/html_sax_parser.c +36 -0
  11. data/ext/nokogiri/html_sax_parser.h +11 -0
  12. data/ext/nokogiri/native.c +41 -0
  13. data/ext/nokogiri/native.h +50 -0
  14. data/ext/nokogiri/xml_cdata.c +44 -0
  15. data/ext/nokogiri/xml_cdata.h +9 -0
  16. data/ext/nokogiri/xml_comment.c +42 -0
  17. data/ext/nokogiri/xml_comment.h +9 -0
  18. data/ext/nokogiri/xml_document.c +206 -0
  19. data/ext/nokogiri/xml_document.h +10 -0
  20. data/ext/nokogiri/xml_dtd.c +121 -0
  21. data/ext/nokogiri/xml_dtd.h +8 -0
  22. data/ext/nokogiri/xml_io.c +17 -0
  23. data/ext/nokogiri/xml_io.h +9 -0
  24. data/ext/nokogiri/xml_node.c +727 -0
  25. data/ext/nokogiri/xml_node.h +13 -0
  26. data/ext/nokogiri/xml_node_set.c +118 -0
  27. data/ext/nokogiri/xml_node_set.h +9 -0
  28. data/ext/nokogiri/xml_reader.c +465 -0
  29. data/ext/nokogiri/xml_reader.h +10 -0
  30. data/ext/nokogiri/xml_sax_parser.c +201 -0
  31. data/ext/nokogiri/xml_sax_parser.h +10 -0
  32. data/ext/nokogiri/xml_syntax_error.c +199 -0
  33. data/ext/nokogiri/xml_syntax_error.h +11 -0
  34. data/ext/nokogiri/xml_text.c +40 -0
  35. data/ext/nokogiri/xml_text.h +9 -0
  36. data/ext/nokogiri/xml_xpath.c +53 -0
  37. data/ext/nokogiri/xml_xpath.h +11 -0
  38. data/ext/nokogiri/xml_xpath_context.c +214 -0
  39. data/ext/nokogiri/xml_xpath_context.h +9 -0
  40. data/ext/nokogiri/xslt_stylesheet.c +123 -0
  41. data/ext/nokogiri/xslt_stylesheet.h +9 -0
  42. data/lib/action-nokogiri.rb +30 -0
  43. data/lib/nokogiri.rb +72 -0
  44. data/lib/nokogiri/css.rb +25 -0
  45. data/lib/nokogiri/css/generated_parser.rb +721 -0
  46. data/lib/nokogiri/css/generated_tokenizer.rb +159 -0
  47. data/lib/nokogiri/css/node.rb +97 -0
  48. data/lib/nokogiri/css/parser.rb +64 -0
  49. data/lib/nokogiri/css/parser.y +216 -0
  50. data/lib/nokogiri/css/syntax_error.rb +6 -0
  51. data/lib/nokogiri/css/tokenizer.rb +9 -0
  52. data/lib/nokogiri/css/tokenizer.rex +63 -0
  53. data/lib/nokogiri/css/xpath_visitor.rb +168 -0
  54. data/lib/nokogiri/decorators.rb +2 -0
  55. data/lib/nokogiri/decorators/hpricot.rb +3 -0
  56. data/lib/nokogiri/decorators/hpricot/node.rb +56 -0
  57. data/lib/nokogiri/decorators/hpricot/node_set.rb +54 -0
  58. data/lib/nokogiri/decorators/hpricot/xpath_visitor.rb +28 -0
  59. data/lib/nokogiri/decorators/slop.rb +31 -0
  60. data/lib/nokogiri/hpricot.rb +51 -0
  61. data/lib/nokogiri/html.rb +105 -0
  62. data/lib/nokogiri/html/builder.rb +9 -0
  63. data/lib/nokogiri/html/document.rb +9 -0
  64. data/lib/nokogiri/html/sax/parser.rb +21 -0
  65. data/lib/nokogiri/version.rb +3 -0
  66. data/lib/nokogiri/xml.rb +83 -0
  67. data/lib/nokogiri/xml/after_handler.rb +18 -0
  68. data/lib/nokogiri/xml/attr.rb +10 -0
  69. data/lib/nokogiri/xml/before_handler.rb +33 -0
  70. data/lib/nokogiri/xml/builder.rb +84 -0
  71. data/lib/nokogiri/xml/cdata.rb +9 -0
  72. data/lib/nokogiri/xml/comment.rb +6 -0
  73. data/lib/nokogiri/xml/document.rb +55 -0
  74. data/lib/nokogiri/xml/dtd.rb +6 -0
  75. data/lib/nokogiri/xml/element.rb +6 -0
  76. data/lib/nokogiri/xml/entity_declaration.rb +9 -0
  77. data/lib/nokogiri/xml/node.rb +333 -0
  78. data/lib/nokogiri/xml/node_set.rb +197 -0
  79. data/lib/nokogiri/xml/notation.rb +6 -0
  80. data/lib/nokogiri/xml/reader.rb +20 -0
  81. data/lib/nokogiri/xml/sax.rb +9 -0
  82. data/lib/nokogiri/xml/sax/document.rb +59 -0
  83. data/lib/nokogiri/xml/sax/parser.rb +37 -0
  84. data/lib/nokogiri/xml/syntax_error.rb +21 -0
  85. data/lib/nokogiri/xml/text.rb +6 -0
  86. data/lib/nokogiri/xml/xpath.rb +10 -0
  87. data/lib/nokogiri/xml/xpath/syntax_error.rb +8 -0
  88. data/lib/nokogiri/xml/xpath_context.rb +14 -0
  89. data/lib/nokogiri/xslt.rb +28 -0
  90. data/lib/nokogiri/xslt/stylesheet.rb +6 -0
  91. data/test/css/test_nthiness.rb +159 -0
  92. data/test/css/test_parser.rb +237 -0
  93. data/test/css/test_tokenizer.rb +162 -0
  94. data/test/css/test_xpath_visitor.rb +64 -0
  95. data/test/files/dont_hurt_em_why.xml +422 -0
  96. data/test/files/exslt.xml +8 -0
  97. data/test/files/exslt.xslt +35 -0
  98. data/test/files/staff.xml +59 -0
  99. data/test/files/staff.xslt +32 -0
  100. data/test/files/tlm.html +850 -0
  101. data/test/helper.rb +78 -0
  102. data/test/hpricot/files/basic.xhtml +17 -0
  103. data/test/hpricot/files/boingboing.html +2266 -0
  104. data/test/hpricot/files/cy0.html +3653 -0
  105. data/test/hpricot/files/immob.html +400 -0
  106. data/test/hpricot/files/pace_application.html +1320 -0
  107. data/test/hpricot/files/tenderlove.html +16 -0
  108. data/test/hpricot/files/uswebgen.html +220 -0
  109. data/test/hpricot/files/utf8.html +1054 -0
  110. data/test/hpricot/files/week9.html +1723 -0
  111. data/test/hpricot/files/why.xml +19 -0
  112. data/test/hpricot/load_files.rb +11 -0
  113. data/test/hpricot/test_alter.rb +67 -0
  114. data/test/hpricot/test_builder.rb +27 -0
  115. data/test/hpricot/test_parser.rb +426 -0
  116. data/test/hpricot/test_paths.rb +15 -0
  117. data/test/hpricot/test_preserved.rb +77 -0
  118. data/test/hpricot/test_xml.rb +30 -0
  119. data/test/html/sax/test_parser.rb +27 -0
  120. data/test/html/test_builder.rb +89 -0
  121. data/test/html/test_document.rb +150 -0
  122. data/test/html/test_node.rb +21 -0
  123. data/test/test_convert_xpath.rb +185 -0
  124. data/test/test_css_cache.rb +57 -0
  125. data/test/test_gc.rb +15 -0
  126. data/test/test_memory_leak.rb +38 -0
  127. data/test/test_nokogiri.rb +97 -0
  128. data/test/test_reader.rb +222 -0
  129. data/test/test_xslt_transforms.rb +93 -0
  130. data/test/xml/sax/test_parser.rb +95 -0
  131. data/test/xml/test_attr.rb +15 -0
  132. data/test/xml/test_builder.rb +16 -0
  133. data/test/xml/test_cdata.rb +18 -0
  134. data/test/xml/test_comment.rb +16 -0
  135. data/test/xml/test_document.rb +195 -0
  136. data/test/xml/test_dtd.rb +43 -0
  137. data/test/xml/test_node.rb +394 -0
  138. data/test/xml/test_node_set.rb +143 -0
  139. data/test/xml/test_text.rb +13 -0
  140. data/test/xml/test_xpath.rb +105 -0
  141. data/vendor/hoe.rb +1020 -0
  142. metadata +233 -0
@@ -0,0 +1,6 @@
1
+ module Nokogiri
2
+ module XML
3
+ class Notation < Struct.new(:name, :public_id, :system_id)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ module Nokogiri
2
+ module XML
3
+ class Reader
4
+ include Enumerable
5
+
6
+ def attributes
7
+ Hash[*(attribute_nodes.map { |node|
8
+ [node.name, node.to_s]
9
+ }.flatten)].merge(namespaces || {})
10
+ end
11
+
12
+ def each(&block)
13
+ while node = self.read
14
+ block.call(node)
15
+ end
16
+ end
17
+ private :initialize
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ require 'nokogiri/xml/sax/document'
2
+ require 'nokogiri/xml/sax/parser'
3
+
4
+ module Nokogiri
5
+ module XML
6
+ module SAX
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,59 @@
1
+ module Nokogiri
2
+ module XML
3
+ module SAX
4
+ class Document
5
+ ###
6
+ # Called when document starts parsing
7
+ def start_document
8
+ end
9
+
10
+ ###
11
+ # Called when document ends parsing
12
+ def end_document
13
+ end
14
+
15
+ ###
16
+ # Called at the beginning of an element
17
+ # +name+ is the name of the tag with +attrs+ as attributes
18
+ def start_element name, attrs = []
19
+ end
20
+
21
+ ###
22
+ # Called at the end of an element
23
+ # +name+ is the tag name
24
+ def end_element name
25
+ end
26
+
27
+ ###
28
+ # Characters read between a tag
29
+ # +string+ contains the character data
30
+ def characters string
31
+ end
32
+
33
+ ###
34
+ # Called when comments are encountered
35
+ # +string+ contains the comment data
36
+ def comment string
37
+ end
38
+
39
+ ###
40
+ # Called on document warnings
41
+ # +string+ contains the warning
42
+ def warning string
43
+ end
44
+
45
+ ###
46
+ # Called on document errors
47
+ # +string+ contains the error
48
+ def error string
49
+ end
50
+
51
+ ###
52
+ # Called when cdata blocks are found
53
+ # +string+ contains the cdata content
54
+ def cdata_block string
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ module Nokogiri
2
+ module XML
3
+ module SAX
4
+ class Parser
5
+ attr_accessor :document
6
+ def initialize(doc = XML::SAX::Document.new)
7
+ @document = doc
8
+ end
9
+
10
+ ###
11
+ # Parse given +thing+ which may be a string containing xml, or an
12
+ # IO object.
13
+ def parse thing
14
+ if thing.respond_to?(:read) && thing.respond_to?(:close)
15
+ parse_io(thing)
16
+ else
17
+ parse_memory(thing)
18
+ end
19
+ end
20
+
21
+ ###
22
+ # Parse given +io+
23
+ def parse_io io, encoding = 0
24
+ native_parse_io io, encoding
25
+ end
26
+
27
+ ###
28
+ # Parse a file with +filename+
29
+ def parse_file filename
30
+ raise Errno::ENOENT unless File.exists?(filename)
31
+ raise Errno::EISDIR if File.directory?(filename)
32
+ native_parse_file filename
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ module Nokogiri
2
+ module XML
3
+ class SyntaxError < SyntaxError
4
+ def none?
5
+ level == 0
6
+ end
7
+
8
+ def warning?
9
+ level == 1
10
+ end
11
+
12
+ def error?
13
+ level == 2
14
+ end
15
+
16
+ def fatal?
17
+ level == 3
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ module Nokogiri
2
+ module XML
3
+ class Text < Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ require 'nokogiri/xml/xpath/syntax_error'
2
+
3
+ module Nokogiri
4
+ module XML
5
+ class XPath
6
+ attr_accessor :document
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module Nokogiri
2
+ module XML
3
+ class XPath
4
+ class SyntaxError < ::SyntaxError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ module Nokogiri
2
+ module XML
3
+ class XPathContext
4
+
5
+ def register_namespaces(namespaces)
6
+ namespaces.each do |k, v|
7
+ k = k.gsub(/.*:/,'') # strip off 'xmlns:' or 'xml:'
8
+ register_ns(k, v)
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ require 'nokogiri/xslt/stylesheet'
2
+
3
+ module Nokogiri
4
+ module XSLT
5
+ class << self
6
+ def parse(string)
7
+ Stylesheet.parse_stylesheet_doc(XML.parse(string))
8
+ end
9
+
10
+ def quote_params params
11
+ parray = (params.instance_of?(Hash) ? params.to_a.flatten : params).dup
12
+ parray.each_with_index do |v,i|
13
+ if i % 2 > 0
14
+ parray[i]=
15
+ if v =~ /'/
16
+ "concat('#{ v.gsub(/'/, %q{', "'", '}) }')"
17
+ else
18
+ "'#{v}'";
19
+ end
20
+ else
21
+ parray[i] = v.to_s
22
+ end
23
+ end
24
+ parray.flatten
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ module Nokogiri
2
+ module XSLT
3
+ class Stylesheet
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,159 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', "helper"))
2
+
3
+ module Nokogiri
4
+ module CSS
5
+ class TestNthiness < Nokogiri::TestCase
6
+ def setup
7
+ doc = <<EOF
8
+ <html>
9
+ <table>
10
+ <tr><td>row1 </td></tr>
11
+ <tr><td>row2 </td></tr>
12
+ <tr><td>row3 </td></tr>
13
+ <tr><td>row4 </td></tr>
14
+ <tr><td>row5 </td></tr>
15
+ <tr><td>row6 </td></tr>
16
+ <tr><td>row7 </td></tr>
17
+ <tr><td>row8 </td></tr>
18
+ <tr><td>row9 </td></tr>
19
+ <tr><td>row10 </td></tr>
20
+ <tr><td>row11 </td></tr>
21
+ <tr><td>row12 </td></tr>
22
+ <tr><td>row13 </td></tr>
23
+ <tr><td>row14 </td></tr>
24
+ </table>
25
+ <div>
26
+ <b>bold1 </b>
27
+ <i>italic1 </i>
28
+ <b>bold2 </b>
29
+ <i>italic2 </i>
30
+ <p>para1 </p>
31
+ <b>bold3 </b>
32
+ </div>
33
+ <div>
34
+ <p>para2 </p>
35
+ <p>para3 </p>
36
+ </div>
37
+ <div>
38
+ <p>para4 </p>
39
+ </div>
40
+ <p class='empty'></p>
41
+ <p class='not-empty'><b></b></p>
42
+ </html>
43
+ EOF
44
+ @parser = Nokogiri.Hpricot doc
45
+ end
46
+
47
+
48
+ def test_even
49
+ assert_result_rows [2,4,6,8,10,12,14], @parser.search("table/tr:nth(even)")
50
+ end
51
+
52
+ def test_odd
53
+ assert_result_rows [1,3,5,7,9,11,13], @parser.search("table/tr:nth(odd)")
54
+ end
55
+
56
+ def test_2n
57
+ assert_equal @parser.search("table/tr:nth(even)").inner_text, @parser.search("table/tr:nth(2n)").inner_text
58
+ end
59
+
60
+ def test_2np1
61
+ assert_equal @parser.search("table/tr:nth(odd)").inner_text, @parser.search("table/tr:nth(2n+1)").inner_text
62
+ end
63
+
64
+ def test_4np3
65
+ assert_result_rows [3,7,11], @parser.search("table/tr:nth(4n+3)")
66
+ end
67
+
68
+ def test_3np4
69
+ assert_result_rows [4,7,10,13], @parser.search("table/tr:nth(3n+4)")
70
+ end
71
+
72
+ def test_mnp3
73
+ assert_result_rows [1,2,3], @parser.search("table/tr:nth(-n+3)")
74
+ end
75
+
76
+ def test_np3
77
+ assert_result_rows [3,4,5,6,7,8,9,10,11,12,13,14], @parser.search("table/tr:nth(n+3)")
78
+ end
79
+
80
+ def test_first
81
+ assert_result_rows [1], @parser.search("table/tr:first")
82
+ assert_result_rows [1], @parser.search("table/tr:first()")
83
+ end
84
+
85
+ def test_last
86
+ assert_result_rows [14], @parser.search("table/tr:last")
87
+ assert_result_rows [14], @parser.search("table/tr:last()")
88
+ end
89
+
90
+ def test_first_child
91
+ assert_result_rows [1], @parser.search("div/b:first-child"), "bold"
92
+ assert_result_rows [1], @parser.search("table/tr:first-child")
93
+ end
94
+
95
+ def test_last_child
96
+ assert_result_rows [3], @parser.search("div/b:last-child"), "bold"
97
+ assert_result_rows [14], @parser.search("table/tr:last-child")
98
+ end
99
+
100
+ def test_first_of_type
101
+ assert_result_rows [1], @parser.search("table/tr:first-of-type")
102
+ assert_result_rows [1], @parser.search("div/b:first-of-type"), "bold"
103
+ end
104
+
105
+ def test_last_of_type
106
+ assert_result_rows [14], @parser.search("table/tr:last-of-type")
107
+ assert_result_rows [3], @parser.search("div/b:last-of-type"), "bold"
108
+ end
109
+
110
+ def test_only_of_type
111
+ assert_result_rows [1,4], @parser.search("div/p:only-of-type"), "para"
112
+ end
113
+
114
+ def test_only_child
115
+ assert_result_rows [4], @parser.search("div/p:only-child"), "para"
116
+ end
117
+
118
+ def test_empty
119
+ result = @parser.search("p:empty")
120
+ assert_equal 1, result.size, "unexpected number of rows returned: '#{result.inner_text}'"
121
+ assert_equal 'empty', result.first['class']
122
+ end
123
+
124
+ def test_parent
125
+ result = @parser.search("p:parent")
126
+ assert_equal 5, result.size
127
+ 0.upto(3) do |j|
128
+ assert_equal "para#{j+1} ", result[j].inner_text
129
+ end
130
+ assert_equal "not-empty", result[4]['class']
131
+ end
132
+
133
+ def test_siblings
134
+ doc = <<-EOF
135
+ <html><body><div>
136
+ <p id="1">p1 </p>
137
+ <p id="2">p2 </p>
138
+ <p id="3">p3 </p>
139
+ <p id="4">p4 </p>
140
+ <p id="5">p5 </p>
141
+ EOF
142
+ parser = Nokogiri.Hpricot doc
143
+
144
+ assert_equal 2, parser.search("#3 ~ p").size
145
+ assert_equal "p4 p5 ", parser.search("#3 ~ p").inner_text
146
+ assert_equal 0, parser.search("#5 ~ p").size
147
+
148
+ assert_equal 1, parser.search("#3 + p").size
149
+ assert_equal "p4 ", parser.search("#3 + p").inner_text
150
+ assert_equal 0, parser.search("#5 + p").size
151
+ end
152
+
153
+ def assert_result_rows intarray, result, word="row"
154
+ assert_equal intarray.size, result.size, "unexpected number of rows returned: '#{result.inner_text}'"
155
+ assert_equal intarray.map{|j| "#{word}#{j}"}.join(' '), result.inner_text.strip, result.inner_text
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,237 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', "helper"))
2
+
3
+ module Nokogiri
4
+ module CSS
5
+ class TestParser < Nokogiri::TestCase
6
+ def setup
7
+ @parser = Nokogiri::CSS::Parser.new
8
+ end
9
+
10
+ def test_syntax_error_raised
11
+ assert_raises(CSS::SyntaxError) { @parser.parse("a[x=]") }
12
+ end
13
+
14
+ def test_find_by_type
15
+ ast = @parser.parse("a:nth-child(2)").first
16
+ matches = ast.find_by_type(
17
+ [:CONDITIONAL_SELECTOR,
18
+ [:ELEMENT_NAME],
19
+ [:PSEUDO_CLASS,
20
+ [:FUNCTION]
21
+ ]
22
+ ]
23
+ )
24
+ assert_equal(1, matches.length)
25
+ assert_equal(ast, matches.first)
26
+ end
27
+
28
+ def test_to_type
29
+ ast = @parser.parse("a:nth-child(2)").first
30
+ assert_equal(
31
+ [:CONDITIONAL_SELECTOR,
32
+ [:ELEMENT_NAME],
33
+ [:PSEUDO_CLASS,
34
+ [:FUNCTION]
35
+ ]
36
+ ], ast.to_type
37
+ )
38
+ end
39
+
40
+ def test_to_a
41
+ asts = @parser.parse("a:nth-child(2)")
42
+ assert_equal(
43
+ [:CONDITIONAL_SELECTOR,
44
+ [:ELEMENT_NAME, ["a"]],
45
+ [:PSEUDO_CLASS,
46
+ [:FUNCTION, ["nth-child("], ["2"]]
47
+ ]
48
+ ], asts.first.to_a
49
+ )
50
+ end
51
+
52
+ def test_function_with_arguments
53
+ assert_xpath "//*[position() = 2 and self::a]",
54
+ @parser.parse("a[2]")
55
+ assert_xpath "//*[position() = 2 and self::a]",
56
+ @parser.parse("a:nth-child(2)")
57
+ end
58
+
59
+ def test_carrot
60
+ assert_xpath "//a[starts-with(@id, 'Boing')]",
61
+ @parser.parse("a[id^='Boing']")
62
+ end
63
+
64
+ def test_attributes_with_at
65
+ ## This is non standard CSS
66
+ assert_xpath "//a[@id = 'Boing']",
67
+ @parser.parse("a[@id='Boing']")
68
+ end
69
+
70
+ def test_not_equal
71
+ ## This is non standard CSS
72
+ assert_xpath "//a[child::text() != 'Boing']",
73
+ @parser.parse("a[text()!='Boing']")
74
+ end
75
+
76
+ def test_function
77
+ ## This is non standard CSS
78
+ assert_xpath "//a[child::text()]",
79
+ @parser.parse("a[text()]")
80
+
81
+ ## This is non standard CSS
82
+ assert_xpath "//child::text()",
83
+ @parser.parse("text()")
84
+
85
+ ## This is non standard CSS
86
+ assert_xpath "//a[contains(child::text(), 'Boing')]",
87
+ @parser.parse("a[text()*='Boing']")
88
+
89
+ ## This is non standard CSS
90
+ assert_xpath "//script//comment()",
91
+ @parser.parse("script comment()")
92
+ end
93
+
94
+ def test_nonstandard_nth_selectors
95
+ ## These are non standard CSS
96
+ assert_xpath '//a[position() = 99]', @parser.parse('a:eq(99)')
97
+ assert_xpath '//a[position() = 1]', @parser.parse('a:first') # no parens
98
+ assert_xpath '//a[position() = last()]', @parser.parse('a:last') # no parens
99
+ assert_xpath '//a[position() = 99]', @parser.parse('a:nth(99)')
100
+ assert_xpath '//a[position() = 1]', @parser.parse('a:first()')
101
+ assert_xpath '//a[position() = last()]', @parser.parse('a:last()')
102
+ assert_xpath '//a[node()]', @parser.parse('a:parent')
103
+ end
104
+
105
+ def test_standard_nth_selectors
106
+ assert_xpath '//a[position() = 99]', @parser.parse('a:nth-of-type(99)')
107
+ assert_xpath '//a[position() = 1]', @parser.parse('a:first-of-type()')
108
+ assert_xpath '//a[position() = last()]', @parser.parse('a:last-of-type()')
109
+ assert_xpath '//a[position() = 1]', @parser.parse('a:first-of-type') # no parens
110
+ assert_xpath '//a[position() = last()]', @parser.parse('a:last-of-type') # no parens
111
+ assert_xpath '//a[position() = last() - 99]', @parser.parse('a:nth-last-of-type(99)')
112
+ assert_xpath '//a[position() = last() - 99]', @parser.parse('a:nth-last-of-type(99)')
113
+ end
114
+
115
+ def test_nth_child_selectors
116
+ assert_xpath '//*[position() = 1 and self::a]', @parser.parse('a:first-child')
117
+ assert_xpath '//*[position() = last() and self::a]', @parser.parse('a:last-child')
118
+ assert_xpath '//*[position() = 99 and self::a]', @parser.parse('a:nth-child(99)')
119
+ assert_xpath '//*[position() = last() - 99 and self::a]', @parser.parse('a:nth-last-child(99)')
120
+ end
121
+
122
+ def test_miscellaneous_selectors
123
+ assert_xpath '//*[last() = 1 and self::a]',
124
+ @parser.parse('a:only-child')
125
+ assert_xpath '//a[last() = 1]', @parser.parse('a:only-of-type')
126
+ assert_xpath '//a[not(node())]', @parser.parse('a:empty')
127
+ end
128
+
129
+ def test_nth_a_n_plus_b
130
+ assert_xpath '//a[(position() mod 2) = 0]', @parser.parse('a:nth-of-type(2n)')
131
+ assert_xpath '//a[(position() >= 1) and (((position()-1) mod 2) = 0)]', @parser.parse('a:nth-of-type(2n+1)')
132
+ assert_xpath '//a[(position() mod 2) = 0]', @parser.parse('a:nth-of-type(even)')
133
+ assert_xpath '//a[(position() >= 1) and (((position()-1) mod 2) = 0)]', @parser.parse('a:nth-of-type(odd)')
134
+ assert_xpath '//a[(position() >= 3) and (((position()-3) mod 4) = 0)]', @parser.parse('a:nth-of-type(4n+3)')
135
+ assert_xpath '//a[(position() <= 3) and (((position()-3) mod 1) = 0)]', @parser.parse('a:nth-of-type(-1n+3)')
136
+ assert_xpath '//a[(position() <= 3) and (((position()-3) mod 1) = 0)]', @parser.parse('a:nth-of-type(-n+3)')
137
+ assert_xpath '//a[(position() >= 3) and (((position()-3) mod 1) = 0)]', @parser.parse('a:nth-of-type(1n+3)')
138
+ assert_xpath '//a[(position() >= 3) and (((position()-3) mod 1) = 0)]', @parser.parse('a:nth-of-type(n+3)')
139
+ end
140
+
141
+ def test_preceding_selector
142
+ assert_xpath "//F[preceding-sibling::E]",
143
+ @parser.parse("E ~ F")
144
+ end
145
+
146
+ def test_direct_preceding_selector
147
+ assert_xpath "//E/following-sibling::*[1]/self::F",
148
+ @parser.parse("E + F")
149
+ end
150
+
151
+ def test_attribute
152
+ assert_xpath "//h1[@a = 'Tender Lovemaking']",
153
+ @parser.parse("h1[a='Tender Lovemaking']")
154
+ end
155
+
156
+ def test_id
157
+ assert_xpath "//*[@id = 'foo']", @parser.parse('#foo')
158
+ end
159
+
160
+ def test_pseudo_class_no_ident
161
+ assert_xpath "//*[link(.)]", @parser.parse(':link')
162
+ end
163
+
164
+ def test_pseudo_class
165
+ assert_xpath "//a[link(.)]", @parser.parse('a:link')
166
+ assert_xpath "//a[visited(.)]", @parser.parse('a:visited')
167
+ assert_xpath "//a[hover(.)]", @parser.parse('a:hover')
168
+ assert_xpath "//a[active(.)]", @parser.parse('a:active')
169
+ assert_xpath "//a[active(.) and contains(concat(' ', @class, ' '), ' foo ')]",
170
+ @parser.parse('a:active.foo')
171
+ end
172
+
173
+ def test_star
174
+ assert_xpath "//*", @parser.parse('*')
175
+ assert_xpath "//*[contains(concat(' ', @class, ' '), ' pastoral ')]",
176
+ @parser.parse('*.pastoral')
177
+ end
178
+
179
+ def test_class
180
+ assert_xpath "//*[contains(concat(' ', @class, ' '), ' a ') and contains(concat(' ', @class, ' '), ' b ')]",
181
+ @parser.parse('.a.b')
182
+ assert_xpath "//*[contains(concat(' ', @class, ' '), ' awesome ')]",
183
+ @parser.parse('.awesome')
184
+ assert_xpath "//foo[contains(concat(' ', @class, ' '), ' awesome ')]",
185
+ @parser.parse('foo.awesome')
186
+ assert_xpath "//foo//*[contains(concat(' ', @class, ' '), ' awesome ')]",
187
+ @parser.parse('foo .awesome')
188
+ end
189
+
190
+ def test_not_so_simple_not
191
+ assert_xpath "//*[@id = 'p' and not(contains(concat(' ', @class, ' '), ' a '))]",
192
+ @parser.parse('#p:not(.a)')
193
+ assert_xpath "//p[contains(concat(' ', @class, ' '), ' a ') and not(contains(concat(' ', @class, ' '), ' b '))]",
194
+ @parser.parse('p.a:not(.b)')
195
+ assert_xpath "//p[@a = 'foo' and not(contains(concat(' ', @class, ' '), ' b '))]",
196
+ @parser.parse("p[a='foo']:not(.b)")
197
+ end
198
+
199
+ def test_ident
200
+ assert_xpath '//x', @parser.parse('x')
201
+ end
202
+
203
+ def test_parse_space
204
+ assert_xpath '//x//y', @parser.parse('x y')
205
+ end
206
+
207
+ def test_parse_descendant
208
+ assert_xpath '//x/y', @parser.parse('x > y')
209
+ end
210
+
211
+ def test_parse_slash
212
+ ## This is non standard CSS
213
+ assert_xpath '//x/y', @parser.parse('x/y')
214
+ end
215
+
216
+ def test_parse_doubleslash
217
+ ## This is non standard CSS
218
+ assert_xpath '//x//y', @parser.parse('x//y')
219
+ end
220
+
221
+ def test_multi_path
222
+ assert_xpath ['//x/y', '//y/z'], @parser.parse('x > y, y > z')
223
+ assert_xpath ['//x/y', '//y/z'], @parser.parse('x > y,y > z')
224
+ ###
225
+ # TODO: should we make this work?
226
+ # assert_xpath ['//x/y', '//y/z'], @parser.parse('x > y | y > z')
227
+ end
228
+
229
+ def assert_xpath expecteds, asts
230
+ expecteds = [expecteds].flatten
231
+ expecteds.zip(asts).each do |expected, actual|
232
+ assert_equal expected, actual.to_xpath
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end