fabulator 0.0.8 → 0.0.9

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 (41) hide show
  1. data/History.txt +30 -1
  2. data/VERSION +1 -1
  3. data/features/functions.feature +7 -0
  4. data/features/primitives.feature +1 -1
  5. data/features/step_definitions/template_steps.rb +40 -3
  6. data/features/step_definitions/xml_steps.rb +3 -2
  7. data/features/templates.feature +49 -22
  8. data/features/types.feature +4 -4
  9. data/lib/fabulator.rb +4 -0
  10. data/lib/fabulator/compiler.rb +27 -0
  11. data/lib/fabulator/core.rb +3 -8
  12. data/lib/fabulator/core/actions/choose.rb +0 -18
  13. data/lib/fabulator/core/actions/for_each.rb +0 -48
  14. data/lib/fabulator/core/{actions.rb → lib.rb} +189 -117
  15. data/lib/fabulator/core/structurals.rb +7 -0
  16. data/lib/fabulator/core/{constraint.rb → structurals/constraint.rb} +2 -0
  17. data/lib/fabulator/core/{filter.rb → structurals/filter.rb} +2 -0
  18. data/lib/fabulator/core/{group.rb → structurals/group.rb} +2 -0
  19. data/lib/fabulator/core/{parameter.rb → structurals/parameter.rb} +2 -0
  20. data/lib/fabulator/core/{state.rb → structurals/state.rb} +2 -0
  21. data/lib/fabulator/core/{state_machine.rb → structurals/state_machine.rb} +2 -0
  22. data/lib/fabulator/core/{transition.rb → structurals/transition.rb} +2 -0
  23. data/lib/fabulator/expr.rb +6 -0
  24. data/lib/fabulator/expr/context.rb +65 -10
  25. data/lib/fabulator/expr/node_logic.rb +3 -2
  26. data/lib/fabulator/expr/parser.rb +787 -638
  27. data/lib/fabulator/expr/statement_list.rb +27 -0
  28. data/lib/fabulator/lib.rb +3 -0
  29. data/lib/fabulator/lib/action.rb +12 -9
  30. data/lib/fabulator/lib/lib.rb +85 -9
  31. data/lib/fabulator/structural.rb +24 -5
  32. data/lib/fabulator/tag_lib.rb +78 -124
  33. data/lib/fabulator/tag_lib/presentations.rb +39 -0
  34. data/lib/fabulator/tag_lib/transformations.rb +66 -0
  35. data/lib/fabulator/tag_lib/type.rb +176 -0
  36. data/lib/fabulator/template/parse_result.rb +125 -62
  37. data/lib/fabulator/template/parser.rb +17 -1
  38. data/xslt/form.xsl +163 -2083
  39. data/xsm_expression_parser.racc +35 -20
  40. metadata +17 -13
  41. data/lib/fabulator/context.rb +0 -39
@@ -0,0 +1,39 @@
1
+ module Fabulator
2
+ class TagLib
3
+ class Presentations
4
+ def initialize
5
+ @transformations = Fabulator::TagLib::Transformations.new
6
+ @interactives = { }
7
+ @structurals = { }
8
+ end
9
+
10
+ def transformations_into
11
+ @transformations
12
+ end
13
+
14
+ def interactives
15
+ @interactives.keys
16
+ end
17
+
18
+ def structurals
19
+ @structurals.keys
20
+ end
21
+
22
+ def interactive(nom)
23
+ @interactives[nom.to_sym] = nil
24
+ end
25
+
26
+ def structural(nom)
27
+ @structurals[nom.to_sym] = nil
28
+ end
29
+
30
+ def transform(fmt, doc, opts = { })
31
+ @transformations.transform(fmt, doc, opts)
32
+ end
33
+
34
+ def get_root_namespaces(fmt)
35
+ @transformations.get_root_namespaces(fmt)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,66 @@
1
+ require 'xml/libxml'
2
+ require 'libxslt'
3
+
4
+ module Fabulator
5
+ class TagLib
6
+ class Transformations
7
+
8
+ def initialize
9
+ @formats = { }
10
+ end
11
+
12
+ def html(&block)
13
+ @formats[:html] ||= Fabulator::TagLib::Format.new
14
+ @formats[:html].instance_eval &block
15
+ end
16
+
17
+ def transform(fmt, doc, opts = { })
18
+ if !@formats[fmt.to_sym].nil?
19
+ @formats[fmt.to_sym].transform(doc, opts)
20
+ else
21
+ doc
22
+ end
23
+ end
24
+
25
+ def get_root_namespaces(fmt)
26
+ if !@formats[fmt.to_sym].nil?
27
+ @formats[fmt.to_sym].get_root_namespaces
28
+ else
29
+ []
30
+ end
31
+ end
32
+ end
33
+
34
+ class Format
35
+ def initialize
36
+ end
37
+
38
+ def transform(doc, opts)
39
+ if !@xslt.nil?
40
+ @xslt.apply(doc, opts)
41
+ else
42
+ doc
43
+ end
44
+ end
45
+
46
+ def xslt_from_file(fpath)
47
+ @xslt_file = fpath
48
+ @xslt_doc = LibXML::XML::Document.file(@xslt_file)
49
+ @xslt = LibXSLT::XSLT::Stylesheet.new(@xslt_doc)
50
+ end
51
+
52
+ def get_root_namespaces
53
+ if !@xslt_doc.nil?
54
+ # extract namespace declarations from root element
55
+ ret = [ ]
56
+ @xslt_doc.root.namespaces.each { |ns|
57
+ ret << ns.href
58
+ }
59
+ ret
60
+ else
61
+ []
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,176 @@
1
+ module Fabulator
2
+ class TagLib
3
+ class Type
4
+ def initialize(t)
5
+ @type = t
6
+ @goings = { }
7
+ @comings = { }
8
+ @methods = { }
9
+ end
10
+
11
+ def vtype
12
+ @type
13
+ end
14
+
15
+ def going_to(t, &block)
16
+ return unless block
17
+ @goings[t.join('')] ||= [ ]
18
+ obj = TypeConversion.new(t)
19
+ obj.instance_eval &block
20
+ @goings[t.join('')] = obj
21
+ end
22
+
23
+ def coming_from(t, &block)
24
+ return unless block
25
+ @comings[t.join('')] ||= [ ]
26
+ obj = TypeConversion.new(t)
27
+ obj.instance_eval &block
28
+ @comings[t.join('')] = obj
29
+ end
30
+
31
+ def outgoing_conversions
32
+ @goings
33
+ end
34
+
35
+ def incoming_conversions
36
+ @comings
37
+ end
38
+
39
+ def method(nom, &block)
40
+ @methods[@type[0] + nom.to_s] = block
41
+ end
42
+
43
+ def get_method(nom)
44
+ @methods[nom]
45
+ end
46
+
47
+ def build_conversion_to(to)
48
+ return [] if to.nil? || self == to
49
+ ut = self._unify_types(to, true)
50
+ return [] if ut.nil? || ut[:t].join('') != to.join('')
51
+ return ut[:convert]
52
+ end
53
+
54
+ def unify_with_type(t)
55
+ self._unify_types(t)
56
+ end
57
+
58
+ protected
59
+
60
+ def _unify_types(to, ordered = false)
61
+ return nil if to.nil?
62
+ if to.is_a?(Array)
63
+ to = Fabulator::TagLib.type_handler(to)
64
+ return nil if to.nil?
65
+ end
66
+
67
+ d1 = { @type.join('') => { :t => self, :w => 1.0, :path => [ self ], :convert => [ ] } }
68
+ d2 = { to.vtype.join('') => { :t => to, :w => 1.0, :path => [ to ], :convert => [ ] } }
69
+
70
+ added = true
71
+ while added
72
+ added = false
73
+ [d1, d2].each do |d|
74
+ d.keys.each do |t|
75
+ d[t][:t].outgoing_conversions.each_pair do |conv_key, conv|
76
+ #conv_key = conv.vtype.join('')
77
+ w = d[t][:w] * conv.weight
78
+ if d.has_key?(conv_key)
79
+ if d[conv_key][:w] < w
80
+ d[conv_key][:w] = w
81
+ d[conv_key][:path] = d[t][:path] + [ conv.vtype ]
82
+ d[conv_key][:convert] = d[t][:convert] + [ conv ] - [nil]
83
+ end
84
+ else
85
+ added = true
86
+ d[conv_key] = {
87
+ :t => conv.vtype,
88
+ :w => w,
89
+ :path => d[t][:path] + [ conv.vtype ],
90
+ :convert => d[t][:convert] + [ conv ] - [nil],
91
+ }
92
+ end
93
+ end
94
+ end
95
+ Fabulator::TagLib.types.keys.each do |ns|
96
+ Fabulator::TagLib.types[ns].each_pair do |ct, tob|
97
+ to_key = ns + ct.to_s
98
+ tob.incoming_conversions.each_pair do |from_key, conv|
99
+ #from_key = conv.vtype.join('')
100
+ next unless d.has_key?(from_key)
101
+ w = d[from_key][:w] * conv.weight
102
+ if d.has_key?(to_key)
103
+ if d[to_key][:w] < w
104
+ d[to_key][:w] = w
105
+ d[to_key][:path] = d[from_key][:path] + [ conv.vtype ]
106
+ d[to_key][:convert] = d[from_key][:convert] + [ conv ]
107
+ end
108
+ else
109
+ added = true
110
+ d[to_key] = {
111
+ :t => [ ns, ct ],
112
+ :w => w * 95.0 / 100.0,
113
+ :path => d[from_key][:path] + [ conv.vtype ],
114
+ :convert => d[from_key][:convert] + [ conv ],
115
+ }
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ r = self._select_type_path(d1, d2, to, ordered)
122
+ return r unless r.nil?
123
+ end
124
+ return self._select_type_path(d1, d2, to, ordered)
125
+ end
126
+
127
+ def _select_type_path(d1, d2, t2, ordered)
128
+ common = d1.keys & d2.keys
129
+ if ordered && common.include?(t2.vtype.join(''))
130
+ return d1[t2.vtype.join('')]
131
+ elsif !common.empty?
132
+ return d1[common.sort_by{ |c| d1[c][:w] * d2[c][:w] / d1[c][:path].size / d2[c][:path].size }.reverse.first]
133
+ end
134
+ return nil
135
+ end
136
+
137
+ end
138
+
139
+ class TypeConversion
140
+ def initialize(t)
141
+ @type = t
142
+ @weight = 0.0
143
+ # @guard = nil
144
+ end
145
+
146
+ def vtype
147
+ @type
148
+ end
149
+
150
+ def weight(w = nil)
151
+ @weight = w unless w.nil?
152
+ @weight
153
+ end
154
+
155
+ # def guard(&block)
156
+ # if !block.nil?
157
+ # @guard = block
158
+ # end
159
+ # end
160
+
161
+ def converting(&block)
162
+ @conversion = block
163
+ end
164
+
165
+ def convert(v)
166
+ return v.root.anon_node(nil) if @conversion.nil?
167
+ @conversion.call(v)
168
+ end
169
+
170
+ # def can_convert?(v)
171
+ # return true if @guard.nil?
172
+ # @guard.call(v)
173
+ # end
174
+ end
175
+ end
176
+ end
@@ -1,20 +1,67 @@
1
1
  require 'xml/libxml'
2
- require 'libxslt'
3
2
 
4
3
  module Fabulator::Template
5
4
  class ParseResult
6
5
 
7
- @@fabulator_xslt_file = File.join(File.dirname(__FILE__), "..", "..", "..", "xslt", "form.xsl")
6
+ def initialize(text)
7
+ ## we want to build up the XPath expression for structural and
8
+ ## interactive elements for use in adding default info and
9
+ ## building names for those elements -- saves the XSLT from having
10
+ ## to do this
8
11
 
12
+ structurals = { }
13
+ interactives = { }
9
14
 
10
- @@fabulator_xslt_doc = LibXML::XML::Document.file(@@fabulator_xslt_file)
11
- @@fabulator_xslt = LibXSLT::XSLT::Stylesheet.new(@@fabulator_xslt_doc)
15
+ Fabulator::TagLib.namespaces.each_pair do |ns, ob|
16
+ structurals[ns] = ob.presentation.structurals
17
+ interactives[ns] = ob.presentation.interactives
18
+ end
12
19
 
20
+ @namespaces = { }
21
+ i = 1
22
+ (structurals.keys + interactives.keys).uniq.sort.each do |ns|
23
+ @namespaces["fab_ns_#{i.to_s}"] = ns
24
+ i += 1
25
+ end
26
+
27
+ structural_xpaths = []
28
+ interactive_xpaths = []
29
+ interesting_xpaths = [ ]
30
+ @fab_prefix = ''
31
+ @namespaces.keys.each do |p|
32
+ @fab_prefix = p if @namespaces[p] == Fabulator::FAB_NS
33
+ structural_xpaths += structurals[@namespaces[p]].collect{ |e| "ancestor::#{p}:#{e}" }
34
+ interactive_xpaths += interactives[@namespaces[p]].collect{ |e| "//#{p}:#{e}" }
35
+ interesting_xpaths += structurals[@namespaces[p]].collect{ |e| "//#{p}:#{e}" }
36
+ end
37
+ @structural_xpath = structural_xpaths.join("[@id != ''] | ") + "[@id != '']"
38
+ @interactive_xpath = interactive_xpaths.join(" | ")
39
+
40
+ @interesting_xpath = (interactive_xpaths + interesting_xpaths).join(" | ")
41
+
42
+ @namespaces = @namespaces.collect{ |k,v| "#{k}:#{v}" }
43
+
44
+ ## We also may do our dependency tree -- namespaces in the
45
+ ## root element of the stylesheet will be run after the current
46
+ ## stylesheet
13
47
 
14
- def initialize(text)
15
48
  @doc = LibXML::XML::Document.string text
49
+
50
+ @fab_ns = nil
51
+ @doc.root.namespaces.each do |ns|
52
+ if ns.href == Fabulator::FAB_NS
53
+ @fab_ns = ns
54
+ end
55
+ end
56
+
57
+ if @fab_ns.nil?
58
+ @fab_ns = XML::Namespace.new(@doc.root, @fab_prefix, Fabulator::FAB_NS)
59
+ end
16
60
  end
17
61
 
62
+ # This function walks through all of the elements in the provided
63
+ # markup and adds f:default child elements. TagLibs declare data
64
+ # elements that should receive default values.
18
65
  def add_default_values(context)
19
66
  return if context.nil?
20
67
  each_form_element do |el|
@@ -22,13 +69,7 @@ module Fabulator::Template
22
69
  next if own_id.nil? || own_id.to_s == ''
23
70
 
24
71
  default = nil
25
- is_grid = false
26
- if el.name == 'grid'
27
- default = el.find('./default | ./row/default | ./column/default').to_a
28
- is_grid = true
29
- else
30
- default = el.find('./default').to_a
31
- end
72
+ default = el.find("./#{@fab_prefix}:default", @namespaces).to_a
32
73
 
33
74
  id = el_id(el)
34
75
  ids = id.split('/')
@@ -37,31 +78,8 @@ module Fabulator::Template
37
78
  if !default.nil? && !default.empty?
38
79
  default.each { |d| d.remove! }
39
80
  end
40
- if is_grid
41
- count = (el.attributes['count'].to_s rescue '')
42
- how_many = 'multiple'
43
- direction = 'both'
44
- if count =~ %r{^(multiple|single)(-by-(row|column))?$}
45
- how_many = $1
46
- direction = $3 || 'both'
47
- end
48
- if direction == 'both'
49
- l.collect{|ll| ll.value}.each do |v|
50
- el << text_node('default', v)
51
- end
52
- elsif direction == 'row' || direction == 'column'
53
- el.find("./#{direction}").each do |div|
54
- id = (div.attributes['id'].to_s rescue '')
55
- next if id == ''
56
- l.collect{|c| context.with_root(c).traverse_path(id)}.flatten.collect{|c| c.value}.each do |v|
57
- div << text_node('default', v)
58
- end
59
- end
60
- end
61
- else
62
- l.collect{|ll| ll.value}.each do |v|
63
- el << text_node('default', v)
64
- end
81
+ l.collect{|ll| ll.value}.each do |v|
82
+ el << text_node('default', v)
65
83
  end
66
84
  end
67
85
  end
@@ -92,7 +110,7 @@ module Fabulator::Template
92
110
  end
93
111
 
94
112
  def add_captions(captions = { })
95
- each_form_element do |el|
113
+ each_element do |el|
96
114
  id = el_id(el)
97
115
  next if id == ''
98
116
  caption = nil
@@ -107,7 +125,7 @@ module Fabulator::Template
107
125
  is_grid = false
108
126
  if el.name == 'grid'
109
127
  else
110
- cap = el.find_first('./caption')
128
+ cap = el.find_first("./#{@fab_prefix}:caption", @namespaces)
111
129
  if cap.nil?
112
130
  el << text_node('caption', caption)
113
131
  else
@@ -120,56 +138,101 @@ module Fabulator::Template
120
138
  end
121
139
 
122
140
  def to_s
123
- @doc.to_s
141
+ @doc.to_s.gsub(/^\s*<\?xml\s+.*?\?>\s*/, '')
124
142
  end
125
143
 
126
144
  def to_html(popts = { })
127
- opts = { :form => true }.update(popts)
145
+ opts = { :form => true, :theme => 'coal' }.update(popts)
146
+
147
+ deps = { }
148
+ Fabulator::TagLib.namespaces.each_pair do |ns, ob|
149
+ deps[ns] = ob.presentation.get_root_namespaces(:html) & (Fabulator::TagLib.namespaces.keys) - [ ns ]
150
+ end
151
+
152
+ ordered_ns = [ ]
153
+
154
+ next_round = ([ Fabulator::FAB_NS ] + deps.keys.select { |k| deps[k].empty? }).uniq
128
155
 
129
- res = @@fabulator_xslt.apply(@doc)
156
+ while !next_round.empty? do
157
+ next_round.each { |k| deps.delete(k) }
158
+
159
+ ordered_ns += next_round
160
+
161
+ deps.keys.each do |k|
162
+ deps[k] -= ordered_ns
163
+ end
164
+
165
+ next_round = deps.keys.select{ |k| deps[k].empty? }
166
+ end
130
167
 
168
+ ordered_ns.reverse!
169
+
170
+ res = @doc
171
+ ordered_ns.each do |ns|
172
+ ob = Fabulator::TagLib.namespaces[ns]
173
+ next if ob.nil?
174
+ res = ob.presentation.transform(:html, res, opts)
175
+ end
176
+
177
+ ret = ''
131
178
  if opts[:form]
132
- res.to_s
179
+ ret = res.to_s.gsub(/^\s*<\?xml\s+.*?\?>\s*/, '').gsub(/xmlns(:\S+)?=['"][^'"]*['"]/, '').gsub(/\s+/, ' ').gsub(/\s+>/, '>')
133
180
  else
134
- res.find('//form/*').collect{ |e| e.to_s}.join('')
181
+ ret = res.find('//form/*').collect{ |e| e.to_s}.join('').gsub(/^\s*<\?xml\s+.*?\?>\s*/, '').gsub(/xmlns(:\S+)?=['"][^'"]*['"]/, '').gsub(/\s+/, ' ').gsub(/\s+>/, '>')
182
+
135
183
  end
184
+
185
+ ret.gsub!(/<(div\s*[^<]*?)\/>/, "<\\1></div>")
186
+ ret.gsub!(/<(span\s*[^<]*?)\/>/, "<\\1></span>")
187
+ ret
136
188
  end
137
189
 
138
190
  protected
139
191
 
192
+ def each_element(&block)
193
+ @doc.root.find(@interesting_xpath, @namespaces).each do |el|
194
+ yield el
195
+ end
196
+ end
197
+
140
198
  def each_form_element(&block)
141
- @doc.root.find(%{
142
- //text
143
- | //textline
144
- | //textbox
145
- | //editbox
146
- | //asset
147
- | //password
148
- | //selection
149
- | //grid
150
- | //submit
151
- }).each do |el|
199
+ @doc.root.find(@interactive_xpath, @namespaces).each do |el|
152
200
  yield el
153
201
  end
202
+ # @doc.root.find(%{
203
+ # //text
204
+ # | //textline
205
+ # | //textbox
206
+ # | //editbox
207
+ # | //asset
208
+ # | //password
209
+ # | //selection
210
+ # | //grid
211
+ # | //submit
212
+ # }).each do |el|
213
+ # yield el
214
+ # end
154
215
  end
155
216
 
156
217
  def el_id(el)
157
218
  own_id = el.attributes['id']
158
219
  return '' if own_id.nil? || own_id == ''
159
220
 
160
- ancestors = el.find(%{
161
- ancestor::option[@id != '']
162
- | ancestor::group[@id != '']
163
- | ancestor::form[@id != '']
164
- | ancestor::container[@id != '']
165
- })
221
+ # ancestors = el.find(%{
222
+ # ancestor::option[@id != '']
223
+ # | ancestor::group[@id != '']
224
+ # | ancestor::form[@id != '']
225
+ # | ancestor::container[@id != '']
226
+ # })
227
+ ancestors = el.find(@structural_xpath, @namespaces)
166
228
  ids = ancestors.collect{|a| a.attributes['id']}.select{|a| !a.nil? }
167
229
  ids << own_id
168
230
  ids.collect{|i| i.to_s}.join('/')
169
231
  end
170
232
 
171
233
  def text_node(n,t)
172
- e = XML::Node.new(n)
234
+ e = XML::Node.new(n, nil)
235
+ e.namespaces.namespace = @fab_ns
173
236
  e << t
174
237
  e
175
238
  end