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.
Files changed (61) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +18 -0
  5. data/Rakefile +56 -0
  6. data/VERSION +1 -0
  7. data/cml.gemspec +129 -0
  8. data/lib/cml/converters/jsawesome.rb +103 -0
  9. data/lib/cml/gold.rb +33 -0
  10. data/lib/cml/liquid_filters.rb +13 -0
  11. data/lib/cml/parser.rb +160 -0
  12. data/lib/cml/tag.rb +308 -0
  13. data/lib/cml/tags/checkbox.rb +77 -0
  14. data/lib/cml/tags/checkboxes.rb +38 -0
  15. data/lib/cml/tags/group.rb +25 -0
  16. data/lib/cml/tags/hidden.rb +17 -0
  17. data/lib/cml/tags/iterate.rb +86 -0
  18. data/lib/cml/tags/meta.rb +12 -0
  19. data/lib/cml/tags/multiple_text.rb +11 -0
  20. data/lib/cml/tags/option.rb +29 -0
  21. data/lib/cml/tags/radio.rb +48 -0
  22. data/lib/cml/tags/radios.rb +36 -0
  23. data/lib/cml/tags/ratings.rb +61 -0
  24. data/lib/cml/tags/search.rb +97 -0
  25. data/lib/cml/tags/select.rb +50 -0
  26. data/lib/cml/tags/text.rb +45 -0
  27. data/lib/cml/tags/textarea.rb +45 -0
  28. data/lib/cml/tags/thumb.rb +25 -0
  29. data/lib/cml/tags/unknown.rb +21 -0
  30. data/lib/cml.rb +15 -0
  31. data/spec/complex_spec.rb +127 -0
  32. data/spec/converters/jsawesome_spec.rb +75 -0
  33. data/spec/fixtures/complex.cml +23 -0
  34. data/spec/fixtures/html.cml +34 -0
  35. data/spec/fixtures/invalid.cml +10 -0
  36. data/spec/fixtures/script-style.cml +12 -0
  37. data/spec/fixtures/segfault.cml +1 -0
  38. data/spec/gold_spec.rb +29 -0
  39. data/spec/meta_spec.rb +47 -0
  40. data/spec/normalize_spec.rb +214 -0
  41. data/spec/show_data_spec.rb +308 -0
  42. data/spec/sorta_match.rb +41 -0
  43. data/spec/spec_helper.rb +26 -0
  44. data/spec/tags/checkbox_spec.rb +155 -0
  45. data/spec/tags/checkboxes_spec.rb +171 -0
  46. data/spec/tags/group_spec.rb +108 -0
  47. data/spec/tags/hidden_spec.rb +17 -0
  48. data/spec/tags/iterate_spec.rb +259 -0
  49. data/spec/tags/meta_spec.rb +14 -0
  50. data/spec/tags/multiple_text_spec.rb +40 -0
  51. data/spec/tags/radios_spec.rb +81 -0
  52. data/spec/tags/ratings_spec.rb +79 -0
  53. data/spec/tags/search_spec.rb +132 -0
  54. data/spec/tags/select_spec.rb +76 -0
  55. data/spec/tags/tag_spec.rb +93 -0
  56. data/spec/tags/text_spec.rb +66 -0
  57. data/spec/tags/textarea_spec.rb +58 -0
  58. data/spec/tags/thumb_spec.rb +20 -0
  59. data/spec/tags/unknown_spec.rb +19 -0
  60. data/spec/validation_spec.rb +62 -0
  61. 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,12 @@
1
+ module CML
2
+ module Tags
3
+ class Meta < Tag
4
+ Template = <<-HTML.freeze
5
+ HTML
6
+
7
+ def wrapper(parsed)
8
+ parsed
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__)+"/text"
2
+ module CML
3
+ module Tags
4
+ class MultipleText < Text
5
+ def initialize(cml, opts)
6
+ cml = cml.to_xml unless cml.is_a?(String)
7
+ super(cml.sub(/multiple_text/, "text multiple=\"true\""), opts)
8
+ end
9
+ end
10
+ end
11
+ 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