fabulator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +41 -0
  3. data/README.rdoc +61 -0
  4. data/Rakefile +54 -0
  5. data/lib/fabulator.rb +5 -0
  6. data/lib/fabulator/action.rb +46 -0
  7. data/lib/fabulator/action_lib.rb +438 -0
  8. data/lib/fabulator/context.rb +39 -0
  9. data/lib/fabulator/core.rb +8 -0
  10. data/lib/fabulator/core/actions.rb +514 -0
  11. data/lib/fabulator/core/actions/choose.rb +167 -0
  12. data/lib/fabulator/core/actions/for_each.rb +105 -0
  13. data/lib/fabulator/core/actions/variables.rb +52 -0
  14. data/lib/fabulator/core/constraint.rb +117 -0
  15. data/lib/fabulator/core/filter.rb +41 -0
  16. data/lib/fabulator/core/group.rb +123 -0
  17. data/lib/fabulator/core/parameter.rb +128 -0
  18. data/lib/fabulator/core/state.rb +91 -0
  19. data/lib/fabulator/core/state_machine.rb +153 -0
  20. data/lib/fabulator/core/transition.rb +164 -0
  21. data/lib/fabulator/expr.rb +37 -0
  22. data/lib/fabulator/expr/axis.rb +133 -0
  23. data/lib/fabulator/expr/axis_descendent_or_self.rb +26 -0
  24. data/lib/fabulator/expr/bin_expr.rb +178 -0
  25. data/lib/fabulator/expr/context.rb +368 -0
  26. data/lib/fabulator/expr/for_expr.rb +74 -0
  27. data/lib/fabulator/expr/function.rb +52 -0
  28. data/lib/fabulator/expr/if_expr.rb +22 -0
  29. data/lib/fabulator/expr/let_expr.rb +17 -0
  30. data/lib/fabulator/expr/literal.rb +39 -0
  31. data/lib/fabulator/expr/node.rb +216 -0
  32. data/lib/fabulator/expr/node_logic.rb +99 -0
  33. data/lib/fabulator/expr/parser.rb +1470 -0
  34. data/lib/fabulator/expr/path_expr.rb +49 -0
  35. data/lib/fabulator/expr/predicates.rb +45 -0
  36. data/lib/fabulator/expr/statement_list.rb +96 -0
  37. data/lib/fabulator/expr/step.rb +43 -0
  38. data/lib/fabulator/expr/unary_expr.rb +30 -0
  39. data/lib/fabulator/expr/union_expr.rb +21 -0
  40. data/lib/fabulator/template.rb +9 -0
  41. data/lib/fabulator/template/context.rb +51 -0
  42. data/lib/fabulator/template/parse_result.rb +153 -0
  43. data/lib/fabulator/template/parser.rb +17 -0
  44. data/lib/fabulator/template/standard_tags.rb +95 -0
  45. data/lib/fabulator/template/taggable.rb +88 -0
  46. data/lib/fabulator/version.rb +14 -0
  47. data/test/test_fabulator.rb +17 -0
  48. data/test/test_helper.rb +24 -0
  49. data/xslt/form.xsl +2161 -0
  50. metadata +182 -0
@@ -0,0 +1,39 @@
1
+ module Fabulator
2
+ class Context
3
+ attr_accessor :data, :state
4
+
5
+ def initialize
6
+ @state = 'start'
7
+ @data = nil
8
+ end
9
+
10
+ def empty?
11
+ @state = 'start' if @state.nil?
12
+ (@data.nil? || @data.empty?) && @state == 'start'
13
+ end
14
+
15
+ def merge!(d, path=nil)
16
+ return if @data.nil?
17
+ return @data.merge_data(d,path)
18
+ end
19
+
20
+ def clear(path = nil)
21
+ return if @data.nil?
22
+ return @data.clear(path)
23
+ end
24
+
25
+ def context
26
+ { :state => @state, :data => @data }
27
+ end
28
+
29
+ def context=(c)
30
+ @state = c[:state]
31
+ @data = c[:data]
32
+ end
33
+
34
+ def get(p = nil)
35
+ return if @data.nil?
36
+ return @data.get(p)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ require 'fabulator/core/state_machine'
2
+ require 'fabulator/core/constraint'
3
+ require 'fabulator/core/filter'
4
+ require 'fabulator/core/group'
5
+ require 'fabulator/core/parameter'
6
+ require 'fabulator/core/state'
7
+ require 'fabulator/core/transition'
8
+ require 'fabulator/core/actions'
@@ -0,0 +1,514 @@
1
+ require 'fabulator/action_lib'
2
+ require 'fabulator/core/actions/choose'
3
+ require 'fabulator/core/actions/for_each'
4
+ require 'fabulator/core/actions/variables'
5
+
6
+ module Fabulator
7
+ module Core
8
+ module Actions
9
+ class Lib
10
+ include ActionLib
11
+ register_namespace FAB_NS
12
+
13
+ action 'choose', Choose
14
+ action 'for-each', ForEach
15
+ action 'value-of', ValueOf
16
+ action 'value', Value
17
+ action 'while', While
18
+ action 'considering', Considering
19
+ action 'variable', Variable
20
+ action 'if', If
21
+ action 'go-to', Goto
22
+ action 'raise', Raise
23
+ action 'div', Block
24
+ action 'catch', Catch
25
+ action 'super', Super
26
+
27
+ ###
28
+ ### core types
29
+ ###
30
+
31
+ register_type 'boolean', {
32
+ :ops => {
33
+ },
34
+ :to => [
35
+ { :type => [ FAB_NS, 'string' ],
36
+ :weight => 1.0,
37
+ :convert => lambda { |i| i.value ? 'true' : '' }
38
+ },
39
+ { :type => [ FAB_NS, 'numeric' ],
40
+ :weight => 1.0,
41
+ :convert => lambda{ |i| Rational.new(i.value ? 1 : 0, 1) }
42
+ },
43
+ ]
44
+ }
45
+
46
+ register_type 'string', {
47
+ :ops => {
48
+ #:plus => {
49
+ #},
50
+ :minus => {
51
+ :proc => lambda { |a,b| a.split(b).join('')}
52
+ },
53
+ :mpy => {
54
+ :args => [ [ FAB_NS, 'string' ], [ FAB_NS, 'integer' ] ],
55
+ :proc => lambda { |a,b| a * b }
56
+ },
57
+ :lt => { },
58
+ :eq => { },
59
+ },
60
+ :to => [
61
+ { :type => [ FAB_NS, 'boolean' ],
62
+ :weight => 0.0001,
63
+ :convert => lambda { |s| !(s.value.nil? || s.value == '' || s.value =~ /\s*/) }
64
+ },
65
+ { :type => [ FAB_NS, 'html' ],
66
+ :weight => 1.0,
67
+ :convert => lambda { |s| s.value.gsub(/&/, '&amp;').gsub(/</, '&lt;') }
68
+ },
69
+ ],
70
+ }
71
+
72
+ register_type 'uri', {
73
+ :to => [
74
+ { :type => [ FAB_NS, 'string' ],
75
+ :weight => 1.0,
76
+ :convert => lambda { |u| u.get_attribute('namespace').value + u.get_attribute('name').value }
77
+ }
78
+ ]
79
+ }
80
+
81
+ register_type 'numeric', {
82
+ :ops => {
83
+ #:plus => { },
84
+ #:minus => { },
85
+ #:mpy => { },
86
+ #:div => { },
87
+ #:mod => { },
88
+ #:lt => { },
89
+ #:eq => { },
90
+ },
91
+ :to => [
92
+ { :type => [ FAB_NS, 'string' ],
93
+ :weight => 1.0,
94
+ :convert => lambda { |n| (n.value % 1 == 0 ? n.value.to_i : n.value.to_d).to_s }
95
+ },
96
+ { :type => [ FAB_NS, 'boolean' ],
97
+ :weight => 0.0001,
98
+ :convert => lambda { |n| n.value != 0 }
99
+ },
100
+ ],
101
+ }
102
+
103
+ register_type 'expression', {
104
+ }
105
+
106
+ ###
107
+ ### Numeric functions
108
+ ###
109
+
110
+ NUMERIC = [ FAB_NS, 'numeric' ]
111
+
112
+ mapping 'abs' do |ctx, arg|
113
+ arg.value.abs
114
+ end
115
+
116
+ mapping 'ceiling' do |ctx, arg|
117
+ arg.to(NUMERIC).value.to_d.ceil.to_r
118
+ end
119
+
120
+ mapping 'floor' do |ctx, arg|
121
+ arg.to(NUMERIC).value.to_d.floor.to_r
122
+ end
123
+
124
+ reduction 'sum', { :scaling => :log } do |ctx, args|
125
+ zero = ActionLib.find_op(args.first.vtype, :zero)
126
+ if(zero && zero[:proc])
127
+ res = zero[:proc].call(ctx)
128
+ end
129
+ res = 0 if res.nil?
130
+ return [ res ] if args.empty?
131
+
132
+ op = ActionLib.find_op(args.first.vtype, :plus)
133
+
134
+ if op.nil? || op[:proc].nil?
135
+ args.each do |a|
136
+ res = res + a.value
137
+ end
138
+ else
139
+ args.each do |a|
140
+ res = op[:proc].call(res, a)
141
+ end
142
+ end
143
+ [ res ]
144
+ end
145
+
146
+ reduction 'avg' do |ctx, args|
147
+ res = 0.0
148
+ n = 0.0
149
+ args.each do |a|
150
+ res = res + a.value.to_f
151
+ n = n + 1.0
152
+ end
153
+ res = res / n if n > 0
154
+ if res.floor == res
155
+ [ res.to_i ]
156
+ else
157
+ [ res ]
158
+ end
159
+ end
160
+
161
+ reduction 'max', { :scaling => :log } do |ctx, args|
162
+ res = nil
163
+ args.each do |a|
164
+ res = a.to(NUMERIC).value if res.nil? || a.to(NUMERIC).value > res
165
+ end
166
+
167
+ [ctx.root.anon_node(res, NUMERIC)]
168
+ end
169
+
170
+ reduction 'min', { :scaling => :log } do |ctx, args|
171
+ res = nil
172
+ args.each do |a|
173
+ res = a.to(NUMERIC).value if res.nil? || a.to(NUMERIC).value < res
174
+ end
175
+
176
+ [ctx.root.anon_node(res, NUMERIC)]
177
+ end
178
+
179
+ function 'histogram' do |ctx, args|
180
+ acc = { }
181
+ args.flatten.each do |a|
182
+ acc[a.to_s] ||= 0
183
+ acc[a.to_s] = acc[a.to_s] + 1
184
+ end
185
+ acc
186
+ end
187
+
188
+ # TODO: make 'consolidate' a general-purpose function that translates
189
+ # into the consolidation function of reductions
190
+ #
191
+ # the code here is the consolidation function for histogram
192
+ # f:sum is the consolidation function for f:sum
193
+ #
194
+ reduction 'consolidate', { :scaling => :log } do |ctx, args|
195
+ acc = { }
196
+ attrs = { }
197
+ children = { }
198
+ args.each do |a|
199
+ a.children.each do |c|
200
+ acc[c.name] ||= 0
201
+ acc[c.name] = acc[c.name] + c.value
202
+ attrs[c.name] ||= { }
203
+ c.attributes.each do |a|
204
+ attrs[c.name][a.name] ||= [ ]
205
+ attrs[c.name][a.name] << a.value
206
+ end
207
+ children[c.name] ||= [ ]
208
+ children[c.name] += c.children
209
+ end
210
+ end
211
+
212
+ ret = ctx.root.anon_node(nil)
213
+ acc.each_pair do |tok, cnt|
214
+ t = ret.create_child(tok, cnt, [FAB_NS, 'numeric'])
215
+ attrs[tok].each_pair do |a, vs|
216
+ t.set_attribute(a, vs.flatten)
217
+ end
218
+ children[tok].each do |child|
219
+ t.add_child(child.clone)
220
+ end
221
+ end
222
+ ret
223
+ end
224
+
225
+ ###
226
+ ### String functions
227
+ ###
228
+
229
+ STRING = [ FAB_NS, 'string' ]
230
+ BOOLEAN = [ FAB_NS, 'boolean' ]
231
+
232
+ #
233
+ # f:concat(node-set) => node
234
+ #
235
+ reduction 'concat', { :scaling => :log } do |ctx, args|
236
+ return '' if args.empty?
237
+ [ args.collect{ |a| a.value.to_s}.join('') ]
238
+ end
239
+
240
+ #
241
+ # f:string-join(node-set, joiner) => node
242
+ #
243
+ function 'string-join' do |ctx, args|
244
+ joiner = args[1].first.value.to_s
245
+ [ args[0].collect{|a| a.value.to_s }.join(joiner) ]
246
+ end
247
+
248
+ #
249
+ # f:substring(node-set, begin)
250
+ # f:substring(node-set, begin, length)
251
+ #
252
+ function 'substring' do |ctx, args|
253
+ first = args[1].first.value
254
+ if args.size == 3
255
+ last = args[2].first.value
256
+ return [ args[0].collect{ |src| src.value.to_s.substr(first, last) } ]
257
+ else
258
+ return [ args[0].collect{ |src| src.value.to_s.substr(first) } ]
259
+ end
260
+ end
261
+
262
+ #
263
+ # f:string-length(node-list) => node-list
264
+ #
265
+ mapping 'string-length' do |ctx, arg|
266
+ arg.to_s.length
267
+ end
268
+
269
+ mapping 'normalize-space' do |ctx, arg|
270
+ arg.to_s.gsub(/^\s+/, '').gsub(/\s+$/,'').gsub(/\s+/, ' ')
271
+ end
272
+
273
+ mapping 'upper-case' do |ctx, arg|
274
+ arg.to_s.upcase
275
+ end
276
+
277
+ mapping 'lower-case' do |ctx, arg|
278
+ arg.to_s.downcase
279
+ end
280
+
281
+ function 'split' do |ctx, args|
282
+ div = args[1].first.to_s
283
+ args[0].collect{ |a| a.to_s.split(div) }
284
+ end
285
+
286
+ function 'contains' do |ctx, args|
287
+ tgt = (args[1].first.to_s rescue '')
288
+ return args[0].collect{ |a| (a.to_s.include?(tgt) rescue false) }
289
+ end
290
+
291
+ function 'starts-with' do |ctx, args|
292
+ tgt = (args[1].first.to_s rescue '')
293
+ tgt_len = tgt.size - 1
294
+ return args[0].collect{ |a| (a.to_s[0,tgt_len] == tgt rescue false) }
295
+ end
296
+
297
+ function 'ends-with' do |ctx, args|
298
+ tgt = (args[1].first.to_s rescue '').reverse
299
+ tgt_len = tgt.size
300
+ return args[0].collect{ |a| (a.to_s[-tgt_len,-1] == tgt rescue false) }
301
+ end
302
+
303
+ function 'substring-before' do |ctx, args|
304
+ tgt = (args[1].first.to_s rescue '')
305
+ return [ '' ] if tgt == ''
306
+
307
+ return args[0].collect{ |a| (a.value.to_s.split(tgt,2))[0] }
308
+ end
309
+
310
+ function 'substring-after' do |ctx, args|
311
+ tgt = (args[1].first.to_s rescue '')
312
+
313
+ return args[0].collect{ |a| a.to_s } if tgt == ''
314
+
315
+ return args[0].collect{ |a| a.to_s.include?(tgt) ? (a.to_s.split(tgt))[-1] : "" }
316
+ end
317
+
318
+
319
+ ###
320
+ ### Regexes
321
+ ###
322
+
323
+ function 'keep' do |ctx, args|
324
+ # args[0] - strings to operate on
325
+ # args[1] - char classes to keep: alpha, numeric, space, punctuation, control
326
+ # we replace with 'space' if no args[2]
327
+ replacement = args.size > 2 ? args[2].first.to_s : ' '
328
+ classes = args[1].collect { |a|
329
+ case a.to_s
330
+ when 'alpha': 'a-zA-Z'
331
+ when 'lower': 'a-z'
332
+ when 'upper': 'A-Z'
333
+ when 'numeric': '0-9'
334
+ when 'space': ' '
335
+ when 'punctuation': ''
336
+ when 'control': ''
337
+ else ''
338
+ end
339
+ }.join('')
340
+
341
+ args[0].collect{ |a|
342
+ a.to_s.gsub(/[^#{classes}]+/, replacement)
343
+ }
344
+ end
345
+
346
+
347
+ ###
348
+ ### Boolean
349
+ ###
350
+
351
+ function 'true', BOOLEAN do |ctx, args|
352
+ return [ ctx.root.anon_node( true, [ FAB_NS, 'boolean' ] ) ]
353
+ end
354
+
355
+ function 'false', BOOLEAN do |ctx, args|
356
+ return [ ctx.root.anon_node( false, [ FAB_NS, 'boolean' ] ) ]
357
+ end
358
+
359
+ mapping 'not' do |ctx, arg|
360
+ !arg.value
361
+ end
362
+
363
+ ###
364
+ ### data node functions
365
+ ###
366
+
367
+ mapping 'name' do |ctx, arg|
368
+ arg.name || ''
369
+ end
370
+
371
+ mapping 'root' do |ctx, arg|
372
+ arg.root
373
+ end
374
+
375
+ mapping 'lang' do |ctx, arg|
376
+ # we want to track language for rdf purposes?
377
+ end
378
+
379
+ mapping 'path' do |ctx, arg|
380
+ arg.path
381
+ end
382
+
383
+ mapping 'dump' do |ctx, arg|
384
+ YAML::dump(
385
+ arg.is_a?(Array) ? arg.collect{ |a| a.to_h } : arg.to_h
386
+ )
387
+ end
388
+
389
+ mapping 'eval' do |ctx, arg|
390
+ p = Fabulator::Expr::Parser.new
391
+ e = arg.to_s
392
+ pe = e.nil? ? nil : p.parse(e,ctx)
393
+ pe.nil? ? [] : pe.run(ctx)
394
+ end
395
+
396
+ ###
397
+ ### Sequences
398
+ ###
399
+
400
+ mapping 'empty' do |ctx, arg|
401
+ arg.nil? || !arg.is_a?(Array) || arg.empty?
402
+ end
403
+
404
+ mapping 'exists' do |ctx, arg|
405
+ !(arg.nil? || !arg.is_a?(Array) || arg.empty?)
406
+ end
407
+
408
+ function 'reverse' do |ctx, args|
409
+ args.flatten.reverse
410
+ end
411
+
412
+ mapping 'zero-or-one' do |ctx, arg|
413
+ arg.is_a?(Array) && arg.size <= 1
414
+ end
415
+
416
+ mapping 'one-or-more' do |ctx, arg|
417
+ arg.is_a?(Array) && arg.size >= 1
418
+ end
419
+
420
+ mapping 'zero-or-one' do |ctx, arg|
421
+ arg.is_a?(Array) && arg.size == 1
422
+ end
423
+
424
+ reduction 'count' do |ctx, args|
425
+ args.size
426
+ end
427
+
428
+ ###
429
+ ### Context
430
+ ###
431
+
432
+ function 'position', NUMERIC do |ctx, args|
433
+ ctx.position
434
+ end
435
+
436
+ function 'last', BOOLEAN do |ctx, args|
437
+ ctx.last?
438
+ end
439
+
440
+ function 'first', BOOLEAN do |ctx, args|
441
+ ctx.position == 1
442
+ end
443
+
444
+ ###
445
+ ### URIs
446
+ ###
447
+
448
+ mapping 'uri-prefix' do |ctx, arg|
449
+ res = [ ]
450
+ prefix = arg.to_s
451
+ # resolve prefix to href
452
+ if ctx.get_ns(prefix)
453
+ return ctx.root.anon_node( ctx.get_ns(prefix), [FAB_NS, 'string'])
454
+ else
455
+ return []
456
+ end
457
+ end
458
+
459
+
460
+ ###
461
+ ### Filters
462
+ ###
463
+
464
+ filter 'trim' do |c|
465
+ v = c.root.value
466
+ if !v.nil?
467
+ v.chomp!
468
+ v.gsub!(/^\s*/,'')
469
+ v.gsub!(/\s*$/,'')
470
+ v.gsub!(/\s+/, ' ')
471
+ end
472
+ v
473
+ end
474
+
475
+ filter 'downcase' do |c|
476
+ v = c.root.value
477
+ if !v.nil?
478
+ v.downcase!
479
+ c.root.value = v
480
+ end
481
+ c
482
+ end
483
+
484
+ filter 'upcase' do |c|
485
+ v = c.root.value
486
+ if !v.nil?
487
+ v.upcase!
488
+ c.root.value = v
489
+ end
490
+ c
491
+ end
492
+
493
+ filter 'integer' do |c|
494
+ v = c.root.value
495
+ if !v.nil?
496
+ v = v.to_i.to_s
497
+ c.root.value = v
498
+ end
499
+ c
500
+ end
501
+
502
+ filter 'decimal' do |c|
503
+ v = c.root.value
504
+ if !v.nil?
505
+ v = v.to_f.to_s
506
+ c.root.value = v
507
+ end
508
+ c
509
+ end
510
+
511
+ end
512
+ end
513
+ end
514
+ end