xforms 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.
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