fabulator 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
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