cml 1.4.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.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/cml.gemspec +129 -0
- data/lib/cml/converters/jsawesome.rb +103 -0
- data/lib/cml/gold.rb +33 -0
- data/lib/cml/liquid_filters.rb +13 -0
- data/lib/cml/parser.rb +160 -0
- data/lib/cml/tag.rb +308 -0
- data/lib/cml/tags/checkbox.rb +77 -0
- data/lib/cml/tags/checkboxes.rb +38 -0
- data/lib/cml/tags/group.rb +25 -0
- data/lib/cml/tags/hidden.rb +17 -0
- data/lib/cml/tags/iterate.rb +86 -0
- data/lib/cml/tags/meta.rb +12 -0
- data/lib/cml/tags/multiple_text.rb +11 -0
- data/lib/cml/tags/option.rb +29 -0
- data/lib/cml/tags/radio.rb +48 -0
- data/lib/cml/tags/radios.rb +36 -0
- data/lib/cml/tags/ratings.rb +61 -0
- data/lib/cml/tags/search.rb +97 -0
- data/lib/cml/tags/select.rb +50 -0
- data/lib/cml/tags/text.rb +45 -0
- data/lib/cml/tags/textarea.rb +45 -0
- data/lib/cml/tags/thumb.rb +25 -0
- data/lib/cml/tags/unknown.rb +21 -0
- data/lib/cml.rb +15 -0
- data/spec/complex_spec.rb +127 -0
- data/spec/converters/jsawesome_spec.rb +75 -0
- data/spec/fixtures/complex.cml +23 -0
- data/spec/fixtures/html.cml +34 -0
- data/spec/fixtures/invalid.cml +10 -0
- data/spec/fixtures/script-style.cml +12 -0
- data/spec/fixtures/segfault.cml +1 -0
- data/spec/gold_spec.rb +29 -0
- data/spec/meta_spec.rb +47 -0
- data/spec/normalize_spec.rb +214 -0
- data/spec/show_data_spec.rb +308 -0
- data/spec/sorta_match.rb +41 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/tags/checkbox_spec.rb +155 -0
- data/spec/tags/checkboxes_spec.rb +171 -0
- data/spec/tags/group_spec.rb +108 -0
- data/spec/tags/hidden_spec.rb +17 -0
- data/spec/tags/iterate_spec.rb +259 -0
- data/spec/tags/meta_spec.rb +14 -0
- data/spec/tags/multiple_text_spec.rb +40 -0
- data/spec/tags/radios_spec.rb +81 -0
- data/spec/tags/ratings_spec.rb +79 -0
- data/spec/tags/search_spec.rb +132 -0
- data/spec/tags/select_spec.rb +76 -0
- data/spec/tags/tag_spec.rb +93 -0
- data/spec/tags/text_spec.rb +66 -0
- data/spec/tags/textarea_spec.rb +58 -0
- data/spec/tags/thumb_spec.rb +20 -0
- data/spec/tags/unknown_spec.rb +19 -0
- data/spec/validation_spec.rb +62 -0
- metadata +182 -0
data/lib/cml/tag.rb
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module CML
|
4
|
+
class Tag
|
5
|
+
attr_reader :attrs, :tag, :data, :cml, :opts
|
6
|
+
#liquid_methods :raw_label
|
7
|
+
|
8
|
+
def initialize(cml, opts = {})
|
9
|
+
@cml = cml.is_a?(String) ? Parser.parse(cml).at("/root/*") : cml
|
10
|
+
@attrs = @cml.attributes#Hash[*cml.attributes.map {|k,v| [k.intern,v]}.flatten]
|
11
|
+
@tag = @cml.name
|
12
|
+
@opts = opts
|
13
|
+
end
|
14
|
+
|
15
|
+
#This is convoluted as fuck
|
16
|
+
def prefix(name)
|
17
|
+
if @opts[:iteration]
|
18
|
+
m = case @tag
|
19
|
+
when "checkbox": "[{{i}}][]"
|
20
|
+
when "radio": "[{{i}}]"
|
21
|
+
when "rating": @opts[:normalize] ? "[{{i}}][]" : "[{{i}}]"
|
22
|
+
when "text", "textarea": @opts[:normalize] ? "[{{i}}][]" : "[]"
|
23
|
+
else
|
24
|
+
"[]"
|
25
|
+
end
|
26
|
+
elsif multi_type.to_s =~ /list$/
|
27
|
+
m = "[]"
|
28
|
+
else
|
29
|
+
m = @opts[:normalize] ? "[]" : ""
|
30
|
+
end
|
31
|
+
@opts[:prefix] ? "#{@opts[:prefix]}[#{name}]#{m}" : name
|
32
|
+
end
|
33
|
+
|
34
|
+
def name(auto_gold = true, suffix="")
|
35
|
+
name = (@attrs["name"] || @attrs["label"]).to_s.downcase.gsub(/\s/,"_").gsub(/\W/,"")
|
36
|
+
name += ("_" + suffix) unless suffix.size == 0
|
37
|
+
if auto_gold && @opts[:normalize]
|
38
|
+
if g = @opts[:parser].golds[name]
|
39
|
+
name = g
|
40
|
+
else
|
41
|
+
#This kinda sucks...
|
42
|
+
name = "#{name}_gold" unless name =~ /_gold$/ || name.empty?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
#This really sucks
|
46
|
+
name = name.sub(/_gold$/,"") unless auto_gold
|
47
|
+
#this underscoring multi_type bullshit needs to go way
|
48
|
+
name + (multi_type.to_s =~ /column/ && !(name =~ /_1$/) ? "_1" : "")
|
49
|
+
end
|
50
|
+
|
51
|
+
#:column means a new column in the result csv, does this by adding _1 to the name
|
52
|
+
#:list means an array of values in the result csv
|
53
|
+
#:columnlist means a new column that is a list
|
54
|
+
def multi_type
|
55
|
+
grouped = @cml.xpath("ancestor::cml:*[@multiple]")
|
56
|
+
if @attrs["multiple"] || !grouped.empty?
|
57
|
+
if @cml.at(".//cml:checkboxes|.//cml:radios|.//cml:ratings")
|
58
|
+
:column
|
59
|
+
elsif !grouped.empty?
|
60
|
+
if grouped.length > 1
|
61
|
+
:columnlist
|
62
|
+
elsif grouped.detect {|g| g.at(".//cml:checkboxes|.//cml:radios|.//cml:ratings") }
|
63
|
+
@attrs["multiple"] ? :columnlist : :column
|
64
|
+
else
|
65
|
+
:list
|
66
|
+
end
|
67
|
+
else
|
68
|
+
:list
|
69
|
+
end
|
70
|
+
else
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def gold?
|
76
|
+
@attrs["gold"] ? @cml : @cml.at("./cml:gold")
|
77
|
+
end
|
78
|
+
|
79
|
+
def iterating?
|
80
|
+
@cml.at_xpath("ancestor::cml:iterate")
|
81
|
+
end
|
82
|
+
|
83
|
+
def gold=(opts = {})
|
84
|
+
cur = gold?
|
85
|
+
advanced = ["strict", "regex"].any? {|k| opts && opts[k] }
|
86
|
+
if !cur
|
87
|
+
cur = advanced ? @cml.add_child(Parser.parse("<cml:gold/>").at("/root/*")) : @cml
|
88
|
+
end
|
89
|
+
|
90
|
+
if cur.name == "gold"
|
91
|
+
if !opts
|
92
|
+
cur.remove
|
93
|
+
elsif advanced
|
94
|
+
opts.each do |k,v|
|
95
|
+
cur[k] = v.to_s if v
|
96
|
+
end
|
97
|
+
else
|
98
|
+
cur.remove
|
99
|
+
cur = @cml
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
if cur.name != "gold"
|
104
|
+
if !opts
|
105
|
+
@cml.remove_attribute("gold")
|
106
|
+
elsif opts["src"] && opts["src"] != "#{name}_gold"
|
107
|
+
@cml["gold"] = opts["src"]
|
108
|
+
else
|
109
|
+
@cml["gold"] = "true"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def wrapper_classes
|
115
|
+
extras = []
|
116
|
+
extras << "logic-only-if:#{@attrs["only-if"]}" if @attrs["only-if"]
|
117
|
+
extras << "matcher:#{@attrs["matcher"]}" if @attrs["matcher"]
|
118
|
+
extras += ["multiple",multi_type] if @attrs["multiple"]
|
119
|
+
extras << "cml_field" unless ["group","iterate"].include?(@tag)
|
120
|
+
extras << @attrs["class"].to_s if @attrs["class"]
|
121
|
+
"#{@tag.gsub(/_/," ")} #{extras.join(" ")}".strip
|
122
|
+
end
|
123
|
+
|
124
|
+
def children
|
125
|
+
false
|
126
|
+
end
|
127
|
+
|
128
|
+
def classes
|
129
|
+
@classes ||= [name] << validations << @attrs["class"].to_s.split(/ /)
|
130
|
+
@classes << "cml_data_preloaded" if @opts[:show_data] && @opts[:data][name]
|
131
|
+
@classes.uniq.reject {|c| c.length == 0 }.join(" ").strip
|
132
|
+
end
|
133
|
+
|
134
|
+
def convert(opts = nil)
|
135
|
+
@opts = @opts.merge(opts) if opts
|
136
|
+
html = to_html
|
137
|
+
|
138
|
+
html.strip.length == 0 ? Nokogiri::XML::Text.new(html, @cml.document) : Nokogiri::HTML.fragment(html)
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_html
|
142
|
+
template = @opts[:bare] ? self.class::BareTemplate : self.class::Template
|
143
|
+
#Add a hidden element if this is a checkbox inside an iteration
|
144
|
+
#if @tag == "checkbox" && @opts[:iteration]
|
145
|
+
# hidden = CML::Tags::Hidden.new("<cml:hidden name=\"#{name}\" value=\"\"/>", @opts).to_html
|
146
|
+
#else
|
147
|
+
# hidden = ""
|
148
|
+
#end
|
149
|
+
wrapper(Liquid::Template.parse(template).render(data, [LiquidFilters]))
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_s
|
153
|
+
@cml.to_s
|
154
|
+
end
|
155
|
+
|
156
|
+
def raw_label
|
157
|
+
label = @attrs["label"] ? @attrs["label"].to_s : nil
|
158
|
+
label = cml.inner_text if label.nil? && @tag == "option"
|
159
|
+
label ||= (@attrs["name"] || "").to_s.capitalize
|
160
|
+
end
|
161
|
+
|
162
|
+
def label(tag = "label")
|
163
|
+
required = " <span class=\"required\">(required)</span>" if validations.include?("required")
|
164
|
+
raw_label == "" ? "" : "<#{tag} class=\"legend\">#{raw_label}#{required}</#{tag.split(" ")[0]}>"
|
165
|
+
end
|
166
|
+
|
167
|
+
def legend
|
168
|
+
label("h2")
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_liquid
|
172
|
+
LiquidHash[
|
173
|
+
"to_s" => to_html,
|
174
|
+
"raw_label" => raw_label.to_s
|
175
|
+
]
|
176
|
+
end
|
177
|
+
|
178
|
+
def gold_reason
|
179
|
+
return "" unless @opts[:normalize] && !name.empty? && @opts[:no_reason].nil? && !["iterate", "group"].include?(@tag)
|
180
|
+
reason_name = @opts[:iteration] ? prefix(name+"_reason").sub(/\[\{\{i\}\}\]/,"") : prefix(name+"_reason").sub(/\[\]/,"")
|
181
|
+
<<-HTML
|
182
|
+
<div class="cml_gold_reason">
|
183
|
+
<label class="legend">Reason</label>
|
184
|
+
<textarea name="#{reason_name}">#{Array((@opts[:data] || {})[name+"_reason"])[@opts[:iteration].to_i]}</textarea>
|
185
|
+
</div>
|
186
|
+
HTML
|
187
|
+
end
|
188
|
+
|
189
|
+
def validators
|
190
|
+
@validators ||= @attrs["validates"].to_s.split(/ /)
|
191
|
+
end
|
192
|
+
|
193
|
+
def validations
|
194
|
+
@validations ||= validators.map {|v| "validates-#{v}"}
|
195
|
+
end
|
196
|
+
|
197
|
+
def instructions
|
198
|
+
@instructions ||= @attrs["instructions"] || (@cml.at("./cml:instructions") ? @cml.at("./cml:instructions").inner_html : nil)
|
199
|
+
@instructions ? "<p class=\"instructions\">#{@instructions}</p>\n#{gold_reason}" : gold_reason
|
200
|
+
end
|
201
|
+
|
202
|
+
def validate?
|
203
|
+
true
|
204
|
+
end
|
205
|
+
|
206
|
+
def cacheable?
|
207
|
+
!(@opts[:normalize] || @opts[:show_data] || @opts[:no_memoize] || @opts[:missed])
|
208
|
+
end
|
209
|
+
|
210
|
+
#Check if the provided value contains a liquid variable
|
211
|
+
#If have a variable, data, and are not cachable return the resolved data
|
212
|
+
def ensure_resolved(value)
|
213
|
+
variable = value[/\{\{\s*([\w-]+)/, 1]
|
214
|
+
res = variable && @opts[:data] && !cacheable? ? @opts[:data][variable] : value
|
215
|
+
#Don't resolve arrays as this would suck
|
216
|
+
res.is_a?(Array) ? value : res
|
217
|
+
end
|
218
|
+
|
219
|
+
def wrapper(parsed, tag = "div")
|
220
|
+
#Deal with a label only wrapper... kinda jank
|
221
|
+
if tag == "label" && instructions
|
222
|
+
tag = "div"
|
223
|
+
parsed = "<label>#{parsed}</label>"
|
224
|
+
end
|
225
|
+
#Inserting instructions here makes it a non-variable input maybe...
|
226
|
+
style = @attrs["style"] ? "style=\"#{@attrs["style"]}\"" : ""
|
227
|
+
|
228
|
+
data_attrs = data_attributes_string || ""
|
229
|
+
|
230
|
+
"<#{tag} class=\"#{wrapper_classes}\" #{style} #{data_attrs}>#{parsed}#{instructions}</#{tag}>"
|
231
|
+
end
|
232
|
+
|
233
|
+
def value
|
234
|
+
ensure_resolved(@attrs["value"].to_s)
|
235
|
+
end
|
236
|
+
|
237
|
+
def preloaded_data
|
238
|
+
@opts[:show_data] && @opts[:data][name]
|
239
|
+
end
|
240
|
+
|
241
|
+
def gold_class
|
242
|
+
if @opts[:missed]
|
243
|
+
if @opts[:missed][name]
|
244
|
+
bad = Array(@opts[:missed][name][0])
|
245
|
+
good = Array(@opts[:missed][name][1])
|
246
|
+
if bad.include?(value)
|
247
|
+
return "gold_bad"
|
248
|
+
elsif good.include?(value)
|
249
|
+
return "gold_good"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
""
|
254
|
+
end
|
255
|
+
|
256
|
+
def data
|
257
|
+
(@opts[:data] || {}).merge({
|
258
|
+
"name" => prefix(name),
|
259
|
+
"label" => label,
|
260
|
+
"legend" => legend,
|
261
|
+
"classes" => classes,
|
262
|
+
"value" => value,
|
263
|
+
"gold_class" => gold_class
|
264
|
+
})
|
265
|
+
end
|
266
|
+
|
267
|
+
protected
|
268
|
+
|
269
|
+
def data_attributes
|
270
|
+
data_attrs = {}
|
271
|
+
@attrs.each do |key, value|
|
272
|
+
if key =~ /^data-/
|
273
|
+
data_attrs[key] = value
|
274
|
+
end
|
275
|
+
end
|
276
|
+
data_attrs.empty? ? nil : data_attrs
|
277
|
+
end
|
278
|
+
|
279
|
+
def data_attributes_string
|
280
|
+
return "" unless data_attrs = data_attributes
|
281
|
+
attributes = []
|
282
|
+
data_attrs.each do |key, attribute|
|
283
|
+
name = attribute.name
|
284
|
+
value = attribute.value
|
285
|
+
|
286
|
+
attributes << "#{name}=\"#{CGI.escapeHTML( value )}\""
|
287
|
+
end
|
288
|
+
|
289
|
+
attributes.join(" ")
|
290
|
+
end
|
291
|
+
|
292
|
+
#memoize hotness
|
293
|
+
def self.memoize(*methods)
|
294
|
+
methods.each do |m|
|
295
|
+
class_eval %Q{
|
296
|
+
alias :original_#{m} :#{m}
|
297
|
+
|
298
|
+
def #{m}(*args)
|
299
|
+
@#{m}_cache ||= {}
|
300
|
+
key = {:prefix => @opts[:prefix], :missed => @opts[:missed]}.merge(:_args => args)
|
301
|
+
return @#{m}_cache[key] if @#{m}_cache[key]
|
302
|
+
@#{m}_cache[key] = send(:original_#{m}, *args)
|
303
|
+
end
|
304
|
+
}
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Checkbox < Tag
|
4
|
+
BareTemplate = <<-HTML.freeze
|
5
|
+
{{default}}
|
6
|
+
<input name="{{name}}" type="checkbox" value="{{value}}" class="{{classes}}" {{checked}}/>
|
7
|
+
HTML
|
8
|
+
|
9
|
+
Template = <<-HTML.freeze
|
10
|
+
{{default_normalized}}<div class="cml_row"><label class="{{gold_class}}">#{BareTemplate} {{label}}</label></div>
|
11
|
+
HTML
|
12
|
+
|
13
|
+
def prefix(name)
|
14
|
+
#Don't add another bracket if it's already a list...
|
15
|
+
name = super(name)
|
16
|
+
name + (name !~ /\[\]$/ && grouped? ? "[]" : "")
|
17
|
+
end
|
18
|
+
|
19
|
+
def grouped?
|
20
|
+
@opts[:bare] || (@cml.parent && @cml.parent.name == "checkboxes")
|
21
|
+
end
|
22
|
+
|
23
|
+
def wrapper(parsed)
|
24
|
+
grouped? || @opts[:no_reason] ? parsed : super
|
25
|
+
end
|
26
|
+
|
27
|
+
def value
|
28
|
+
value = (@attrs["value"] || (grouped? ? @attrs["label"] : "true")).to_s
|
29
|
+
ensure_resolved(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def classes
|
33
|
+
super+(@checked ? " cml_gold_loaded" : "")
|
34
|
+
end
|
35
|
+
|
36
|
+
def checked
|
37
|
+
manipulated = !@opts[:missed].nil?
|
38
|
+
if @opts[:data] && @opts[:normalize]
|
39
|
+
manipulated = true
|
40
|
+
seeds = Array(@opts[:data][@opts[:parser].golds[name(false)]] || @opts[:data][name(false)]).map {|s| s.is_a?(Array) ? s.map {|t| t.to_s.downcase } : s.to_s.downcase }
|
41
|
+
seeds = @opts[:iteration] ? [seeds[@opts[:iteration]]].flatten : seeds
|
42
|
+
if seeds.compact.empty?
|
43
|
+
manipulated = false
|
44
|
+
elsif seeds.include?(value.to_s.downcase)
|
45
|
+
@checked = true
|
46
|
+
return "checked=\"checked\""
|
47
|
+
end
|
48
|
+
elsif preload = preloaded_data
|
49
|
+
manipulated = true
|
50
|
+
if preload.map{|x| x.to_s.downcase }.include?(value.to_s.downcase)
|
51
|
+
return "checked=\"checked\""
|
52
|
+
end
|
53
|
+
end
|
54
|
+
!manipulated && @attrs["checked"] ? "checked=\"checked\"" : ""
|
55
|
+
end
|
56
|
+
|
57
|
+
def data
|
58
|
+
default = ""
|
59
|
+
default_normalized = ""
|
60
|
+
if @attrs["default"]
|
61
|
+
if @opts[:normalize]
|
62
|
+
default_normalized = Checkbox.new("<cml:checkbox label=\"Default value: #{@attrs["default"]}\" name=\"#{name(false)}\" value=\"#{@attrs["default"]}\"/>", @opts.merge(:no_reason => true, :normalize => true)).to_html
|
63
|
+
else
|
64
|
+
default = Hidden.new("<cml:hidden name=\"#{name(false)}\" value=\"#{@attrs["default"]}\"/>", @opts.merge(:grouped => grouped?, :no_field_wrap => true)).to_html
|
65
|
+
end
|
66
|
+
end
|
67
|
+
#be sure to set the instance variable ahead of time
|
68
|
+
c = checked
|
69
|
+
super.merge({
|
70
|
+
"default" => default,
|
71
|
+
"default_normalized" => default_normalized,
|
72
|
+
"checked" => c,
|
73
|
+
"label" => @attrs["label"].to_s})
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Checkboxes < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{legend}}
|
6
|
+
{{content}}
|
7
|
+
HTML
|
8
|
+
|
9
|
+
def data
|
10
|
+
#Don't memoize individual checkboxes if we are normalizing
|
11
|
+
super.merge({"content" => content(@opts[:normalize] ? @opts[:iteration] : nil) })
|
12
|
+
end
|
13
|
+
|
14
|
+
def content(*args)
|
15
|
+
cloned= @cml.dup
|
16
|
+
c = children
|
17
|
+
cloned.xpath(".//cml:*[not(self::cml:checkbox)]").remove
|
18
|
+
#re-merge opts to get iterations...
|
19
|
+
cloned.xpath(".//cml:checkbox").each_with_index do |r,i|
|
20
|
+
r.namespace = nil
|
21
|
+
r.replace(c[i].convert(@opts))
|
22
|
+
end
|
23
|
+
#Removal of the root tag
|
24
|
+
html = cloned.to_xhtml.gsub(/<\/?cml:checkboxes[^>]*?>|<\/?>/,'')
|
25
|
+
end
|
26
|
+
memoize :content
|
27
|
+
|
28
|
+
def children
|
29
|
+
@cml.xpath(".//cml:checkbox").map do |c|
|
30
|
+
c["name"] ||= name(false)
|
31
|
+
c["validates"] ||= @attrs["validates"].to_s
|
32
|
+
Checkbox.new(c, @opts)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
memoize :children
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Group < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{label}}
|
6
|
+
{{content}}
|
7
|
+
HTML
|
8
|
+
|
9
|
+
def validate?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def children
|
14
|
+
Parser.new(@cml.children.to_xml, @opts.merge(:no_wrap => true)).tags
|
15
|
+
end
|
16
|
+
memoize :children
|
17
|
+
|
18
|
+
def data
|
19
|
+
#this is dangerous because of seg faults...
|
20
|
+
super.merge({"content" => Parser.new(@cml, @opts.merge(:no_wrap => true)).to_html})
|
21
|
+
end
|
22
|
+
memoize :data
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Hidden < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
<input name="{{name}}" type="hidden" value="{{value}}" class="{{classes}}"/>
|
6
|
+
HTML
|
7
|
+
|
8
|
+
def prefix(name)
|
9
|
+
super(name) + (@opts[:grouped] ? "[]" : "")
|
10
|
+
end
|
11
|
+
|
12
|
+
def wrapper(parsed)
|
13
|
+
@opts[:no_field_wrap] ? parsed : super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Iterate < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{ content }}
|
6
|
+
HTML
|
7
|
+
|
8
|
+
def validate?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def children
|
13
|
+
#Only take the first children because that's how she likes it
|
14
|
+
parser.tags
|
15
|
+
end
|
16
|
+
|
17
|
+
def wrapper_classes
|
18
|
+
(super + " " + @attrs["class"].to_s).strip
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
#this is dangerous because of seg faults...
|
23
|
+
super.merge({
|
24
|
+
"content" => content
|
25
|
+
})
|
26
|
+
end
|
27
|
+
|
28
|
+
def columns
|
29
|
+
@opts[:data].select {|k,v| v.is_a?(Array)}.map {|a| a[0]} rescue []
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
#This sure is ugly
|
35
|
+
def content
|
36
|
+
reproduce.map!.with_index do |html,i|
|
37
|
+
h = "<div class=\"cml_nugget\">#{html.dup}</div>"
|
38
|
+
columns.each {|c| h.gsub!(/\{\{\s*#{c}\s*[^\[]/, "{{ #{c}[#{i}] }") }
|
39
|
+
h.gsub!(/\{\{i\}\}/, i.to_s)
|
40
|
+
#Nested gold hotness
|
41
|
+
if key = @opts[:parser].golds.keys.detect {|k| k =~ /structured$/ }
|
42
|
+
reason_key = key[1..-12]+"_reason"
|
43
|
+
end
|
44
|
+
reasons = @opts[:data][reason_key]
|
45
|
+
missed = @opts[:missed] && @opts[:missed]["structured"] && @opts[:missed]["structured"][i] && !@opts[:missed]["structured"][i].empty?
|
46
|
+
|
47
|
+
if missed && reasons.is_a?(Array) && reasons.length > 1 && reasons[i].to_s.strip.length > 1
|
48
|
+
h + "<div class=\"reason\"><h4>The reason this question was marked wrong is:</h4><p>#{reasons[i]}</p></div>"
|
49
|
+
else
|
50
|
+
#Don't show the nugget if they didn't miss it
|
51
|
+
@opts[:normalize].nil? && @opts[:missed] && !missed ? "" : h
|
52
|
+
end
|
53
|
+
end.join("\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def parser
|
57
|
+
return @parser if @parser
|
58
|
+
@parser = Parser.new(@cml.children.to_xml, @opts.merge(
|
59
|
+
:no_wrap => true,
|
60
|
+
:multi_type => multi_type,
|
61
|
+
:missed => @opts[:missed] && @opts[:missed]["structured"] ? @opts[:missed]["structured"][0] : nil,
|
62
|
+
:iteration => 0)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def on
|
67
|
+
@attrs["on"].to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
def reproduce
|
71
|
+
children = []
|
72
|
+
iterations = @opts[:data][@attrs["on"].to_s].length rescue 1
|
73
|
+
iterations.times do |i|
|
74
|
+
@cache ||= parser.to_html(
|
75
|
+
:missed => @opts[:missed] && @opts[:missed]["structured"] ? @opts[:missed]["structured"][i] : nil,
|
76
|
+
:iteration => i
|
77
|
+
)
|
78
|
+
children << @cache
|
79
|
+
#kill the cache if we have to display missed messaging or we're digging gold
|
80
|
+
@cache = nil if @opts[:normalize] || (@opts[:missed] && @opts[:missed]["structured"])
|
81
|
+
end
|
82
|
+
children.empty? ? [Parser.new("<cml:hidden name=\"#{@attrs["on"]}\" value=\"Nothing to iterate\"/>", @opts.merge(:no_wrap => true)).to_html] : children
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Option < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
<option {{value}} {{selected}}>{{label}}</option>
|
6
|
+
HTML
|
7
|
+
|
8
|
+
def wrapper(parsed)
|
9
|
+
parsed
|
10
|
+
end
|
11
|
+
|
12
|
+
def value
|
13
|
+
(@attrs["value"] || @attrs["label"]).to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def selected=(selected)
|
17
|
+
@attrs["selected"] = selected
|
18
|
+
end
|
19
|
+
|
20
|
+
def data
|
21
|
+
super.merge({
|
22
|
+
"value" => @attrs["value"] ? "value=\"#{@attrs["value"]}\"" : "",
|
23
|
+
"selected" => @attrs["selected"] ? "selected=\"selected\"" : "",
|
24
|
+
"label" => (@attrs["label"] || @cml.inner_html).to_s
|
25
|
+
})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Radio < Tag
|
4
|
+
BareTemplate = <<-HTML.freeze
|
5
|
+
<input name="{{name}}" type="radio" value="{{value}}" class="{{classes}}" {{checked}}/>
|
6
|
+
HTML
|
7
|
+
|
8
|
+
Template = <<-HTML.freeze
|
9
|
+
<div class="cml_row"><label class="{{gold_class}}">#{BareTemplate} {{label}}</label></div>
|
10
|
+
HTML
|
11
|
+
|
12
|
+
#Radios should always be grouped, but for consistency with checkboxes, we check
|
13
|
+
def grouped?
|
14
|
+
@opts[:bare] || (@cml.parent && ["radios","ratings"].include?(@cml.parent.name))
|
15
|
+
end
|
16
|
+
|
17
|
+
def wrapper(parsed)
|
18
|
+
grouped? ? parsed : super
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
ensure_resolved((@attrs["value"] || @attrs["label"]).to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate?
|
26
|
+
@attrs["duplicate"] ? false : true
|
27
|
+
end
|
28
|
+
|
29
|
+
def checked
|
30
|
+
manipulated = !@opts[:missed].nil?
|
31
|
+
if preload = preloaded_data
|
32
|
+
manipulated = true
|
33
|
+
if preload.map{|x| x.to_s.downcase }.include?(value.to_s.downcase)
|
34
|
+
return "checked=\"checked\""
|
35
|
+
end
|
36
|
+
end
|
37
|
+
!manipulated && @attrs["checked"] ? "checked=\"checked\"" : ""
|
38
|
+
end
|
39
|
+
|
40
|
+
def data
|
41
|
+
super.merge({
|
42
|
+
"checked" => checked,
|
43
|
+
"label" => @attrs["label"].to_s
|
44
|
+
})
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Radios < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{legend}}
|
6
|
+
{{content}}
|
7
|
+
HTML
|
8
|
+
|
9
|
+
def data
|
10
|
+
super.merge({"content" => content })
|
11
|
+
end
|
12
|
+
|
13
|
+
def content
|
14
|
+
cloned= @cml.dup
|
15
|
+
c = children
|
16
|
+
cloned.xpath(".//cml:*[not(self::cml:radio)]").remove
|
17
|
+
cloned.xpath(".//cml:radio").each_with_index do |r,i|
|
18
|
+
r.namespace = nil
|
19
|
+
r.replace(c[i].convert)
|
20
|
+
end
|
21
|
+
#Remove the surrounding tag
|
22
|
+
cloned.to_xhtml.gsub(/<\/?cml:radios[^>]*?>|<\/?>/,'')
|
23
|
+
end
|
24
|
+
memoize :content
|
25
|
+
|
26
|
+
def children
|
27
|
+
@cml.xpath(".//cml:radio").map do |c|
|
28
|
+
c["name"] ||= name
|
29
|
+
c["validates"] ||= @attrs["validates"].to_s
|
30
|
+
Radio.new(c, @opts)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
memoize :children
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|