jam 0.2

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.
@@ -0,0 +1,2 @@
1
+ require 'jam/template'
2
+
@@ -0,0 +1 @@
1
+ # Cherry Adaptor
@@ -0,0 +1,104 @@
1
+ module Jam
2
+
3
+ def self.css_to_xpath(rule)
4
+ regElement = /^([#.]?)([a-z0-9\\*_-]*)((\|)([a-z0-9\\*_-]*))?/i
5
+ regAttr1 = /^\[([^\]]*)\]/i
6
+ regAttr2 = /^\[\s*([^~=\s]+)\s*(~?=)\s*"([^"]+)"\s*\]/i
7
+ regPseudo = /^:([a-z_-])+/i
8
+ regCombinator = /^(\s*[>+\s])?/i
9
+ regComma = /^\s*,/i
10
+
11
+ index = 1;
12
+ parts = ["//", "*"]
13
+ lastRule = nil
14
+
15
+ while rule.length && rule != lastRule
16
+ lastRule = rule
17
+
18
+ # Trim leading whitespace
19
+ rule = rule.gsub(/^\s*|\s*$/, "") #.replace
20
+ break if rule.length == 0
21
+
22
+ # Match the element identifier
23
+ m = regElement.match(rule)
24
+ if m
25
+ if !m[1]
26
+ # XXXjoe Namespace ignored for now
27
+ if m[5]
28
+ parts[index] = m[5]
29
+ else
30
+ parts[index] = m[2]
31
+ end
32
+ elsif (m[1] == '#')
33
+ parts.push("[@id='" + m[2] + "']")
34
+ elsif (m[1] == '.')
35
+ parts.push("[contains(@class, '" + m[2] + "')]")
36
+ end
37
+ rule = rule.substr(m[0].length)
38
+ end
39
+
40
+ # Match attribute selectors
41
+ m = regAttr2.match(rule)
42
+ if m
43
+ if m[2] == "~="
44
+ parts.push("[contains(@" + m[1] + ", '" + m[3] + "')]")
45
+ else
46
+ parts.push("[@" + m[1] + "='" + m[3] + "']")
47
+ end
48
+ rule = rule.substr(m[0].length)
49
+ else
50
+ m = regAttr1.match(rule)
51
+ if m
52
+ parts.push("[@" + m[1] + "]")
53
+ rule = rule.substr(m[0].length)
54
+ end
55
+ end
56
+
57
+ # Skip over pseudo-classes and pseudo-elements, which are of no use to us
58
+ m = regPseudo.match(rule)
59
+ while m
60
+ rule = rule.substr(m[0].length)
61
+ m = regPseudo.match(rule)
62
+ end
63
+
64
+ # Match combinators
65
+ m = regCombinator.match(rule)
66
+ if m && m[0].length
67
+ if m[0].index(">")
68
+ parts.push("/")
69
+ elsif m[0].index("+")
70
+ parts.push("/following-sibling::")
71
+ else
72
+ parts.push("//")
73
+ end
74
+ index = parts.length
75
+ parts.push("*")
76
+ rule = rule.substr(m[0].length)
77
+ end
78
+
79
+ m = regComma.match(rule)
80
+ if m
81
+ parts.push(" | ", "//", "*")
82
+ index = parts.length - 1
83
+ rule = rule.substr(m[0].length)
84
+ end
85
+ end
86
+
87
+ xpath = parts.join("")
88
+ return xpath
89
+ end
90
+
91
+ end #module Jam
92
+
93
+
94
+ class String
95
+
96
+ #
97
+ def substr(start, ending=-1)
98
+ str = self[start..ending]
99
+ self[start..ending] = ''
100
+ str
101
+ end
102
+
103
+ end
104
+
@@ -0,0 +1,132 @@
1
+
2
+ # css_to_xpath - generic CSS to XPath selector transformer
3
+ #
4
+ # * @author Andrea Giammarchi
5
+ # * @license Mit Style License
6
+ # * @blog http://webreflection.blogspot.com/
7
+ # * @project http://code.google.com/p/css2xpath/
8
+ # * @version 0.1 - Converts correctly every SlickSpeed CSS selector [http://mootools.net/slickspeed/]
9
+ # * @note stand alone vice-versa subproject [http://code.google.com/p/css2xpath/]
10
+ # * @info http://www.w3.org/TR/CSS2/selector.html
11
+ # * @credits some tips from Aristotle Pagaltzis [http://plasmasturm.org/log/444/]
12
+ #
13
+
14
+ module Jam
15
+
16
+ def self.css_to_xpath(css)
17
+ CSStoXPath.css_to_xpath(css)
18
+ end
19
+
20
+ module CSStoXPath
21
+ extend self
22
+
23
+ RULES = []
24
+
25
+ def rule(re_from, re_to=nil, &block)
26
+ RULES << [re_from, re_to || block]
27
+ end
28
+
29
+ # add @ for attribs
30
+ rule /\[([^\]~\$\*\^\|\!]+)(=[^\]]+)?\]/, '[@\1\2]'
31
+
32
+ # multiple queries
33
+ rule /\s*,\s*/, "|"
34
+
35
+ # , + ~ >
36
+ rule /\s*(\+|~|>)\s*/, '\1'
37
+
38
+ # * ~ + >
39
+ rule /([a-zA-Z0-9\_\-\*])~([a-zA-Z0-9\_\-\*])/ , '\1/following-sibling::\2'
40
+ rule /([a-zA-Z0-9\_\-\*])\+([a-zA-Z0-9\_\-\*])/, '\1/following-sibling::*[1]/self::\2'
41
+ rule /([a-zA-Z0-9\_\-\*])>([a-zA-Z0-9\_\-\*])/ , '\1/\2'
42
+
43
+ # all unescaped stuff escaped
44
+ rule /\[([^=]+)=([^'|"][^\]]*)\]/, '[\1=\'\2\']'
45
+
46
+ # all descendant or self to //
47
+ rule /(^|[^a-zA-Z0-9\_\-\*])(#|\.)([a-zA-Z0-9\_\-]+)/, '\1*\2\3'
48
+ rule /([\>\+\|\~\,\s])([a-zA-Z\*]+)/, '\1//\2'
49
+ rule /\s+\/\//, '//'
50
+
51
+ # :first-child
52
+ rule /([a-zA-Z0-9\_\-\*]+):first-child/, '*[1]/self::\1'
53
+
54
+ # :last-child
55
+ rule /([a-zA-Z0-9\_\-\*]+):last-child/, '\1[not(following-sibling::*)]'
56
+
57
+ # :only-child
58
+ rule /([a-zA-Z0-9\_\-\*]+):only-child/, '*[last()=1]/self::\1'
59
+
60
+ # :empty
61
+ rule /([a-zA-Z0-9\_\-\*]+):empty/, '\1[not(*) and not(normalize-space())]'
62
+
63
+ # :not
64
+ rule /([a-zA-Z0-9\_\-\*]+):not\(([^\)]*)\)/ do |s, a, b|
65
+ return a.concat("[not(", css_to_xpath(b).gsub(/^[^\[]+\[([^\]]*)\].*$/, '\1'), ")]")
66
+ end
67
+
68
+ # :nth-child
69
+ rule /([a-zA-Z0-9\_\-\*]+):nth-child\(([^\)]*)\)/ do |s, a, b|
70
+ case b
71
+ when "n"
72
+ return a
73
+ when "even"
74
+ return "*[position() mod 2=0 and position()>=0]/self::" + a
75
+ when "odd"
76
+ return a + "[(count(preceding-sibling::*) + 1) mod 2=1]"
77
+ else
78
+ b = (b || "0").gsub(/^([0-9]*)n.*?([0-9]*)$/, '\1+\2').split("+")
79
+ b[1] = b[1] || "0"
80
+ return "*[(position()-".concat(b[1], ") mod ", b[0], "=0 and position()>=", b[1], "]/self::", a)
81
+ end
82
+ end
83
+
84
+ # :contains(selectors)
85
+ rule /:contains\(([^\)]*)\)/ do |s, a|
86
+ # return "[contains(css:lower-case(string(.)),'" + a.toLowerCase() + "')]"; // it does not work in firefox 3*/
87
+ return "[contains(string(.),'" + a + "')]"
88
+ end
89
+
90
+ # |= attrib
91
+ rule /\[([a-zA-Z0-9\_\-]+)\|=([^\]]+)\]/, '[@\1=\2 or starts-with(@\1,concat(\2,\'-\'))]'
92
+
93
+ # *= attrib
94
+ rule /\[([a-zA-Z0-9\_\-]+)\*=([^\]]+)\]/, '[contains(@\1,\2)]'
95
+
96
+ # ~= attrib
97
+ rule /\[([a-zA-Z0-9\_\-]+)~=([^\]]+)\]/, '[contains(concat(' ',normalize-space(@\1),' '),concat(' ',\2,' '))]'
98
+
99
+ # ^= attrib
100
+ rule /\[([a-zA-Z0-9\_\-]+)\^=([^\]]+)\]/, '[starts-with(@\1,\2)]'
101
+
102
+ # $= attrib
103
+ rule /\[([a-zA-Z0-9\_\-]+)\$=([^\]]+)\]/ do |s, a, b|
104
+ return '[substring(@'.concat(a, ',string-length(@', a, ')-', b.length - 3, ')=', b, ']')
105
+ end
106
+
107
+ # != attrib
108
+ rule /\[([a-zA-Z0-9\_\-]+)\!=([^\]]+)\]/, '[not(@\1) or @\1!=\2]'
109
+
110
+ # ids and classes
111
+ rule /#([a-zA-Z0-9\_\-]+)/ , '[@id=\'\1\']'
112
+ rule /\.([a-zA-Z0-9\_\-]+)/, '[contains(concat(\' \',normalize-space(@class),\' \'),\' \1 \')]'
113
+
114
+ # normalize multiple filters
115
+ rule /\]\[([^\]]+)/, ' and (\1)'
116
+
117
+ def css_to_xpath(css)
118
+ RULES.each do |f, t|
119
+ case t
120
+ when Proc
121
+ css = css.gsub(f, &t)
122
+ else
123
+ css = css.gsub(f, t)
124
+ end
125
+ end
126
+ return ".//" + css;
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+
@@ -0,0 +1,169 @@
1
+ #require 'facets/hash/to_h'
2
+
3
+ module Jam
4
+
5
+ # = Jam Engine
6
+ #
7
+ # The Engine class serves as the base class for the various Jam adapters.
8
+ #
9
+ #--
10
+ # TODO: Can we normalize parser options across all backends. REXML seems a limiting factor.
11
+ #++
12
+
13
+ class Engine
14
+
15
+ # Interpolate data into document, returning the document object.
16
+ #
17
+ def interpolate(node, data)
18
+ interpolate_node(node, data)
19
+ end
20
+
21
+ private
22
+
23
+ # Converts plain keys to css selectors.
24
+ #
25
+ # TODO: Change to use special attribute instead of 'id'?
26
+ #
27
+ def prepare_data(data)
28
+ d = {}
29
+ data.each do |key, val|
30
+ case key.to_s
31
+ when /^<(.*?)>(.*)$/
32
+ d["#{$1}#{$2}"] = val
33
+ when /^\w/
34
+ d["##{key}"] = val
35
+ else
36
+ d[key.to_s] = val
37
+ end
38
+ end
39
+ return d
40
+ end
41
+
42
+ # Interpolate data.
43
+ #
44
+ def interpolate_node(node, data)
45
+ case data
46
+ when nil
47
+ remove(node)
48
+ when Array
49
+ interpolate_sequence(node, data)
50
+ when String, Numeric
51
+ interpolate_scalar(node, data)
52
+ when Hash
53
+ interpolate_mapping(node, data)
54
+ else
55
+ interpolate_object(node, data)
56
+ end
57
+ return node
58
+ end
59
+
60
+ # Interpolate mapping.
61
+ #
62
+ def interpolate_mapping(node, data)
63
+ data = prepare_data(data)
64
+
65
+ data.each do |id, val|
66
+ att = false
67
+ qry = id.to_s
68
+
69
+ result = qry.match(/^((.*?)\/)?([@](.*?))$/);
70
+ if result
71
+ att = result[4]
72
+ qry = result[2] #+ '[@' + att + ']'
73
+ else
74
+ att = false
75
+ end
76
+
77
+ if att
78
+ #qry = qry + '[@' + att + ']';
79
+ #search(node,qry).attr(att,val);
80
+ if qry
81
+ #if tag == false
82
+ # qry = '#' + qry
83
+ #end
84
+ nodeset = search(node, qry)
85
+ attribute(nodeset, att, val)
86
+ else
87
+ attribute(node, att, val)
88
+ end
89
+ else
90
+ nodeset = search(node, qry)
91
+ #if nodeset.size > 0
92
+ interpolate_node(nodeset, val)
93
+ #end
94
+ end
95
+ end
96
+ end
97
+
98
+ # Interpolate object.
99
+ #
100
+ def interpolate_object(node, data)
101
+ data = Hash[data.instance_variables.map{ |iv| [iv[1..-1], data.instance_variable_get(iv)] }]
102
+ interpolate_mapping(node, data)
103
+ end
104
+
105
+ # Interpolate attribute.
106
+ #
107
+ #def interpolate_attribute(nodes, data)
108
+ #
109
+ #end
110
+
111
+ # Interpolate array sequence.
112
+ #
113
+ def interpolate_sequence(nodes, data)
114
+ each_node(nodes) do |node|
115
+ parent = node.parent
116
+ data.each do |new_data|
117
+ new_node = interpolate_node(node.dup, new_data)
118
+ append(parent, new_node)
119
+ remove(node)
120
+ end
121
+ end
122
+ end
123
+
124
+ # Interpolate value.
125
+ #
126
+ # TODO This should have some special HTML features ?
127
+ #
128
+ # TODO Should we have two modes --one with and one without the extra HTML features?
129
+ #
130
+ def interpolate_scalar(nodes, data)
131
+ #var all_special = new Array;
132
+
133
+ # text inputs
134
+ #var special = this.find('input[@type=text]');
135
+ #special.val(data.toString());
136
+ #all_special.concat(special);
137
+ # textarea
138
+ # TODO
139
+
140
+ #this.not(special).empty();
141
+ #this.not(special).append(data.toString());
142
+ #alert(data);
143
+ #empty(node)
144
+ replace_content_with_text(nodes, data.to_s)
145
+ end
146
+
147
+ end #class Engine
148
+
149
+ end #module Jam
150
+
151
+
152
+
153
+ =begin
154
+ # Interpolate list.
155
+
156
+ function interpolate_list(node, data) {
157
+ temp = node.copy(true)
158
+ node.empty!
159
+ for (var d in data) {
160
+ nc = temp.copy(true);
161
+ interpolate_children( nc, d )
162
+ for (var c in nc.children) {
163
+ node.add(c);
164
+ };
165
+ };
166
+ };
167
+ =end
168
+
169
+
@@ -0,0 +1,137 @@
1
+ require 'hpricot'
2
+ require 'jam/engine'
3
+
4
+ module Hpricot
5
+
6
+ class Doc
7
+ def jam(data, opts={})
8
+ engine = ::Jam::Hpricot.new()
9
+ engine.interpolate(self, data)
10
+ end
11
+ end
12
+
13
+ class Elem
14
+ # This turns the Elem into an Elements object containing it.
15
+ def jam(data, opts={})
16
+ engine = ::Jam::Hpricot.new()
17
+ engine.interpolate(::Hpricot::Elements[self], data)
18
+ end
19
+ end
20
+
21
+ class Elements
22
+ def jam(data, opts={})
23
+ engine = ::Jam::Hpricot.new()
24
+ engine.interpolate(self, data)
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ module Jam
31
+
32
+ # Hpricot Adaptor
33
+ #
34
+ class Hpricot < Engine
35
+
36
+ #
37
+ def initialize(*options)
38
+ @options = options
39
+ end
40
+
41
+ #
42
+ def document(source)
43
+ ::Hpricot.parse(source) #, *@options)
44
+ end
45
+
46
+ #
47
+ def search(node, qry)
48
+ node.search(qry)
49
+ end
50
+
51
+ # deep copy # TODO: works?
52
+ def copy(node)
53
+ node.dup
54
+ end
55
+
56
+ #
57
+ def remove(node)
58
+ node.remove
59
+ end
60
+
61
+ #
62
+ def empty(ns)
63
+ ns.empty
64
+ end
65
+
66
+ #
67
+ def append(ns, child)
68
+ ns.append(child.to_s)
69
+ end
70
+
71
+ #
72
+ def replace(ns, child)
73
+ empty(ns)
74
+ append(ns, child)
75
+ end
76
+
77
+ #
78
+ def remove(node)
79
+ node.remove
80
+ end
81
+
82
+ #
83
+ def replace_content_with_text(node, text)
84
+ case node
85
+ when ::Hpricot::Elements
86
+ node.inner_html = text
87
+ when ::Array
88
+ node.each do |n|
89
+ n.inner_html = text
90
+ end
91
+ else
92
+
93
+ end
94
+ end
95
+
96
+ #
97
+ def attribute(ns, att, val)
98
+ ns.set(att, val)
99
+ end
100
+
101
+ # Remove jam nodes that ask for it, and all jam attributes.
102
+ #
103
+ def cleanup(node)
104
+ #node = node.root if ::Nokogiri::XML::Document === node
105
+ # remove unwanted tags
106
+ node.search("//*[@jam='erase']").each do |n|
107
+ unwrap(n)
108
+ end
109
+ # remove jam attributes
110
+
111
+ #
112
+ node
113
+ end
114
+
115
+ # Unwrap a node, such that the outer tag is
116
+ # removed, leaving only it's own children.
117
+ #
118
+ def unwrap(node)
119
+ node.each_child{ |n| node.parent.insert_before(n, node) }
120
+ ::Hpricot::Elements[node].remove
121
+ end
122
+
123
+ # Iterate over each node.
124
+ #
125
+ def each_node(nodes)
126
+ unless ::Hpricot::Elements === nodes
127
+ nodes = [nodes]
128
+ end
129
+ nodes.each do |node|
130
+ yield(node)
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+