cml 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.rspec +1 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +33 -0
  4. data/README.rdoc +13 -3
  5. data/Rakefile +9 -49
  6. data/cml.gemspec +23 -125
  7. data/lib/cml/converters/jsawesome.rb +3 -3
  8. data/lib/cml/gold.rb +12 -7
  9. data/lib/cml/liquid_filters.rb +4 -0
  10. data/lib/cml/logic.rb +424 -0
  11. data/lib/cml/logic_tree/graph.rb +107 -0
  12. data/lib/cml/logic_tree/solver.rb +43 -0
  13. data/lib/cml/parser.rb +47 -7
  14. data/lib/cml/tag.rb +42 -21
  15. data/lib/cml/tags/checkbox.rb +14 -4
  16. data/lib/cml/tags/checkboxes.rb +4 -0
  17. data/lib/cml/tags/group.rb +4 -0
  18. data/lib/cml/tags/hours.rb +263 -0
  19. data/lib/cml/tags/iterate.rb +4 -0
  20. data/lib/cml/tags/meta.rb +4 -0
  21. data/lib/cml/tags/option.rb +4 -0
  22. data/lib/cml/tags/radio.rb +13 -4
  23. data/lib/cml/tags/radios.rb +4 -0
  24. data/lib/cml/tags/ratings.rb +9 -1
  25. data/lib/cml/tags/search.rb +8 -2
  26. data/lib/cml/tags/select.rb +6 -2
  27. data/lib/cml/tags/taxonomy.rb +148 -0
  28. data/lib/cml/tags/thumb.rb +4 -0
  29. data/lib/cml/tags/unknown.rb +4 -0
  30. data/lib/cml/version.rb +3 -0
  31. data/lib/cml.rb +3 -0
  32. data/spec/converters/jsawesome_spec.rb +0 -9
  33. data/spec/fixtures/logic_broken.cml +5 -0
  34. data/spec/fixtures/logic_circular.cml +5 -0
  35. data/spec/fixtures/logic_grouped.cml +7 -0
  36. data/spec/fixtures/logic_nested.cml +7 -0
  37. data/spec/fixtures/logic_none.cml +7 -0
  38. data/spec/fixtures/logic_not_nested.cml +7 -0
  39. data/spec/liquid_filter_spec.rb +19 -0
  40. data/spec/logic_depends_on_spec.rb +242 -0
  41. data/spec/logic_spec.rb +207 -0
  42. data/spec/logic_tree_graph_spec.rb +465 -0
  43. data/spec/logic_tree_solver_spec.rb +58 -0
  44. data/spec/meta_spec.rb +12 -2
  45. data/spec/show_data_spec.rb +3 -2
  46. data/spec/spec_helper.rb +22 -6
  47. data/spec/tags/checkboxes_spec.rb +2 -2
  48. data/spec/tags/group_spec.rb +5 -5
  49. data/spec/tags/hours_spec.rb +404 -0
  50. data/spec/tags/radios_spec.rb +2 -2
  51. data/spec/tags/ratings_spec.rb +1 -1
  52. data/spec/tags/select_spec.rb +45 -0
  53. data/spec/tags/tag_spec.rb +25 -0
  54. data/spec/tags/taxonomy_spec.rb +112 -0
  55. data/spec/validation_spec.rb +52 -0
  56. metadata +112 -17
  57. data/VERSION +0 -1
data/lib/cml/tag.rb CHANGED
@@ -2,9 +2,28 @@ require 'cgi'
2
2
 
3
3
  module CML
4
4
  class Tag
5
+
6
+ include CML::TagLogic
7
+
5
8
  attr_reader :attrs, :tag, :data, :cml, :opts
6
9
  #liquid_methods :raw_label
7
10
 
11
+ #memoize hotness
12
+ def self.memoize(*methods)
13
+ methods.each do |m|
14
+ class_eval %Q{
15
+ alias :original_#{m} :#{m}
16
+
17
+ def #{m}(*args)
18
+ @#{m}_cache ||= {}
19
+ key = {:prefix => @opts[:prefix], :missed => @opts[:missed]}.merge(:_args => args)
20
+ return @#{m}_cache[key] if @#{m}_cache[key]
21
+ @#{m}_cache[key] = self.original_#{m}(*args)
22
+ end
23
+ }
24
+ end
25
+ end
26
+
8
27
  def initialize(cml, opts = {})
9
28
  @cml = cml.is_a?(String) ? Parser.parse(cml).at("/root/*") : cml
10
29
  @attrs = @cml.attributes#Hash[*cml.attributes.map {|k,v| [k.intern,v]}.flatten]
@@ -16,10 +35,14 @@ module CML
16
35
  def prefix(name)
17
36
  if @opts[:iteration]
18
37
  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}}][]" : "[]"
38
+ when "checkbox"
39
+ "[{{i}}][]"
40
+ when "radio"
41
+ "[{{i}}]"
42
+ when "rating"
43
+ @opts[:normalize] ? "[{{i}}][]" : "[{{i}}]"
44
+ when "text", "textarea"
45
+ @opts[:normalize] ? "[{{i}}][]" : "[]"
23
46
  else
24
47
  "[]"
25
48
  end
@@ -52,7 +75,8 @@ module CML
52
75
  #:list means an array of values in the result csv
53
76
  #:columnlist means a new column that is a list
54
77
  def multi_type
55
- grouped = @cml.xpath("ancestor::cml:*[@multiple]")
78
+ grouped = self.parent_multiples
79
+
56
80
  if @attrs["multiple"] || !grouped.empty?
57
81
  if @cml.at(".//cml:checkboxes|.//cml:radios|.//cml:ratings")
58
82
  :column
@@ -72,6 +96,11 @@ module CML
72
96
  end
73
97
  end
74
98
 
99
+ def parent_multiples
100
+ @cml.xpath("ancestor::cml:*[@multiple]")
101
+ end
102
+ memoize :parent_multiples
103
+
75
104
  def gold?
76
105
  @attrs["gold"] ? @cml : @cml.at("./cml:gold")
77
106
  end
@@ -191,7 +220,7 @@ module CML
191
220
  end
192
221
 
193
222
  def validations
194
- @validations ||= validators.map {|v| "validates-#{v}"}
223
+ @validations ||= validators.map {|v| v.to_s.empty? ? nil : "validates-#{v}"}.compact
195
224
  end
196
225
 
197
226
  def instructions
@@ -264,6 +293,10 @@ module CML
264
293
  })
265
294
  end
266
295
 
296
+ def finite_value?
297
+ (@attrs['is-finite'] && @attrs['is-finite'].value=='true') || false
298
+ end
299
+
267
300
  protected
268
301
 
269
302
  def data_attributes
@@ -288,21 +321,9 @@ module CML
288
321
 
289
322
  attributes.join(" ")
290
323
  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
324
+
325
+ def title_attribute
326
+ @attrs['title'].nil? ? "" :"title=\"#{@attrs['title']}\""
306
327
  end
307
328
  end
308
329
  end
@@ -7,7 +7,7 @@ module CML
7
7
  HTML
8
8
 
9
9
  Template = <<-HTML.freeze
10
- {{default_normalized}}<div class="cml_row"><label class="{{gold_class}}">#{BareTemplate} {{label}}</label></div>
10
+ {{default_normalized}}<div class="cml_row" {{title}}><label class="{{gold_class}}">#{BareTemplate} {{label}}</label></div>
11
11
  HTML
12
12
 
13
13
  def prefix(name)
@@ -20,6 +20,14 @@ module CML
20
20
  @opts[:bare] || (@cml.parent && @cml.parent.name == "checkboxes")
21
21
  end
22
22
 
23
+ def in_logic_graph?
24
+ !grouped?
25
+ end
26
+
27
+ def finite_value?
28
+ true
29
+ end
30
+
23
31
  def wrapper(parsed)
24
32
  grouped? || @opts[:no_reason] ? parsed : super
25
33
  end
@@ -47,7 +55,7 @@ module CML
47
55
  end
48
56
  elsif preload = preloaded_data
49
57
  manipulated = true
50
- if preload.map{|x| x.to_s.downcase }.include?(value.to_s.downcase)
58
+ if Array(preload).map{|x| x.to_s.downcase }.include?(value.to_s.downcase)
51
59
  return "checked=\"checked\""
52
60
  end
53
61
  end
@@ -70,8 +78,10 @@ module CML
70
78
  "default" => default,
71
79
  "default_normalized" => default_normalized,
72
80
  "checked" => c,
73
- "label" => @attrs["label"].to_s})
81
+ "label" => @attrs["label"].to_s,
82
+ "title" => title_attribute
83
+ })
74
84
  end
75
85
  end
76
86
  end
77
- end
87
+ end
@@ -6,6 +6,10 @@ module CML
6
6
  {{content}}
7
7
  HTML
8
8
 
9
+ def finite_value?
10
+ true
11
+ end
12
+
9
13
  def data
10
14
  #Don't memoize individual checkboxes if we are normalizing
11
15
  super.merge({"content" => content(@opts[:normalize] ? @opts[:iteration] : nil) })
@@ -6,6 +6,10 @@ module CML
6
6
  {{content}}
7
7
  HTML
8
8
 
9
+ def in_logic_graph?
10
+ false
11
+ end
12
+
9
13
  def validate?
10
14
  false
11
15
  end
@@ -0,0 +1,263 @@
1
+ module CML
2
+
3
+ class TimeSelect
4
+ HOURS = [12, 1.upto(11).to_a]
5
+ MINUTES = [0, 15, 30, 45]
6
+ SUFFIXES = [:am, :pm]
7
+
8
+ BlankOption = "<option value=\"\">Select one:</option>"
9
+ GoldBlankOption = "<option value=\"\">(Any)</option>"
10
+ NotListedOption = "<option{{selected_att}} value=\"not_listed\">(Not listed)</option>"
11
+
12
+ # Builds a time <select> input that submits military time values.
13
+ def self.build( name, gold=false, opts={} )
14
+ opts = {
15
+ :selected => nil,
16
+ :allow_unlisted => false
17
+ }.merge( opts )
18
+
19
+ time_options = SUFFIXES.map do |am_pm|
20
+ HOURS.flatten.map do |h|
21
+ h_mil = h
22
+ h_mil = 0 if h_mil == 12 && am_pm == :am
23
+ MINUTES.map do |m|
24
+ time_label = "#{h}:#{self.zero_pad(m)}#{am_pm}"
25
+ mil_time = self.to_military( h_mil, m, am_pm )
26
+ selected_att = mil_time == opts[:selected] ? " selected=\"selected\"" : ""
27
+ "<option#{selected_att} value=\"#{mil_time}\">#{time_label}</option>"
28
+ end.join("")
29
+ end.join("")
30
+ end.join("")
31
+ extras = gold ? GoldBlankOption : BlankOption
32
+
33
+ # Add the not listed field and select it if needed
34
+ if opts[:allow_unlisted]
35
+ selected_att = "not_listed" == opts[:selected] ? " selected=\"selected\"" : ""
36
+ not_listed_html = Liquid::Template.parse( NotListedOption ).render( "selected_att" => selected_att )
37
+ extras += not_listed_html
38
+ end
39
+
40
+ "<select name=\"#{name}\" class=\"cml_hours_time\">#{extras}#{time_options}</select>"
41
+ end
42
+
43
+ protected
44
+
45
+ def self.zero_pad( num )
46
+ '%02d' % num
47
+ end
48
+
49
+ def self.to_military( hour, minute, am_pm )
50
+ if am_pm == :am
51
+ hour = 0 if minute == 0 && hour == 12
52
+ elsif hour < 12
53
+ hour = hour + 12
54
+ end
55
+
56
+ "#{self.zero_pad( hour )}:#{self.zero_pad( minute )}"
57
+ end
58
+ end
59
+
60
+ # =============
61
+ # = CML:Hours =
62
+ # =============
63
+
64
+ module Tags
65
+ class Hours < Tag
66
+
67
+ DAYS = [
68
+ { :key => 'mon', :label => "Monday" },
69
+ { :key => 'tue', :label => "Tuesday" },
70
+ { :key => 'wed', :label => "Wednesday" },
71
+ { :key => 'thu', :label => "Thursday" },
72
+ { :key => 'fri', :label => "Friday" },
73
+ { :key => 'sat', :label => "Saturday" },
74
+ # { :key => 'cat', :label => "Caturday" },
75
+ { :key => 'sun', :label => "Sunday" }
76
+ ]
77
+
78
+ # =============
79
+ # = Templates =
80
+ # =============
81
+
82
+ Template = <<-HTML.freeze
83
+ {{label}}
84
+ <table class="cml_hours cml_row {{classes}}">
85
+ {{rows_html}}
86
+ </table>
87
+ HTML
88
+
89
+ RowTemplate = Liquid::Template.parse(<<-HTML.freeze)
90
+ <tr class="cml_hours_{{day_key}}">
91
+ <td class="cml_hours_day">{{day_label}}</td>
92
+ <td class="cml_hours_hours">
93
+ <p class="cml_hours_1">
94
+ <span class="cml_hours_selects">{{hours_open_1}}</span>
95
+ <span class="cml_hours_to">to</span>
96
+ <span class="cml_hours_selects">{{hours_close_1}}</span>
97
+ </p>
98
+ <p class="cml_hours_2">
99
+ <span class="cml_hours_selects">{{hours_open_2}}</span>
100
+ <span class="cml_hours_to">to</span>
101
+ <span class="cml_hours_selects">{{hours_close_2}}</span>
102
+ </p>
103
+ </td>
104
+ <td class="cml_hours_open_close">
105
+ <p class="cml_hours_openallday">
106
+ {{open_checkbox}}
107
+ </p>
108
+ <p class="cml_hours_closedallday">
109
+ {{closed_checkbox}}
110
+ </p>
111
+ </td>
112
+ </tr>
113
+ HTML
114
+
115
+ TrueFalseCheckboxTemplate = Liquid::Template.parse(<<-HTML)
116
+ <input type="hidden" name="{{name}}" value="FALSE" />
117
+ <input {{checked}}type="checkbox" name="{{name}}" id="{{id}}" value="TRUE" />
118
+ <label for="{{id}}">{{label}}</label>
119
+ HTML
120
+
121
+ TimeSelectTemplate = Liquid::Template.parse( TimeSelect.build( "{{name}}", false ) )
122
+ TimeSelectTemplateAllowUnlisted = Liquid::Template.parse( TimeSelect.build( "{{name}}", false, :allow_unlisted => true ) )
123
+
124
+ def initialize(cml, opts = {})
125
+ super( cml, opts )
126
+ @attrs["matcher"] = "hours"
127
+ @attrs["validates"] = @attrs["validates"].to_s + " hours"
128
+ @attrs["allowunlisted"] = !(@attrs["allowunlisted"].to_s =~ /^t/i).nil?
129
+ end
130
+
131
+ def finite_value?
132
+ true
133
+ end
134
+
135
+ # =============
136
+ # = Rendering =
137
+ # =============
138
+
139
+ def to_html
140
+ data = self.data.merge({
141
+ "rows_html" => rows_html
142
+ })
143
+ rendered = Liquid::Template.parse( Template ).render( data )
144
+ wrapper( rendered )
145
+ end
146
+
147
+ # =============
148
+ # = Normalize =
149
+ # =============
150
+
151
+ def normalize?
152
+ @opts[:normalize]
153
+ end
154
+
155
+ def raw_data
156
+ @opts[:data] || {}
157
+ end
158
+
159
+ # Returns data value as array of values or nil, if none.
160
+ def normalized_data_value( name )
161
+ val = raw_data[name]
162
+ if val && !val.empty?
163
+ Array( val )
164
+ else
165
+ nil
166
+ end
167
+ end
168
+
169
+ # "true", true, "coolio" --> true
170
+ # "false", false, "", nil --> false
171
+ def truthy_checkbox_value?( value )
172
+ value = value.is_a?(Array) ? value[0].to_s : value.to_s
173
+ !( value.empty? || value =~ /^f/i )
174
+ end
175
+
176
+ # ===============
177
+ # = Field names =
178
+ # ===============
179
+
180
+ # Builds a field name for the given day and suffix. e.g:
181
+ # field_name( <tue>, "open_1" ) --> "u123[my_field_tue_open_1]"
182
+ def field_name( day, field_suffix )
183
+ prefix( name( true, "#{day[:key]}_#{field_suffix}" ) )
184
+ end
185
+
186
+ protected
187
+
188
+ # Render a time <select> for the given day and field suffix ("open_1",
189
+ # "closed_1", etc.)
190
+ def time_select_html( day, suffix )
191
+ key = name( true, "#{day[:key]}_#{suffix}" )
192
+ name = field_name( day, suffix )
193
+ allow_unlisted = @attrs["allowunlisted"]
194
+
195
+ if normalize?
196
+ if values = self.normalized_data_value( key )
197
+ # With preloaded values
198
+ values.map do |value|
199
+ TimeSelect.build( name, true, {
200
+ :selected => value,
201
+ :allow_unlisted => allow_unlisted
202
+ })
203
+ end.join( "\n" )
204
+ else
205
+ # No preloaded values
206
+ TimeSelect.build( name, true, :allow_unlisted => allow_unlisted )
207
+ end
208
+ else
209
+ template = allow_unlisted ? TimeSelectTemplateAllowUnlisted : TimeSelectTemplate
210
+ template.render( 'name' => name )
211
+ end
212
+ end
213
+
214
+ # Turn any string into a reasonable id attribute
215
+ def make_id( str )
216
+ "id_#{str}".gsub(/[^a-z0-9\{\}]/, "_").gsub(/_+/, "_").sub(/_+$/, "")
217
+ end
218
+
219
+ # Render HTML with a <tr> of inputs for every day of the week
220
+ def rows_html
221
+ DAYS.map do |day|
222
+ RowTemplate.render({
223
+ "day_key" => day[:key],
224
+ "day_label" => day[:label],
225
+ 'open_checkbox' => self.open_all_day_html( day ),
226
+ 'closed_checkbox' => self.closed_all_day_html( day ),
227
+ "hours_open_1" => self.time_select_html( day, "open_1" ),
228
+ "hours_close_1" => self.time_select_html( day, "close_1" ),
229
+ "hours_open_2" => self.time_select_html( day, "open_2" ),
230
+ "hours_close_2" => self.time_select_html( day, "close_2" )
231
+ })
232
+ end.join("\n")
233
+ end
234
+
235
+ def open_all_day_html( day )
236
+ self.checkbox_html( "Open all day", day, "openallday" )
237
+ end
238
+
239
+ def closed_all_day_html( day )
240
+ self.checkbox_html( "Closed all day", day, "closedallday" )
241
+ end
242
+
243
+ # Build HTML for a "TRUE" / "FALSE" checkbox with the usual hidden input
244
+ # hackery.
245
+ def checkbox_html( label, day, suffix )
246
+ key = name( true, "#{day[:key]}_#{suffix}" )
247
+ name = field_name( day, suffix )
248
+ if truthy_checkbox_value?( raw_data[key] )
249
+ checked = "checked=\"checked\" "
250
+ else
251
+ checked = ""
252
+ end
253
+
254
+ TrueFalseCheckboxTemplate.render({
255
+ 'name' => name,
256
+ 'id' => self.make_id( name ),
257
+ 'label' => label,
258
+ 'checked' => checked
259
+ })
260
+ end
261
+ end
262
+ end
263
+ end
@@ -5,6 +5,10 @@ module CML
5
5
  {{ content }}
6
6
  HTML
7
7
 
8
+ def in_logic_graph?
9
+ false
10
+ end
11
+
8
12
  def validate?
9
13
  false
10
14
  end
data/lib/cml/tags/meta.rb CHANGED
@@ -4,6 +4,10 @@ module CML
4
4
  Template = <<-HTML.freeze
5
5
  HTML
6
6
 
7
+ def in_logic_graph?
8
+ false
9
+ end
10
+
7
11
  def wrapper(parsed)
8
12
  parsed
9
13
  end
@@ -5,6 +5,10 @@ module CML
5
5
  <option {{value}} {{selected}}>{{label}}</option>
6
6
  HTML
7
7
 
8
+ def in_logic_graph?
9
+ false
10
+ end
11
+
8
12
  def wrapper(parsed)
9
13
  parsed
10
14
  end
@@ -6,7 +6,7 @@ module CML
6
6
  HTML
7
7
 
8
8
  Template = <<-HTML.freeze
9
- <div class="cml_row"><label class="{{gold_class}}">#{BareTemplate} {{label}}</label></div>
9
+ <div class="cml_row" {{title}}><label class="{{gold_class}}">#{BareTemplate} {{label}}</label></div>
10
10
  HTML
11
11
 
12
12
  #Radios should always be grouped, but for consistency with checkboxes, we check
@@ -14,6 +14,14 @@ module CML
14
14
  @opts[:bare] || (@cml.parent && ["radios","ratings"].include?(@cml.parent.name))
15
15
  end
16
16
 
17
+ def in_logic_graph?
18
+ !grouped?
19
+ end
20
+
21
+ def finite_value?
22
+ true
23
+ end
24
+
17
25
  def wrapper(parsed)
18
26
  grouped? ? parsed : super
19
27
  end
@@ -30,7 +38,7 @@ module CML
30
38
  manipulated = !@opts[:missed].nil?
31
39
  if preload = preloaded_data
32
40
  manipulated = true
33
- if preload.map{|x| x.to_s.downcase }.include?(value.to_s.downcase)
41
+ if Array(preload).map{|x| x.to_s.downcase }.include?(value.to_s.downcase)
34
42
  return "checked=\"checked\""
35
43
  end
36
44
  end
@@ -40,9 +48,10 @@ module CML
40
48
  def data
41
49
  super.merge({
42
50
  "checked" => checked,
43
- "label" => @attrs["label"].to_s
51
+ "label" => @attrs["label"].to_s,
52
+ "title" => title_attribute
44
53
  })
45
54
  end
46
55
  end
47
56
  end
48
- end
57
+ end
@@ -31,6 +31,10 @@ module CML
31
31
  end
32
32
  end
33
33
  memoize :children
34
+
35
+ def finite_value?
36
+ true
37
+ end
34
38
  end
35
39
  end
36
40
  end
@@ -39,6 +39,10 @@ module CML
39
39
  def gold_classes
40
40
  ratings.map {|rating| rating.gold_class }
41
41
  end
42
+
43
+ def children
44
+ ratings
45
+ end
42
46
 
43
47
  def ratings
44
48
  if @cml.xpath(".//cml:rating").empty?
@@ -56,6 +60,10 @@ module CML
56
60
  end
57
61
  end
58
62
  memoize :ratings
63
+
64
+ def finite_value?
65
+ true
66
+ end
59
67
  end
60
68
  end
61
- end
69
+ end
@@ -25,6 +25,10 @@ module CML
25
25
  !(@attrs["field"] && @attrs["field"].to_s == "false")
26
26
  end
27
27
 
28
+ def in_logic_graph?
29
+ is_field?
30
+ end
31
+
28
32
  def search_terms_classes
29
33
  (@attrs["default"] ? "default " : "") + engine + " no_validate"
30
34
  end
@@ -46,11 +50,13 @@ module CML
46
50
  end
47
51
 
48
52
  def search_terms_value
49
- ((@opts[:data] || {})[search_terms_name] || @attrs["default"]).to_s
53
+ val = ((@opts[:data] || {})[search_terms_name] || @attrs["default"])
54
+ val.is_a?(Array) ? val[0].to_s : val.to_s
50
55
  end
51
56
 
52
57
  def search_result_value
53
- (@attrs["value"] || (@opts[:data] || {})[name] || "").to_s
58
+ val = (@attrs["value"] || (@opts[:data] || {})[name] || "")
59
+ val.is_a?(Array) ? val[0].to_s : val.to_s
54
60
  end
55
61
 
56
62
  def data
@@ -34,7 +34,7 @@ module CML
34
34
  # Preloaded data
35
35
  preload = preloaded_data
36
36
 
37
- cloned.add_child(d[0].cml) if d.length > 0
37
+ cloned.children.first.add_previous_sibling(d[0].cml) if d.length > 0
38
38
  cloned.xpath(".//cml:*[not(self::cml:option)]").remove
39
39
  cloned.xpath(".//cml:option").each_with_index do |r,i|
40
40
  r.namespace = nil
@@ -45,6 +45,10 @@ module CML
45
45
  cloned.to_xhtml.gsub(/<\/?cml:select[^>]*?>|<\/?>/,'')
46
46
  end
47
47
  memoize :content
48
+
49
+ def finite_value?
50
+ true
51
+ end
48
52
  end
49
53
  end
50
- end
54
+ end