-
Notifications
You must be signed in to change notification settings - Fork 61
/
renderer.rb
124 lines (102 loc) · 2.68 KB
/
renderer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
module XPath
class Renderer
def self.render(node, type)
new(type).render(node)
end
def initialize(type)
@type = type
end
def render(node)
arguments = node.arguments.map { |argument| convert_argument(argument) }
send(node.expression, *arguments)
end
def convert_argument(argument)
case argument
when Expression, Union then render(argument)
when Array then argument.map { |element| convert_argument(element) }
when String then string_literal(argument)
when Literal then argument.value
else argument.to_s
end
end
def string_literal(string)
if string.include?("'")
string = string.split("'", -1).map do |substr|
"'#{substr}'"
end.join(%q{,"'",})
"concat(#{string})"
else
"'#{string}'"
end
end
def this_node
'.'
end
def descendant(current, element_names)
with_element_conditions("#{current}//", element_names)
end
def child(current, element_names)
with_element_conditions("#{current}/", element_names)
end
def axis(current, name, element_names)
with_element_conditions("#{current}/#{name}::", element_names)
end
def anywhere(element_names)
with_element_conditions("//", element_names)
end
def where(on, condition)
"#{on}[#{condition}]"
end
def attribute(current, name)
if valid_xml_name?(name)
"#{current}/@#{name}"
else
"#{current}/attribute::*[local-name(.) = #{string_literal(name)}]"
end
end
def binary_operator(name, left, right)
"(#{left} #{name} #{right})"
end
def is(one, two)
if @type == :exact
binary_operator("=", one, two)
else
function(:contains, one, two)
end
end
def variable(name)
"%{#{name}}"
end
def text(current)
"#{current}/text()"
end
def literal(node)
node
end
def css(current, selector)
paths = Nokogiri::CSS.xpath_for(selector).map do |xpath_selector|
"#{current}#{xpath_selector}"
end
union(paths)
end
def union(*expressions)
expressions.join(' | ')
end
def function(name, *arguments)
"#{name}(#{arguments.join(", ")})"
end
private
def with_element_conditions(expression, element_names)
if element_names.length == 1
"#{expression}#{element_names.first}"
elsif element_names.length > 1
"#{expression}*[#{element_names.map { |e| "self::#{e}" }.join(" | ")}]"
else
"#{expression}*"
end
end
def valid_xml_name?(name)
name =~ /^[a-zA-Z_:][a-zA-Z0-9_:\.\-]*$/
end
end
end