xforms 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in xforms.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,94 @@
1
+ module XForms
2
+
3
+ class ::Object
4
+ def dyn_value; self; end
5
+ end
6
+
7
+ class XPathRef
8
+ attr_accessor :form
9
+ attr_accessor :path
10
+
11
+ def initialize(form, path)
12
+ @form = form
13
+ @path = path
14
+ end
15
+
16
+ def dyn_value
17
+ form.model_instance.xpath(path, form.namespaces, self)
18
+ end
19
+
20
+ def itext id
21
+ form.itext[I18n.locale.to_s][id] || ''
22
+ end
23
+ end
24
+
25
+ module Label
26
+ attr_accessor :label
27
+
28
+ def label
29
+ @label.dyn_value
30
+ end
31
+ end
32
+
33
+ class Control
34
+ attr_accessor :form
35
+ attr_accessor :binding
36
+ attr_accessor :readonly
37
+ attr_accessor :constraint
38
+ attr_accessor :constraint_message
39
+ attr_accessor :relevant
40
+ include Label
41
+
42
+ def initialize(form)
43
+ @form = form
44
+ @readonly = false
45
+ end
46
+
47
+ def value=(value)
48
+ form.model_instance.xpath(binding).first.children = value
49
+ end
50
+
51
+ def value
52
+ form.model_instance.xpath(binding).first.inner_text
53
+ end
54
+
55
+ def readonly
56
+ @readonly.dyn_value
57
+ end
58
+
59
+ def valid?
60
+ return true unless constraint
61
+ node = form.model_instance.xpath(binding).first
62
+ node.xpath(constraint) == true
63
+ end
64
+
65
+ def relevant?
66
+ return true unless @relevant
67
+ @relevant.dyn_value
68
+ end
69
+ end
70
+
71
+ class Input < Control
72
+ end
73
+
74
+ class Select1 < Control
75
+ attr_accessor :items
76
+
77
+ def initialize(form)
78
+ super
79
+ @items = []
80
+ end
81
+ end
82
+
83
+ class Item
84
+ attr_accessor :form
85
+ attr_accessor :value
86
+ include Label
87
+
88
+ def initialize(form, label = nil, value = nil)
89
+ @form = form
90
+ @label = label
91
+ @value = value
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,23 @@
1
+ module XForms
2
+ class Form
3
+ attr_accessor :model_instance
4
+ attr_accessor :itext
5
+ attr_accessor :controls
6
+ attr_accessor :namespaces
7
+
8
+ def initialize
9
+ @controls = []
10
+ @namespaces = {}
11
+ end
12
+
13
+ def self.parse_file(path)
14
+ File.open(path, 'r') do |f|
15
+ parse f
16
+ end
17
+ end
18
+
19
+ def self.parse(input)
20
+ FormParser.new(input).parse
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,162 @@
1
+ require 'nokogiri'
2
+
3
+ module XForms
4
+ class FormParser
5
+ XFORMS_NS = 'http://www.w3.org/2002/xforms'
6
+
7
+ def initialize(input)
8
+ @reader = Nokogiri::XML::Reader(input)
9
+ @form = Form.new
10
+ @bindings = {}
11
+ end
12
+
13
+ def parse
14
+ while read
15
+ if is_xforms_element?
16
+ parse_xforms_element
17
+ end
18
+ end
19
+ @form
20
+ end
21
+
22
+ private
23
+
24
+ def parse_xforms_element
25
+ case @reader.local_name
26
+ when 'model' then parse_model
27
+ when 'input' then parse_input
28
+ when 'select1' then parse_select1
29
+ end
30
+ end
31
+
32
+ def parse_model
33
+ read_descendants do
34
+ if is_xforms_element? 'instance'
35
+ @form.model_instance = Nokogiri::XML(@reader.inner_xml)
36
+ elsif is_xforms_element? 'bind'
37
+ parse_bind
38
+ elsif is_element? 'itext'
39
+ parse_itext
40
+ end
41
+ end
42
+ end
43
+
44
+ def parse_bind
45
+ @bindings[@reader.attribute('id')] = {
46
+ :nodeset => @reader.attribute('nodeset'),
47
+ :readonly => @reader.attribute('readonly'),
48
+ :constraint => @reader.attribute('constraint'),
49
+ :constraint_message => @reader.attribute("#{jr_prefix}:constraintMsg"),
50
+ :relevant => @reader.attribute('relevant')
51
+ }
52
+ end
53
+
54
+ def jr_prefix
55
+ ns = @form.namespaces.key('http://openrosa.org/javarosa')
56
+ ns.split(':').last if ns
57
+ end
58
+
59
+ def parse_input
60
+ parse_control XForms::Input
61
+ end
62
+
63
+ def parse_select1
64
+ parse_control XForms::Select1 do |select1|
65
+ if is_xforms_element? 'item'
66
+ select1.items << parse_item
67
+ end
68
+ end
69
+ end
70
+
71
+ def parse_item
72
+ item = XForms::Item.new @form
73
+ read_descendants do
74
+ if is_xforms_element? 'label'
75
+ item.label = parse_label
76
+ elsif is_xforms_element? 'value'
77
+ item.value = read_inner_text
78
+ end
79
+ end
80
+ item
81
+ end
82
+
83
+ def parse_control type
84
+ @form.controls << control = type.new(@form)
85
+
86
+ if ref = @reader.attribute('ref')
87
+ control.binding = ref
88
+ elsif bind = @reader.attribute('bind')
89
+ binding = @bindings[bind]
90
+ control.binding = binding[:nodeset]
91
+ control.readonly = XPathRef.new(@form, binding[:readonly]) if binding[:readonly]
92
+ control.constraint = binding[:constraint]
93
+ control.constraint_message = binding[:constraint_message]
94
+ control.relevant = XPathRef.new(@form, binding[:relevant]) if binding[:relevant]
95
+ end
96
+
97
+ read_descendants do
98
+ if is_xforms_element? 'label'
99
+ control.label = parse_label
100
+ elsif block_given?
101
+ yield control
102
+ end
103
+ end
104
+ end
105
+
106
+ def parse_label
107
+ if @reader.attribute('ref')
108
+ XPathRef.new @form, @reader.attribute('ref')
109
+ else
110
+ read_inner_text
111
+ end
112
+ end
113
+
114
+ def parse_itext
115
+ @form.itext = Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = {} } }
116
+ read_descendants do
117
+ if is_element? 'translation'
118
+ lang = @reader.attribute('lang')
119
+ read_descendants do
120
+ if is_element? 'text'
121
+ id = @reader.attribute('id')
122
+ read_descendants do
123
+ @form.itext[lang][id] = read_inner_text if is_element? 'value'
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ def read
132
+ node = @reader.read
133
+ @form.namespaces.merge! node.namespaces if node
134
+ node
135
+ end
136
+
137
+ def read_inner_text
138
+ read_descendants do
139
+ if @reader.node_type == Nokogiri::XML::Reader::TYPE_TEXT
140
+ return @reader.value
141
+ end
142
+ end
143
+ end
144
+
145
+ def read_descendants
146
+ unless @reader.self_closing?
147
+ current_depth = @reader.depth
148
+ while read && @reader.depth > current_depth
149
+ yield
150
+ end
151
+ end
152
+ end
153
+
154
+ def is_element?(name, ns = nil)
155
+ @reader.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT && @reader.namespace_uri == ns && (name.nil? || @reader.local_name == name)
156
+ end
157
+
158
+ def is_xforms_element?(name = nil)
159
+ is_element? name, XFORMS_NS
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,3 @@
1
+ module XForms
2
+ VERSION = "0.0.1"
3
+ end
data/lib/xforms.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "xforms/control"
2
+ require "xforms/form"
3
+ require "xforms/form_parser"
4
+ require "xforms/version"
5
+ require 'nokogiri'
6
+ require 'i18n'
7
+
8
+ module XForms
9
+ # Your code goes here...
10
+ end
@@ -0,0 +1,52 @@
1
+ <?xml version='1.0' encoding='utf-8'?>
2
+ <xform xmlns:xf="http://www.w3.org/2002/xforms" xmlns:x="http://tempuri.org/" xmlns:jr="http://openrosa.org/javarosa">
3
+ <xf:model>
4
+ <xf:instance>
5
+ <x:data>
6
+ <x:foo>123</x:foo>
7
+ <x:bar />
8
+ <x:baz />
9
+ </x:data>
10
+ </xf:instance>
11
+
12
+ <xf:bind id="bind1" nodeset="x:data/x:baz" constraint=". &lt; 10" jr:constraintMsg="Error Message" />
13
+ <xf:bind id="bind2" nodeset="x:data/x:foo" readonly="true()" />
14
+ <xf:bind id="bind3" nodeset="x:data/x:bar" relevant="/x:data/x:foo = 321" />
15
+ </xf:model>
16
+
17
+ <xf:input ref="x:data/x:foo">
18
+ <xf:label>Foo</xf:label>
19
+ </xf:input>
20
+
21
+ <xf:select1 ref="x:data/x:bar">
22
+ <xf:label>Bar</xf:label>
23
+ <xf:item>
24
+ <xf:label>Item1</xf:label>
25
+ <xf:value>1</xf:value>
26
+ </xf:item>
27
+ <xf:item>
28
+ <xf:label>Item2</xf:label>
29
+ <xf:value>2</xf:value>
30
+ </xf:item>
31
+ </xf:select1>
32
+
33
+ <xf:input bind="bind1" />
34
+
35
+ <xf:select1>
36
+ <xf:item />
37
+ <xf:label>Baz</xf:label>
38
+ </xf:select1>
39
+
40
+ <xf:upload>
41
+ <xf:label>...</xf:label>
42
+ </xf:upload>
43
+
44
+ <xf:select>
45
+ <xf:item />
46
+ </xf:select>
47
+
48
+ <xf:input bind="bind2" />
49
+
50
+ <xf:input bind="bind3" />
51
+
52
+ </xform>
@@ -0,0 +1,40 @@
1
+ <?xml version='1.0' encoding='utf-8'?>
2
+ <xform xmlns:xf="http://www.w3.org/2002/xforms" xmlns:x="http://tempuri.org/" xmlns:jr="http://openrosa.org/javarosa">
3
+ <xf:model>
4
+ <xf:instance>
5
+ <x:data />
6
+ </xf:instance>
7
+
8
+ <itext>
9
+ <translation lang="es" lang-name="Español">
10
+ <text id="text_id">
11
+ <value>Texto</value>
12
+ </text>
13
+ <text id="empty">
14
+ <value></value>
15
+ </text>
16
+ </translation>
17
+ <translation lang="en" lang-name="Español">
18
+ <text id="text_id">
19
+ <value>Texto</value>
20
+ </text>
21
+ </translation>
22
+ </itext>
23
+ </xf:model>
24
+
25
+ <xf:input>
26
+ <xf:label ref="jr:itext('text_id')" />
27
+ </xf:input>
28
+
29
+ <xf:select1>
30
+ <xf:item>
31
+ <xf:label ref="jr:itext('text_id')" />
32
+ <xf:value>1</xf:value>
33
+ </xf:item>
34
+ </xf:select1>
35
+
36
+ <xf:input>
37
+ <xf:label ref="jr:itext('empty')" />
38
+ </xf:input>
39
+
40
+ </xform>
@@ -0,0 +1,19 @@
1
+ describe "iText" do
2
+
3
+ let(:xform1_path) { File.expand_path '../data/xform_itext.xml', __FILE__ }
4
+ let(:form) { XForms::Form.parse_file xform1_path }
5
+ before(:each) { I18n.locale = :es }
6
+
7
+ it "gets label from itext" do
8
+ form.controls[0].label.should == 'Texto'
9
+ end
10
+
11
+ it "gets label from itext for item" do
12
+ form.controls[1].items.first.label.should == 'Texto'
13
+ end
14
+
15
+ it "gets label from empty itext" do
16
+ form.controls[2].label.should == ''
17
+ end
18
+
19
+ end
data/spec/form_spec.rb ADDED
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ require 'xforms'
3
+ require 'stringio'
4
+
5
+ describe "Form" do
6
+
7
+ let(:xform1_path) { File.expand_path '../data/xform1.xml', __FILE__ }
8
+ let(:form) { XForms::Form.parse_file xform1_path }
9
+ let(:input) { form.controls[0] }
10
+ let(:select1) { form.controls[1] }
11
+ let(:model) { form.model_instance }
12
+
13
+ it "parse model instance" do
14
+ model.root.name.should == 'data'
15
+ end
16
+
17
+ it "parse from string" do
18
+ form = XForms::Form.parse File.read(xform1_path)
19
+ form.model_instance.root.name.should == 'data'
20
+ end
21
+
22
+ it "parse from IO" do
23
+ form = XForms::Form.parse StringIO.new(File.read(xform1_path))
24
+ form.model_instance.root.name.should == 'data'
25
+ end
26
+
27
+ it "parses input" do
28
+ input.should be_a(XForms::Input)
29
+ input.label.should == 'Foo'
30
+ end
31
+
32
+ it "parses select1" do
33
+ select1.should be_a(XForms::Select1)
34
+ select1.label.should == 'Bar'
35
+ select1.items[0].label.should == 'Item1'
36
+ select1.items[0].value.should == '1'
37
+ select1.items[1].label.should == 'Item2'
38
+ select1.items[1].value.should == '2'
39
+ end
40
+
41
+ it "set label correctly after empty element" do
42
+ form.controls[3].label.should == 'Baz'
43
+ end
44
+
45
+ it "set input value" do
46
+ input.value = 'something'
47
+ model.xpath('x:data/x:foo').inner_text.should == 'something'
48
+ end
49
+
50
+ it "get input value" do
51
+ input.value.should == '123'
52
+ end
53
+
54
+ it "set select value" do
55
+ select1.value = '1'
56
+ model.xpath('x:data/x:bar').inner_text.should == '1'
57
+ end
58
+
59
+ it "uses binding for third control" do
60
+ form.controls[2].value = 'something_else'
61
+ model.xpath('x:data/x:baz').inner_text.should == 'something_else'
62
+ end
63
+
64
+ it "parse readonly" do
65
+ form.controls[4].readonly.should == true
66
+ end
67
+
68
+ it "readonly is false by default" do
69
+ form.controls[2].readonly.should == false
70
+ end
71
+
72
+ it "reject invalid value" do
73
+ form.controls[2].value = '55'
74
+ form.controls[2].should_not be_valid
75
+ end
76
+
77
+ it "accept valid value" do
78
+ form.controls[2].value = '5'
79
+ form.controls[2].should be_valid
80
+ end
81
+
82
+ it "field without constraint is always valid" do
83
+ form.controls[0].should be_valid
84
+ end
85
+
86
+ it "field with constraint message" do
87
+ form.controls[2].constraint_message.should == 'Error Message'
88
+ end
89
+
90
+ it "irrelevant field" do
91
+ form.controls[5].should_not be_relevant
92
+ end
93
+
94
+ it "relevant field" do
95
+ form.controls[0].value = '321'
96
+ form.controls[5].should be_relevant
97
+ end
98
+
99
+ it "is relevant if no condition is specified" do
100
+ form.controls[0].should be_relevant
101
+ end
102
+
103
+ end
@@ -0,0 +1,11 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+ end
data/xforms.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "xforms/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "xforms"
7
+ s.version = XForms::VERSION
8
+ s.authors = ["Juan Wajnerman", "Ary Borenszweig"]
9
+ s.email = ["jwajnerman@manas.com.ar", "aborenszweig@manas.com.ar"]
10
+ s.homepage = ""
11
+ s.summary = %q{XForms 1.1 implementation}
12
+ s.description = %q{This gem can be used to parse and fill XForms 1.1}
13
+
14
+ s.rubyforge_project = "xforms"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "nokogiri"
24
+ s.add_runtime_dependency "i18n"
25
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xforms
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Juan Wajnerman
9
+ - Ary Borenszweig
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-02-17 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &70165811338460 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *70165811338460
26
+ - !ruby/object:Gem::Dependency
27
+ name: nokogiri
28
+ requirement: &70165811337980 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70165811337980
37
+ - !ruby/object:Gem::Dependency
38
+ name: i18n
39
+ requirement: &70165811337360 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70165811337360
48
+ description: This gem can be used to parse and fill XForms 1.1
49
+ email:
50
+ - jwajnerman@manas.com.ar
51
+ - aborenszweig@manas.com.ar
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - .gitignore
57
+ - .rbenv-version
58
+ - .rspec
59
+ - Gemfile
60
+ - Rakefile
61
+ - lib/xforms.rb
62
+ - lib/xforms/control.rb
63
+ - lib/xforms/form.rb
64
+ - lib/xforms/form_parser.rb
65
+ - lib/xforms/version.rb
66
+ - spec/data/xform1.xml
67
+ - spec/data/xform_itext.xml
68
+ - spec/form_itext_spec.rb
69
+ - spec/form_spec.rb
70
+ - spec/spec_helper.rb
71
+ - xforms.gemspec
72
+ homepage: ''
73
+ licenses: []
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project: xforms
92
+ rubygems_version: 1.8.10
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: XForms 1.1 implementation
96
+ test_files:
97
+ - spec/data/xform1.xml
98
+ - spec/data/xform_itext.xml
99
+ - spec/form_itext_spec.rb
100
+ - spec/form_spec.rb
101
+ - spec/spec_helper.rb