xpath 0.1.4 → 1.0.0.beta1
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.
- data/README.rdoc +61 -4
- data/lib/xpath.rb +6 -57
- data/lib/xpath/dsl.rb +104 -0
- data/lib/xpath/expression.rb +8 -298
- data/lib/xpath/html.rb +101 -62
- data/lib/xpath/literal.rb +8 -0
- data/lib/xpath/renderer.rb +146 -0
- data/lib/xpath/union.rb +10 -12
- data/lib/xpath/version.rb +1 -1
- data/spec/fixtures/form.html +49 -7
- data/spec/html_spec.rb +45 -47
- data/spec/union_spec.rb +14 -13
- data/spec/xpath_spec.rb +35 -88
- metadata +78 -69
data/README.rdoc
CHANGED
@@ -1,15 +1,21 @@
|
|
1
1
|
= XPath
|
2
2
|
|
3
|
-
XPath is a Ruby DSL around a subset of XPath 1.0.
|
3
|
+
XPath is a Ruby DSL around a subset of XPath 1.0. Its primary purpose is to
|
4
4
|
facilitate writing complex XPath queries from Ruby code.
|
5
5
|
|
6
|
+
{<img src="http://travis-ci.org/jnicklas/xpath.png" />}[http://travis-ci.org/jnicklas/xpath]
|
7
|
+
|
6
8
|
== Generating expressions
|
7
9
|
|
8
|
-
To create quick, one
|
10
|
+
To create quick, one-off expressions, +XPath.generate+ can be used:
|
9
11
|
|
10
12
|
XPath.generate { |x| x.descendant(:ul)[x.attr(:id) == 'foo'] }
|
11
13
|
|
12
|
-
|
14
|
+
You can also call expression methods directly on the XPath module:
|
15
|
+
|
16
|
+
XPath.descendant(:ul)[XPath.attr(:id) == 'foo'] }
|
17
|
+
|
18
|
+
However for more complex expressions, it is probably more convenient to include
|
13
19
|
the XPath module into your own class or module:
|
14
20
|
|
15
21
|
module MyXPaths
|
@@ -25,12 +31,63 @@ the XPath module into your own class or module:
|
|
25
31
|
end
|
26
32
|
|
27
33
|
Both ways return an XPath::Expression instance, which can be further modified.
|
28
|
-
To convert the expression to a string, just call #to_s on it.
|
34
|
+
To convert the expression to a string, just call #to_s on it. All available
|
35
|
+
expressions are defined in XPath::DSL.
|
36
|
+
|
37
|
+
== String, Hashes and Symbols
|
38
|
+
|
39
|
+
When you send a string as an argument to any XPath function, XPath assumes this
|
40
|
+
to be a string literal. On the other hand if you send in Symbol, XPath assumes
|
41
|
+
this to be an XPath literal. Thus the following two statements are not
|
42
|
+
equivalent:
|
43
|
+
|
44
|
+
XPath.descendant(:p)[XPath.attr(:id) == 'foo']
|
45
|
+
XPath.descendant(:p)[XPath.attr(:id) == :foo]
|
46
|
+
|
47
|
+
These are the XPath expressions that these would be translated to:
|
48
|
+
|
49
|
+
.//p[@id = 'foo']
|
50
|
+
.//p[@id = foo]
|
51
|
+
|
52
|
+
The second expression would match any p tag whose id attribute matches a 'foo'
|
53
|
+
tag it contains. Most likely this is not what you want.
|
54
|
+
|
55
|
+
In fact anything other than a String is treated as a literal. Thus the
|
56
|
+
following works as expected:
|
57
|
+
|
58
|
+
XPath.descendant(:p)[1]
|
59
|
+
|
60
|
+
Keep in mind that XPath is 1-indexed and not 0-indexed like most other
|
61
|
+
programming languages, including Ruby.
|
62
|
+
|
63
|
+
Hashes are automatically converted to equality expressions, so the above
|
64
|
+
example could be written as:
|
65
|
+
|
66
|
+
XPath.descendant(:p)[:@id => 'foo']
|
67
|
+
|
68
|
+
Which would generate the same expression:
|
69
|
+
|
70
|
+
.//p[@id = 'foo']
|
71
|
+
|
72
|
+
Note that the same rules apply here, both the keys and values in the hash are
|
73
|
+
treated the same way as any other expression in XPath. Thus the following are
|
74
|
+
not equivalent:
|
75
|
+
|
76
|
+
XPath.descendant(:p)[:@id => 'foo'] # => .//p[@id = 'foo']
|
77
|
+
XPath.descendant(:p)[:id => 'foo'] # => .//p[id = 'foo']
|
78
|
+
XPath.descendant(:p)['id' => 'foo'] # => .//p['id' = 'foo']
|
29
79
|
|
30
80
|
== HTML
|
31
81
|
|
32
82
|
XPath comes with a set of premade XPaths for use with HTML documents.
|
33
83
|
|
84
|
+
You can generate these like this:
|
85
|
+
|
86
|
+
XPath::HTML.link('Home')
|
87
|
+
XPath::HTML.field('Name')
|
88
|
+
|
89
|
+
See XPath::HTML for all available matchers.
|
90
|
+
|
34
91
|
== License
|
35
92
|
|
36
93
|
(The MIT License)
|
data/lib/xpath.rb
CHANGED
@@ -2,67 +2,16 @@ require 'nokogiri'
|
|
2
2
|
|
3
3
|
module XPath
|
4
4
|
autoload :Expression, 'xpath/expression'
|
5
|
+
autoload :Literal, 'xpath/literal'
|
5
6
|
autoload :Union, 'xpath/union'
|
7
|
+
autoload :Renderer, 'xpath/renderer'
|
6
8
|
autoload :HTML, 'xpath/html'
|
9
|
+
autoload :DSL, 'xpath/dsl'
|
7
10
|
|
8
|
-
extend
|
11
|
+
extend XPath::DSL::TopLevel
|
12
|
+
include XPath::DSL::TopLevel
|
9
13
|
|
10
14
|
def self.generate
|
11
|
-
yield(
|
12
|
-
end
|
13
|
-
|
14
|
-
def current
|
15
|
-
Expression::Self.new
|
16
|
-
end
|
17
|
-
|
18
|
-
def name
|
19
|
-
Expression::Name.new(current)
|
20
|
-
end
|
21
|
-
|
22
|
-
def descendant(*expressions)
|
23
|
-
Expression::Descendant.new(current, expressions)
|
24
|
-
end
|
25
|
-
|
26
|
-
def child(*expressions)
|
27
|
-
Expression::Child.new(current, expressions)
|
28
|
-
end
|
29
|
-
|
30
|
-
def anywhere(expression)
|
31
|
-
Expression::Anywhere.new(expression)
|
32
|
-
end
|
33
|
-
|
34
|
-
def attr(expression)
|
35
|
-
Expression::Attribute.new(current, expression)
|
36
|
-
end
|
37
|
-
|
38
|
-
def contains(expression)
|
39
|
-
Expression::Contains.new(current, expression)
|
40
|
-
end
|
41
|
-
|
42
|
-
def text
|
43
|
-
Expression::Text.new(current)
|
44
|
-
end
|
45
|
-
|
46
|
-
def var(name)
|
47
|
-
Expression::Variable.new(name)
|
48
|
-
end
|
49
|
-
|
50
|
-
def string
|
51
|
-
Expression::StringFunction.new(current)
|
52
|
-
end
|
53
|
-
|
54
|
-
def tag(name)
|
55
|
-
Expression::Tag.new(name)
|
56
|
-
end
|
57
|
-
|
58
|
-
def css(selector)
|
59
|
-
paths = Nokogiri::CSS.xpath_for(selector).map do |selector|
|
60
|
-
Expression::CSS.new(current, Expression::Literal.new(selector))
|
61
|
-
end
|
62
|
-
Union.new(*paths)
|
63
|
-
end
|
64
|
-
|
65
|
-
def varstring(name)
|
66
|
-
var(name).string_literal
|
15
|
+
yield(self)
|
67
16
|
end
|
68
17
|
end
|
data/lib/xpath/dsl.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
module XPath
|
2
|
+
module DSL
|
3
|
+
module TopLevel
|
4
|
+
def current
|
5
|
+
Expression.new(:this_node)
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
Expression.new(:node_name, current)
|
10
|
+
end
|
11
|
+
|
12
|
+
def descendant(*expressions)
|
13
|
+
Expression.new(:descendant, current, expressions)
|
14
|
+
end
|
15
|
+
|
16
|
+
def child(*expressions)
|
17
|
+
Expression.new(:child, current, expressions)
|
18
|
+
end
|
19
|
+
|
20
|
+
def axis(name, tag_name=:*)
|
21
|
+
Expression.new(:axis, current, name, tag_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def anywhere(expression)
|
25
|
+
Expression.new(:anywhere, expression)
|
26
|
+
end
|
27
|
+
|
28
|
+
def attr(expression)
|
29
|
+
Expression.new(:attribute, current, expression)
|
30
|
+
end
|
31
|
+
|
32
|
+
def contains(expression)
|
33
|
+
Expression.new(:contains, current, expression)
|
34
|
+
end
|
35
|
+
|
36
|
+
def starts_with(expression)
|
37
|
+
Expression.new(:starts_with, current, expression)
|
38
|
+
end
|
39
|
+
|
40
|
+
def text
|
41
|
+
Expression.new(:text, current)
|
42
|
+
end
|
43
|
+
|
44
|
+
def string
|
45
|
+
Expression.new(:string_function, current)
|
46
|
+
end
|
47
|
+
|
48
|
+
def css(selector)
|
49
|
+
Expression.new(:css, current, Literal.new(selector))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module ExpressionLevel
|
54
|
+
include XPath::DSL::TopLevel
|
55
|
+
|
56
|
+
def where(expression)
|
57
|
+
Expression.new(:where, current, expression)
|
58
|
+
end
|
59
|
+
alias_method :[], :where
|
60
|
+
|
61
|
+
def next_sibling(*expressions)
|
62
|
+
Expression.new(:next_sibling, current, expressions)
|
63
|
+
end
|
64
|
+
|
65
|
+
def one_of(*expressions)
|
66
|
+
Expression.new(:one_of, current, expressions)
|
67
|
+
end
|
68
|
+
|
69
|
+
def equals(expression)
|
70
|
+
Expression.new(:equality, current, expression)
|
71
|
+
end
|
72
|
+
alias_method :==, :equals
|
73
|
+
|
74
|
+
def or(expression)
|
75
|
+
Expression.new(:or, current, expression)
|
76
|
+
end
|
77
|
+
alias_method :|, :or
|
78
|
+
|
79
|
+
def and(expression)
|
80
|
+
Expression.new(:and, current, expression)
|
81
|
+
end
|
82
|
+
alias_method :&, :and
|
83
|
+
|
84
|
+
def union(*expressions)
|
85
|
+
Union.new(*[self, expressions].flatten)
|
86
|
+
end
|
87
|
+
alias_method :+, :union
|
88
|
+
|
89
|
+
def inverse
|
90
|
+
Expression.new(:inverse, current)
|
91
|
+
end
|
92
|
+
alias_method :~, :inverse
|
93
|
+
|
94
|
+
def string_literal
|
95
|
+
Expression.new(:string_literal, self)
|
96
|
+
end
|
97
|
+
|
98
|
+
def normalize
|
99
|
+
Expression.new(:normalized_space, current)
|
100
|
+
end
|
101
|
+
alias_method :n, :normalize
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/xpath/expression.rb
CHANGED
@@ -1,310 +1,20 @@
|
|
1
1
|
module XPath
|
2
2
|
class Expression
|
3
|
-
|
3
|
+
attr_accessor :expression, :arguments
|
4
|
+
include XPath::DSL::ExpressionLevel
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
class Unary < Expression
|
12
|
-
def initialize(expression)
|
13
|
-
@expression = wrap_xpath(expression)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class Binary < Expression
|
18
|
-
def initialize(left, right)
|
19
|
-
@left = wrap_xpath(left)
|
20
|
-
@right = wrap_xpath(right)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class Multiple < Expression
|
25
|
-
def initialize(left, expressions)
|
26
|
-
@left = wrap_xpath(left)
|
27
|
-
@expressions = expressions.map { |e| wrap_xpath(e) }
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class Literal < Expression
|
32
|
-
def initialize(expression)
|
33
|
-
@expression = expression
|
34
|
-
end
|
35
|
-
|
36
|
-
def to_xpath(predicate=nil)
|
37
|
-
@expression.to_s
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
class Child < Multiple
|
42
|
-
def to_xpath(predicate=nil)
|
43
|
-
if @expressions.length == 1
|
44
|
-
"#{@left.to_xpath(predicate)}/#{@expressions.first.to_xpath(predicate)}"
|
45
|
-
elsif @expressions.length > 1
|
46
|
-
"#{@left.to_xpath(predicate)}/*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
|
47
|
-
else
|
48
|
-
"#{@left.to_xpath(predicate)}/*"
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
class Descendant < Multiple
|
54
|
-
def to_xpath(predicate=nil)
|
55
|
-
if @expressions.length == 1
|
56
|
-
"#{@left.to_xpath(predicate)}//#{@expressions.first.to_xpath(predicate)}"
|
57
|
-
elsif @expressions.length > 1
|
58
|
-
"#{@left.to_xpath(predicate)}//*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
|
59
|
-
else
|
60
|
-
"#{@left.to_xpath(predicate)}//*"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class NextSibling < Multiple
|
66
|
-
def to_xpath(predicate=nil)
|
67
|
-
if @expressions.length == 1
|
68
|
-
"#{@left.to_xpath(predicate)}/following-sibling::*[1]/self::#{@expressions.first.to_xpath(predicate)}"
|
69
|
-
elsif @expressions.length > 1
|
70
|
-
"#{@left.to_xpath(predicate)}/following-sibling::*[1]/self::*[#{@expressions.map { |e| "self::#{e.to_xpath(predicate)}" }.join(" | ")}]"
|
71
|
-
else
|
72
|
-
"#{@left.to_xpath(predicate)}/following-sibling::*[1]/self::*"
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
class Tag < Unary
|
78
|
-
def to_xpath(predicate=nil)
|
79
|
-
"self::#{@expression.to_xpath(predicate)}"
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
class Anywhere < Unary
|
84
|
-
def to_xpath(predicate=nil)
|
85
|
-
"//#{@expression.to_xpath(predicate)}"
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
class Name < Unary
|
90
|
-
def to_xpath(predicate=nil)
|
91
|
-
"name(#{@expression.to_xpath(predicate)})"
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
class Where < Binary
|
96
|
-
def to_xpath(predicate=nil)
|
97
|
-
"#{@left.to_xpath(predicate)}[#{@right.to_xpath(predicate)}]"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
class Attribute < Binary
|
102
|
-
def to_xpath(predicate=nil)
|
103
|
-
if @right.is_a?(Literal)
|
104
|
-
"#{@left.to_xpath(predicate)}/@#{@right.to_xpath(predicate)}"
|
105
|
-
else
|
106
|
-
"#{@left.to_xpath(predicate)}/attribute::node()[name(.) = #{@right.to_xpath(predicate)}]"
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
class Equality < Binary
|
112
|
-
def to_xpath(predicate=nil)
|
113
|
-
"#{@left.to_xpath(predicate)} = #{@right.to_xpath(predicate)}"
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
class StringFunction < Unary
|
118
|
-
def to_xpath(predicate=nil)
|
119
|
-
"string(#{@expression.to_xpath(predicate)})"
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
class StringLiteral < Expression
|
124
|
-
def initialize(expression)
|
125
|
-
@expression = expression
|
126
|
-
end
|
127
|
-
|
128
|
-
def to_xpath(predicate=nil)
|
129
|
-
string = @expression
|
130
|
-
string = @expression.to_xpath(predicate) unless @expression.is_a?(String)
|
131
|
-
if string.include?("'")
|
132
|
-
string = string.split("'", -1).map do |substr|
|
133
|
-
"'#{substr}'"
|
134
|
-
end.join(%q{,"'",})
|
135
|
-
"concat(#{string})"
|
136
|
-
else
|
137
|
-
"'#{string}'"
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
class NormalizedSpace < Unary
|
143
|
-
def to_xpath(predicate=nil)
|
144
|
-
"normalize-space(#{@expression.to_xpath(predicate)})"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
class And < Binary
|
149
|
-
def to_xpath(predicate=nil)
|
150
|
-
"(#{@left.to_xpath(predicate)} and #{@right.to_xpath(predicate)})"
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
class Or < Binary
|
155
|
-
def to_xpath(predicate=nil)
|
156
|
-
"(#{@left.to_xpath(predicate)} or #{@right.to_xpath(predicate)})"
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
class OneOf < Expression
|
161
|
-
def initialize(left, right)
|
162
|
-
@left = wrap_xpath(left)
|
163
|
-
@right = right.map { |r| wrap_xpath(r) }
|
164
|
-
end
|
165
|
-
|
166
|
-
def to_xpath(predicate=nil)
|
167
|
-
@right.map { |r| "#{@left.to_xpath(predicate)} = #{r.to_xpath(predicate)}" }.join(' or ')
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
class Contains < Binary
|
172
|
-
def to_xpath(predicate=nil)
|
173
|
-
"contains(#{@left.to_xpath(predicate)}, #{@right.to_xpath(predicate)})"
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
class Is < Binary
|
178
|
-
def to_xpath(predicate=nil)
|
179
|
-
if predicate == :exact
|
180
|
-
Equality.new(@left, @right).to_xpath(predicate)
|
181
|
-
else
|
182
|
-
Contains.new(@left, @right).to_xpath(predicate)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
class Text < Unary
|
188
|
-
def to_xpath(predicate=nil)
|
189
|
-
"#{@expression.to_xpath(predicate)}/text()"
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
class Variable < Expression
|
194
|
-
def initialize(name)
|
195
|
-
@name = name
|
196
|
-
end
|
197
|
-
|
198
|
-
def to_xpath(predicate=nil)
|
199
|
-
"%{#{@name}}"
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
class Inverse < Unary
|
204
|
-
def to_xpath(predicate=nil)
|
205
|
-
"not(#{@expression.to_xpath(predicate)})"
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
class Applied < Expression
|
210
|
-
def initialize(expression, variables={})
|
211
|
-
@variables = variables
|
212
|
-
@expression = expression
|
213
|
-
end
|
214
|
-
|
215
|
-
def to_xpath(predicate=nil)
|
216
|
-
@expression.to_xpath(predicate) % @variables
|
217
|
-
rescue ArgumentError # for ruby < 1.9 compat
|
218
|
-
@expression.to_xpath(predicate).gsub(/%\{(\w+)\}/) do |_|
|
219
|
-
@variables[$1.to_sym] or raise(ArgumentError, "expected variable #{$1} to be set")
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
class CSS < Binary
|
225
|
-
def to_xpath(predicate=nil)
|
226
|
-
"#{@left.to_xpath}#{@right.to_xpath}"
|
227
|
-
end
|
6
|
+
def initialize(expression, *arguments)
|
7
|
+
@expression = expression
|
8
|
+
@arguments = arguments
|
228
9
|
end
|
229
10
|
|
230
11
|
def current
|
231
12
|
self
|
232
13
|
end
|
233
14
|
|
234
|
-
def
|
235
|
-
|
236
|
-
end
|
237
|
-
|
238
|
-
def where(expression)
|
239
|
-
Expression::Where.new(current, expression)
|
240
|
-
end
|
241
|
-
alias_method :[], :where
|
242
|
-
|
243
|
-
def one_of(*expressions)
|
244
|
-
Expression::OneOf.new(current, expressions)
|
245
|
-
end
|
246
|
-
|
247
|
-
def equals(expression)
|
248
|
-
Expression::Equality.new(current, expression)
|
249
|
-
end
|
250
|
-
alias_method :==, :equals
|
251
|
-
|
252
|
-
def is(expression)
|
253
|
-
Expression::Is.new(current, expression)
|
254
|
-
end
|
255
|
-
|
256
|
-
def or(expression)
|
257
|
-
Expression::Or.new(current, expression)
|
258
|
-
end
|
259
|
-
alias_method :|, :or
|
260
|
-
|
261
|
-
def and(expression)
|
262
|
-
Expression::And.new(current, expression)
|
263
|
-
end
|
264
|
-
alias_method :&, :and
|
265
|
-
|
266
|
-
def union(*expressions)
|
267
|
-
Union.new(*[self, expressions].flatten)
|
268
|
-
end
|
269
|
-
alias_method :+, :union
|
270
|
-
|
271
|
-
def inverse
|
272
|
-
Expression::Inverse.new(current)
|
273
|
-
end
|
274
|
-
alias_method :~, :inverse
|
275
|
-
|
276
|
-
def string_literal
|
277
|
-
Expression::StringLiteral.new(self)
|
278
|
-
end
|
279
|
-
|
280
|
-
def to_xpath(predicate=nil)
|
281
|
-
raise NotImplementedError, "please implement in subclass"
|
282
|
-
end
|
283
|
-
|
284
|
-
def to_s
|
285
|
-
to_xpaths.join(' | ')
|
286
|
-
end
|
287
|
-
|
288
|
-
def to_xpaths
|
289
|
-
[to_xpath(:exact), to_xpath(:fuzzy)].uniq
|
290
|
-
end
|
291
|
-
|
292
|
-
def apply(variables={})
|
293
|
-
Expression::Applied.new(current, variables)
|
294
|
-
end
|
295
|
-
|
296
|
-
def normalize
|
297
|
-
Expression::NormalizedSpace.new(current)
|
298
|
-
end
|
299
|
-
alias_method :n, :normalize
|
300
|
-
|
301
|
-
def wrap_xpath(expression)
|
302
|
-
case expression
|
303
|
-
when ::String then Expression::StringLiteral.new(expression)
|
304
|
-
when ::Symbol then Expression::Literal.new(expression)
|
305
|
-
else expression
|
306
|
-
end
|
15
|
+
def to_xpath
|
16
|
+
Renderer.render(self)
|
307
17
|
end
|
18
|
+
alias_method :to_s, :to_xpath
|
308
19
|
end
|
309
20
|
end
|
310
|
-
|