cml 1.4.2 → 1.5.0

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 (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