fabulator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +41 -0
- data/README.rdoc +61 -0
- data/Rakefile +54 -0
- data/lib/fabulator.rb +5 -0
- data/lib/fabulator/action.rb +46 -0
- data/lib/fabulator/action_lib.rb +438 -0
- data/lib/fabulator/context.rb +39 -0
- data/lib/fabulator/core.rb +8 -0
- data/lib/fabulator/core/actions.rb +514 -0
- data/lib/fabulator/core/actions/choose.rb +167 -0
- data/lib/fabulator/core/actions/for_each.rb +105 -0
- data/lib/fabulator/core/actions/variables.rb +52 -0
- data/lib/fabulator/core/constraint.rb +117 -0
- data/lib/fabulator/core/filter.rb +41 -0
- data/lib/fabulator/core/group.rb +123 -0
- data/lib/fabulator/core/parameter.rb +128 -0
- data/lib/fabulator/core/state.rb +91 -0
- data/lib/fabulator/core/state_machine.rb +153 -0
- data/lib/fabulator/core/transition.rb +164 -0
- data/lib/fabulator/expr.rb +37 -0
- data/lib/fabulator/expr/axis.rb +133 -0
- data/lib/fabulator/expr/axis_descendent_or_self.rb +26 -0
- data/lib/fabulator/expr/bin_expr.rb +178 -0
- data/lib/fabulator/expr/context.rb +368 -0
- data/lib/fabulator/expr/for_expr.rb +74 -0
- data/lib/fabulator/expr/function.rb +52 -0
- data/lib/fabulator/expr/if_expr.rb +22 -0
- data/lib/fabulator/expr/let_expr.rb +17 -0
- data/lib/fabulator/expr/literal.rb +39 -0
- data/lib/fabulator/expr/node.rb +216 -0
- data/lib/fabulator/expr/node_logic.rb +99 -0
- data/lib/fabulator/expr/parser.rb +1470 -0
- data/lib/fabulator/expr/path_expr.rb +49 -0
- data/lib/fabulator/expr/predicates.rb +45 -0
- data/lib/fabulator/expr/statement_list.rb +96 -0
- data/lib/fabulator/expr/step.rb +43 -0
- data/lib/fabulator/expr/unary_expr.rb +30 -0
- data/lib/fabulator/expr/union_expr.rb +21 -0
- data/lib/fabulator/template.rb +9 -0
- data/lib/fabulator/template/context.rb +51 -0
- data/lib/fabulator/template/parse_result.rb +153 -0
- data/lib/fabulator/template/parser.rb +17 -0
- data/lib/fabulator/template/standard_tags.rb +95 -0
- data/lib/fabulator/template/taggable.rb +88 -0
- data/lib/fabulator/version.rb +14 -0
- data/test/test_fabulator.rb +17 -0
- data/test/test_helper.rb +24 -0
- data/xslt/form.xsl +2161 -0
- metadata +182 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
module Fabulator
|
2
|
+
module Core
|
3
|
+
class Group < Fabulator::Action
|
4
|
+
attr_accessor :name, :params, :tags
|
5
|
+
|
6
|
+
namespace Fabulator::FAB_NS
|
7
|
+
|
8
|
+
has_select
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@params = [ ]
|
12
|
+
@constraints = [ ]
|
13
|
+
@filters = [ ]
|
14
|
+
@required_params = [ ]
|
15
|
+
@tags = [ ]
|
16
|
+
end
|
17
|
+
|
18
|
+
def compile_xml(xml, context)
|
19
|
+
super
|
20
|
+
xml.each_element do |e|
|
21
|
+
next unless e.namespaces.namespace.href == FAB_NS
|
22
|
+
|
23
|
+
case e.name
|
24
|
+
when 'param':
|
25
|
+
v = Parameter.new.compile_xml(e,@context)
|
26
|
+
@params << v
|
27
|
+
@required_params = @required_params + v.names if v.required?
|
28
|
+
when 'group':
|
29
|
+
v = Group.new.compile_xml(e,@context)
|
30
|
+
@params << v
|
31
|
+
@required_params = @required_params + v.required_params.collect{ |n| (@name + '/' + n).gsub(/\/+/, '/') }
|
32
|
+
when 'constraint':
|
33
|
+
@constraints << Constraint.new.compile_xml(e,@context)
|
34
|
+
when 'filter':
|
35
|
+
@filters << Filter.new.compile_xml(e,@context)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def apply_filters(context)
|
42
|
+
filtered = [ ]
|
43
|
+
|
44
|
+
@context.with(context) do |ctx|
|
45
|
+
|
46
|
+
self.get_context(ctx).each do |root|
|
47
|
+
@params.each do |param|
|
48
|
+
@filters.each do |f|
|
49
|
+
filtered = filtered + f.apply_filter(ctx.with_root(root))
|
50
|
+
end
|
51
|
+
filtered = filtered + param.apply_filters(ctx.with_root(root))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
filtered.uniq
|
56
|
+
end
|
57
|
+
|
58
|
+
def apply_constraints(context)
|
59
|
+
res = { :missing => [], :invalid => [], :valid => [], :messages => [] }
|
60
|
+
passed = [ ]
|
61
|
+
failed = [ ]
|
62
|
+
@context.with(context) do |ctx|
|
63
|
+
self.get_context(ctx).each do |root|
|
64
|
+
@params.each do |param|
|
65
|
+
@constraints.each do |c|
|
66
|
+
r = c.test_constraint(ctx.with_root(root))
|
67
|
+
passed += r[0]
|
68
|
+
failed += r[1]
|
69
|
+
end
|
70
|
+
p_res = param.apply_constraints(ctx.with_root(root))
|
71
|
+
res[:messages] += p_res[:messages]
|
72
|
+
failed += p_res[:invalid]
|
73
|
+
passed += p_res[:valid]
|
74
|
+
res[:missing] += p_res[:missing]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
res[:invalid] = failed.uniq
|
79
|
+
res[:valid] = (passed - failed).uniq
|
80
|
+
res[:messages].uniq!
|
81
|
+
res[:missing] = (res[:missing] - passed).uniq
|
82
|
+
res
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def get_context(context)
|
87
|
+
return [ context.root ] if @select.nil?
|
88
|
+
ret = [ ]
|
89
|
+
context.in_context do |ctx|
|
90
|
+
ret = @select.run(ctx)
|
91
|
+
end
|
92
|
+
ret
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_constraints(context)
|
96
|
+
passed = [ ]
|
97
|
+
failed = [ ]
|
98
|
+
@context.with(context) do |ctx|
|
99
|
+
roots = self.get_context(ctx)
|
100
|
+
roots.each do |root|
|
101
|
+
@params.each do |param|
|
102
|
+
p_ctx = param.get_context(ctx.with_root(root))
|
103
|
+
if !p_ctx.nil? && !p_ctx.empty?
|
104
|
+
p_ctx.each do |p|
|
105
|
+
@constraints.each do |c|
|
106
|
+
r = c.test_constraint(ctx.with_root(p))
|
107
|
+
passed += r[0]
|
108
|
+
failed += r[1]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
if failed.empty?
|
116
|
+
return [ passed.uniq, [] ]
|
117
|
+
else
|
118
|
+
return [ (passed - failed).uniq, failed ]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Fabulator
|
2
|
+
module Core
|
3
|
+
class Parameter < Fabulator::Action
|
4
|
+
attr_accessor :name
|
5
|
+
|
6
|
+
namespace Fabulator::FAB_NS
|
7
|
+
|
8
|
+
attribute :name, :eval => false, :static => true
|
9
|
+
attribute :required, :static => true, :default => 'false'
|
10
|
+
|
11
|
+
def required?
|
12
|
+
@required
|
13
|
+
end
|
14
|
+
|
15
|
+
def param_names
|
16
|
+
[ @name ]
|
17
|
+
end
|
18
|
+
|
19
|
+
def compile_xml(xml, context)
|
20
|
+
super
|
21
|
+
|
22
|
+
@constraints = [ ]
|
23
|
+
@filters = [ ]
|
24
|
+
|
25
|
+
case @required.downcase
|
26
|
+
when 'yes':
|
27
|
+
@required = true
|
28
|
+
when 'true':
|
29
|
+
@required = true
|
30
|
+
when 'no':
|
31
|
+
@required = false
|
32
|
+
when 'false':
|
33
|
+
@required = false
|
34
|
+
end
|
35
|
+
|
36
|
+
xml.each_element do |e|
|
37
|
+
next unless e.namespaces.namespace.href == FAB_NS
|
38
|
+
case e.name
|
39
|
+
when 'constraint':
|
40
|
+
@constraints << Constraint.new.compile_xml(e, @context)
|
41
|
+
when 'filter':
|
42
|
+
@filters << Filter.new.compile_xml(e, @context)
|
43
|
+
when 'value':
|
44
|
+
@constraints << Constraint.new.compile_xml(e, @context)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_context(context)
|
51
|
+
context = [ context ] unless context.is_a?(Array)
|
52
|
+
context.collect{ |c| c.traverse_path(@name) }.flatten
|
53
|
+
end
|
54
|
+
|
55
|
+
def apply_filters(context)
|
56
|
+
filtered = [ ]
|
57
|
+
@context.with(context) do |ctx|
|
58
|
+
@filters.each do |f|
|
59
|
+
self.get_context(ctx).each do |cc|
|
60
|
+
filtered = filtered + f.run(ctx.with_root(cc))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
filtered
|
65
|
+
end
|
66
|
+
|
67
|
+
def apply_constraints(context)
|
68
|
+
res = { :missing => [], :invalid => [], :valid => [], :messages => [] }
|
69
|
+
ctx = @context.merge(context)
|
70
|
+
items = self.get_context(ctx)
|
71
|
+
#name = context.attribute(FAB_NS, 'name')
|
72
|
+
if items.empty?
|
73
|
+
if required?
|
74
|
+
res[:missing] = [ (ctx.root.path + '/' + @name).gsub(/\/+/, '/') ]
|
75
|
+
end
|
76
|
+
elsif @constraints.empty? # make sure something exists
|
77
|
+
res[:valid] = items
|
78
|
+
elsif @all_constraints
|
79
|
+
@constraints.each do |c|
|
80
|
+
items.each do |item|
|
81
|
+
r = c.test_constraint(ctx.with_root(i))
|
82
|
+
res[:valid] += r[0]
|
83
|
+
if !r[1].empty?
|
84
|
+
res[:invalid] += r[1]
|
85
|
+
res[:messages] += r[1].collect{ |i| c.error_message(i) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
else
|
90
|
+
items.each do |item|
|
91
|
+
passed = @constraints.select {|c| c.test_constraint(ctx.with_root(item))[1].empty? }
|
92
|
+
if passed.empty?
|
93
|
+
res[:invalid] << item
|
94
|
+
res[:messages] += @constraints.collect { |c| c.error_message(item) }
|
95
|
+
else
|
96
|
+
res[:valid] << item
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
return res
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_constraints(context)
|
105
|
+
@context.with(context) do |ctx|
|
106
|
+
me = ctx.traverse_path(@name)
|
107
|
+
return [ [ me.collect{ |m| m.path } ], [] ] if @constraints.empty?
|
108
|
+
paths = [ [], [] ]
|
109
|
+
if @all_constraints
|
110
|
+
@constraints.each do |c|
|
111
|
+
p = c.test_constraints(ctx, me)
|
112
|
+
paths[0] += p[0]
|
113
|
+
paths[1] += p[1]
|
114
|
+
end
|
115
|
+
return [ (paths[0] - paths[1]).uniq, paths[1].uniq ]
|
116
|
+
else
|
117
|
+
@constraints.each do |c|
|
118
|
+
p = c.test_constraints(ctx, me)
|
119
|
+
paths[0] += p[0]
|
120
|
+
paths[1] += p[1]
|
121
|
+
end
|
122
|
+
return [ paths[0].uniq, (paths[1] - paths[0]).uniq ]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Fabulator
|
2
|
+
module Core
|
3
|
+
class State < Fabulator::Action
|
4
|
+
attr_accessor :name, :transitions
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@transitions = []
|
8
|
+
@pre_actions = nil
|
9
|
+
@post_actions = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
namespace Fabulator::FAB_NS
|
13
|
+
attribute :name, :static => true
|
14
|
+
|
15
|
+
|
16
|
+
def compile_xml(xml, ctx)
|
17
|
+
super
|
18
|
+
|
19
|
+
inheriting = !@transitions.empty?
|
20
|
+
xml.each_element do |e|
|
21
|
+
next unless e.namespaces.namespace.href == FAB_NS
|
22
|
+
case e.name
|
23
|
+
when 'goes-to':
|
24
|
+
if inheriting
|
25
|
+
target = e.attributes.get_attribute_ns(FAB_NS, 'view').value
|
26
|
+
tags = (e.attributes.get_attribute_ns(FAB_NS, 'tag').value rescue '').split(/\s+/)
|
27
|
+
old = @transitions.collect{ |t| t.state == target && (tags.empty? || !(tags & t.tags).empty?)}
|
28
|
+
if old.empty?
|
29
|
+
@transitions << Transition.new.compile_xml(e,@context)
|
30
|
+
else
|
31
|
+
old.each do |t|
|
32
|
+
t.compile_xml(e,@context)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
@transitions << Transition.new.compile_xml(e, @context)
|
37
|
+
end
|
38
|
+
when 'before':
|
39
|
+
ActionLib.with_super(@pre_actions) do
|
40
|
+
t = @context.compile_actions(e)
|
41
|
+
@pre_actions = t if @pre_actions.nil? || !t.is_noop?
|
42
|
+
end
|
43
|
+
when 'after':
|
44
|
+
ActionLib.with_super(@post_actions) do
|
45
|
+
t = @context.compile_actions(e)
|
46
|
+
@post_actions = t if @post_actions.nil? || !t.is_noop?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def states
|
54
|
+
@transitions.map { |t| t.state }.uniq
|
55
|
+
end
|
56
|
+
|
57
|
+
def select_transition(context,params)
|
58
|
+
# we need hypthetical variables here :-/
|
59
|
+
best_match = nil
|
60
|
+
@context.with(context) do |ctx|
|
61
|
+
best_match = nil
|
62
|
+
@transitions.each do |t|
|
63
|
+
res = t.validate_params(ctx,params)
|
64
|
+
if res[:missing].empty? && res[:messages].empty? && res[:unknown].empty? && res[:invalid].empty?
|
65
|
+
res[:transition] = t
|
66
|
+
end
|
67
|
+
if best_match.nil? || res[:score] > best_match[:score]
|
68
|
+
best_match = res
|
69
|
+
best_match[:transition] = t
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return best_match
|
74
|
+
end
|
75
|
+
|
76
|
+
def run_pre(context)
|
77
|
+
# do queries, denials, assertions in the order given
|
78
|
+
ctx = context.class.new(@context, context)
|
79
|
+
@pre_actions.run(ctx) unless @pre_actions.nil?
|
80
|
+
return []
|
81
|
+
end
|
82
|
+
|
83
|
+
def run_post(context)
|
84
|
+
# do queries, denials, assertions in the order given
|
85
|
+
ctx = context.class.new(@context, context)
|
86
|
+
@post_actions.run(ctx) unless @post_actions.nil?
|
87
|
+
return []
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'xml/libxml'
|
3
|
+
|
4
|
+
module Fabulator
|
5
|
+
FAB_NS='http://dh.tamu.edu/ns/fabulator/1.0#'
|
6
|
+
RDFS_NS = 'http://www.w3.org/2000/01/rdf-schema#'
|
7
|
+
RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
|
8
|
+
RDFA_NS = 'http://dh.tamu.edu/ns/fabulator/rdf/1.0#'
|
9
|
+
|
10
|
+
class StateChangeException < Exception
|
11
|
+
end
|
12
|
+
|
13
|
+
module Core
|
14
|
+
|
15
|
+
class StateMachine
|
16
|
+
attr_accessor :states, :missing_params, :errors, :namespaces, :updated_at
|
17
|
+
attr_accessor :state
|
18
|
+
|
19
|
+
def compile_xml(xml, context = nil, callbacks = { })
|
20
|
+
# /statemachine/states
|
21
|
+
@states ||= { }
|
22
|
+
@state = 'start'
|
23
|
+
|
24
|
+
if xml.is_a?(String)
|
25
|
+
xml = LibXML::XML::Document.string xml
|
26
|
+
end
|
27
|
+
|
28
|
+
if context.nil?
|
29
|
+
@context = Fabulator::Expr::Context.new.merge(xml.root)
|
30
|
+
else
|
31
|
+
@context = context.merge(xml.root)
|
32
|
+
end
|
33
|
+
|
34
|
+
ActionLib.with_super(@actions) do
|
35
|
+
p_actions = @context.compile_actions(xml.root)
|
36
|
+
@actions = p_actions if @actions.nil? || !p_actions.is_noop?
|
37
|
+
end
|
38
|
+
|
39
|
+
xml.root.each_element do |child|
|
40
|
+
next unless child.namespaces.namespace.href == FAB_NS
|
41
|
+
case child.name
|
42
|
+
when 'view':
|
43
|
+
nom = (child.attributes.get_attribute_ns(FAB_NS, 'name').value rescue nil)
|
44
|
+
if !@states[nom].nil?
|
45
|
+
@states[nom].compile_xml(child, @context)
|
46
|
+
else
|
47
|
+
@states[nom] = State.new.compile_xml(child, @context)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if @states.empty?
|
53
|
+
s = State.new
|
54
|
+
s.name = 'start'
|
55
|
+
@states['start'] = s
|
56
|
+
end
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def clone
|
61
|
+
YAML::load( YAML::dump( self ) )
|
62
|
+
end
|
63
|
+
|
64
|
+
def namespaces
|
65
|
+
@context.ns
|
66
|
+
end
|
67
|
+
|
68
|
+
def init_context(c)
|
69
|
+
@context.root = c.root
|
70
|
+
begin
|
71
|
+
@actions.run(@context)
|
72
|
+
rescue Fabulator::StateChangeException => e
|
73
|
+
@state = e
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def context
|
78
|
+
{ :data => @context.root, :state => @state }
|
79
|
+
end
|
80
|
+
|
81
|
+
def fabulator_context
|
82
|
+
@context
|
83
|
+
end
|
84
|
+
|
85
|
+
def context=(c)
|
86
|
+
if c.is_a?(Fabulator::Expr::Context)
|
87
|
+
@context = c
|
88
|
+
elsif c.is_a?(Fabulator::Expr::Node)
|
89
|
+
@context.root = c
|
90
|
+
elsif c.is_a?(Hash)
|
91
|
+
@context.root = c[:data]
|
92
|
+
@state = c[:state]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def run(params)
|
97
|
+
current_state = @states[@state]
|
98
|
+
return if current_state.nil?
|
99
|
+
# select transition
|
100
|
+
# possible get some errors
|
101
|
+
# run transition, and move to new state as needed
|
102
|
+
@context.in_context do |ctx|
|
103
|
+
self.run_transition(current_state.select_transition(@context, params))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def run_transition(best_transition)
|
108
|
+
return if best_transition.nil? || best_transition.empty?
|
109
|
+
current_state = @states[@state]
|
110
|
+
t = best_transition[:transition]
|
111
|
+
@missing_params = best_transition[:missing]
|
112
|
+
@errors = best_transition[:messages]
|
113
|
+
if @missing_params.empty? && @errors.empty?
|
114
|
+
@state = t.state
|
115
|
+
# merge valid and context
|
116
|
+
best_transition[:valid].sort_by { |a| a.path.length }.each do |item|
|
117
|
+
p = item.path.gsub(/^[^:]+::/, '').split('/') - [ '' ]
|
118
|
+
n = @context.traverse_path(p, true).first
|
119
|
+
n.prune
|
120
|
+
n.copy(item)
|
121
|
+
end
|
122
|
+
# run_post of state we're leaving
|
123
|
+
begin
|
124
|
+
current_state.run_post(@context)
|
125
|
+
t.run(@context)
|
126
|
+
# run_pre for the state we're going to
|
127
|
+
new_state = @states[@state]
|
128
|
+
new_state.run_pre(@context) if !new_state.nil?
|
129
|
+
rescue Fabulator::StateChangeException => e # catch state change
|
130
|
+
new_state = @states[e]
|
131
|
+
begin
|
132
|
+
if !new_state.nil?
|
133
|
+
@state = new_state.name
|
134
|
+
new_state.run_pre(@context)
|
135
|
+
end
|
136
|
+
rescue Fabulator::StateChangeException => e
|
137
|
+
new_state = @states[e]
|
138
|
+
retry
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def data
|
145
|
+
@context.root
|
146
|
+
end
|
147
|
+
|
148
|
+
def state_names
|
149
|
+
(@states.keys.map{ |k| @states[k].states }.flatten + @states.keys).uniq
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|