fabulator 0.0.1

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