xml_resource 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 onrooby GmbH
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ xml_resource
2
+ ============
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'XmlResource'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ Bundler::GemHelper.install_tasks
24
+
25
+ require 'rake/testtask'
26
+
27
+ Rake::TestTask.new(:test) do |t|
28
+ t.libs << 'lib'
29
+ t.libs << 'test'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = false
32
+ end
33
+
34
+ task :default => :test
@@ -0,0 +1,171 @@
1
+ module XmlResource
2
+ class Base
3
+ class_attribute :attributes, :collections, :objects, :root_path
4
+ self.attributes = {}
5
+ self.collections = {}
6
+ self.objects = {}
7
+
8
+ class XmlResource::ParseError < Exception; end
9
+ class XmlResource::TypeCastError < Exception; end
10
+
11
+ class << self
12
+
13
+ def from_xml(xml_or_string)
14
+ xml_or_string and xml = parse(xml_or_string) or return
15
+ attrs = {}
16
+ self.attributes.each do |name, options|
17
+ if xpath = attribute_xpath(name)
18
+ node = xml.at(xpath)
19
+ value = node ? node.inner_text : nil
20
+ if !value.nil? && type = attribute_type(name)
21
+ value = cast_to(type, value)
22
+ end
23
+ attrs[name] = value
24
+ end
25
+ end
26
+ self.objects.each do |name, options|
27
+ if xpath = object_xpath(name)
28
+ attrs[name] = object_class(name).from_xml(xml.at(xpath))
29
+ end
30
+ end
31
+ self.collections.each do |name, options|
32
+ if xpath = collection_xpath(name)
33
+ attrs[name] = collection_class(name).collection_from_xml(xml.at(xpath))
34
+ end
35
+ end
36
+ new attrs
37
+ end
38
+
39
+ def collection_from_xml(xml_or_string)
40
+ parse(xml_or_string).search(root).map { |element| from_xml(element) }.compact
41
+ end
42
+
43
+ def basename
44
+ name.sub(/^.*::/, '')
45
+ end
46
+
47
+ def root
48
+ self.root_path || "/#{self.basename.underscore}"
49
+ end
50
+
51
+ def root=(root)
52
+ self.root_path = root
53
+ end
54
+
55
+ [:attribute, :object, :collection].each do |method_name|
56
+ define_method "#{method_name}_names" do
57
+ send(method_name.to_s.pluralize).keys
58
+ end
59
+ end
60
+
61
+ protected
62
+
63
+ def has_attribute(name, options = {})
64
+ self.attributes = attributes.merge(name.to_sym => options.symbolize_keys!)
65
+ attribute_accessor_method name
66
+ end
67
+
68
+ def has_attributes(*args)
69
+ options = args.extract_options!
70
+ args.each { |arg| has_attribute arg, options }
71
+ end
72
+
73
+ def has_object(name, options = {})
74
+ self.objects = objects.merge(name.to_sym => options.symbolize_keys!)
75
+ attr_accessor name
76
+ end
77
+
78
+ def has_collection(name, options = {})
79
+ self.collections = collections.merge(name.to_sym => options.symbolize_keys!)
80
+ define_method "#{name}" do
81
+ instance_variable_get("@#{name}") or instance_variable_set("@#{name}", [])
82
+ end
83
+ define_method "#{name}=" do |assignment|
84
+ instance_variable_set("@#{name}", assignment) if assignment
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def attribute_accessor_method(name)
91
+ define_method("#{name}") { self[name] }
92
+ define_method("#{name}=") { |value| self[name] = value }
93
+ end
94
+
95
+ [:attribute, :object, :collection].each do |method_name|
96
+ define_method "#{method_name}_xpath" do |name|
97
+ options = send(method_name.to_s.pluralize)
98
+ options[name] && options[name][:xpath] or "/#{name}"
99
+ end
100
+ end
101
+
102
+ def attribute_type(name)
103
+ attributes[name] && attributes[name][:type]
104
+ end
105
+
106
+ def object_class(name)
107
+ if objects[name] && class_name = objects[name][:class_name]
108
+ class_name.constantize
109
+ else
110
+ name.to_s.camelize.constantize
111
+ end
112
+ end
113
+
114
+ def collection_class(name)
115
+ if collections[name] && class_name = collections[name][:class_name]
116
+ class_name.constantize
117
+ else
118
+ name.to_s.singularize.camelize.constantize
119
+ end
120
+ end
121
+
122
+ def cast_to(type, value) # only called for non-nil values
123
+ case type
124
+ when :string then value
125
+ when :integer then value.to_i
126
+ when :float then value.to_f
127
+ when :boolean then !!value
128
+ when :decimal then BigDecimal.new(value)
129
+ when :date then Date.parse(value)
130
+ when :datetime then DateTime.parse(value)
131
+ else
132
+ raise XmlResource::TypeCastError, "don't know how to cast #{value.inspect} to #{type}"
133
+ end
134
+ end
135
+
136
+ def parse(xml_or_string)
137
+ case xml_or_string
138
+ when Hpricot::Elem, Hpricot::Elements then xml_or_string
139
+ when String then Hpricot.XML(xml_or_string).root
140
+ else
141
+ raise XmlResource::ParseError, "cannot parse #{xml_or_string.inspect}"
142
+ end
143
+ end
144
+ end
145
+
146
+ def initialize(attrs = {})
147
+ self.attributes = attrs
148
+ end
149
+
150
+ def valid?
151
+ true
152
+ end
153
+
154
+ def attributes=(attrs)
155
+ attrs.each { |name, value| send "#{name}=", value }
156
+ end
157
+
158
+ def attributes
159
+ @attributes ||= {}.with_indifferent_access
160
+ end
161
+
162
+ def [](attr_name)
163
+ attributes[attr_name]
164
+ end
165
+
166
+ def []=(attr_name, value)
167
+ attributes[attr_name] = value
168
+ end
169
+
170
+ end
171
+ end
@@ -0,0 +1,3 @@
1
+ module XmlResource
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'active_support/core_ext'
2
+ require 'hpricot'
3
+ require 'xml_resource/version'
4
+ require 'xml_resource/base'
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
4
+
5
+ require 'xml_resource'
6
+
7
+ class XmlResourceTest < ActiveSupport::TestCase
8
+
9
+ setup do
10
+ xml = File.read(File.expand_path(File.dirname(__FILE__) + '/../data/orders.xml'))
11
+ @orders = Order.collection_from_xml(xml)
12
+ end
13
+
14
+ test 'Instantiation of attributes' do
15
+ assert_equal 3, orders.size
16
+ assert_equal [1, 2, 3], orders.map(&:id)
17
+ assert_equal %w(A B C), orders.map(&:foobar)
18
+ assert_equal %w(2012-08-27 2012-08-26 2012-08-25).map(&:to_date), orders.map(&:date)
19
+ assert_equal [12.99, 10, -12.99], orders.map(&:shipping_cost)
20
+ assert_equal [BigDecimal], orders.map(&:shipping_cost).map(&:class).uniq
21
+ assert_equal [:quantity, :name], Item.attribute_names
22
+ end
23
+
24
+ test 'Instantiation of objects' do
25
+ assert_equal "Mister Spock", orders.first.customer.name
26
+ assert_equal "Käptn Kirk", orders.second.customer.name
27
+ assert_equal nil, orders.third.customer
28
+ assert_equal ['E F', 'A B', 'C D'], orders.map(&:contact).map(&:name)
29
+ end
30
+
31
+ test 'Instantiation of collections' do
32
+ items = orders.map(&:items)
33
+ assert_equal [1, 2, 3], items.map(&:size)
34
+ assert_equal [[3], [3, 2], [3, 2, 1]], items.map { |ary| ary.map(&:quantity) }
35
+ assert_equal [%w(1Three), %w(2Three 2Two), %w(3Three 3Two 3One)], items.map { |ary| ary.map(&:name) }
36
+ end
37
+
38
+ test 'Inheritance' do
39
+ subklass = Class.new(Item) { self.root = 'subitem' }
40
+ assert_equal 'subitem', subklass.root
41
+ assert_equal 'i', Item.root
42
+ end
43
+
44
+ test 'Validation method' do
45
+ assert_equal true, orders.first.valid?
46
+ end
47
+
48
+ test 'Invalid data raises error' do
49
+ assert_raise(XmlResource::ParseError) { Order.collection_from_xml({}) }
50
+ end
51
+
52
+ private
53
+
54
+ def orders
55
+ @orders
56
+ end
57
+
58
+ end
@@ -0,0 +1,82 @@
1
+ <someroot>
2
+ <order>
3
+ <id>1</id>
4
+ <info>
5
+ <foo type="bar">A</foo>
6
+ <date>2012-08-27</date>
7
+ <foo type="non">sense</foo>
8
+ </info>
9
+ <shipping_cost>12.99</shipping_cost>
10
+ <customer>
11
+ <first_name>Mister</first_name>
12
+ <last_name>Spock</last_name>
13
+ </customer>
14
+ <contact>
15
+ <first_name>E</first_name>
16
+ <last_name>F</last_name>
17
+ </contact>
18
+ <items>
19
+ <i>
20
+ <quantity>3</quantity>
21
+ <name>1Three</name>
22
+ </i>
23
+ </items>
24
+ </order>
25
+
26
+ <order>
27
+ <id>2</id>
28
+ <info>
29
+ <foo type="non">blubb</foo>
30
+ <foo type="bar">B</foo>
31
+ <date>2012-08-26</date>
32
+ </info>
33
+ <shipping_cost>10</shipping_cost>
34
+ <customer>
35
+ <first_name>Käptn</first_name>
36
+ <last_name>Kirk</last_name>
37
+ </customer>
38
+ <contact>
39
+ <first_name>A</first_name>
40
+ <last_name>B</last_name>
41
+ </contact>
42
+ <items>
43
+ <i>
44
+ <quantity>3</quantity>
45
+ <name>2Three</name>
46
+ </i>
47
+ <i>
48
+ <quantity>2</quantity>
49
+ <name>2Two</name>
50
+ </i>
51
+ </items>
52
+ </order>
53
+
54
+ <order>
55
+ <id>3</id>
56
+ <info>
57
+ <foo type="non">wah</foo>
58
+ <foo type="bar">C</foo>
59
+ <date>2012-08-25</date>
60
+ </info>
61
+ <shipping_cost>-12.99</shipping_cost>
62
+ <contact>
63
+ <first_name>C</first_name>
64
+ <last_name>D</last_name>
65
+ </contact>
66
+ <items>
67
+ <i>
68
+ <quantity>3</quantity>
69
+ <name>3Three</name>
70
+ </i>
71
+ <i>
72
+ <quantity>2</quantity>
73
+ <name>3Two</name>
74
+ </i>
75
+ <i>
76
+ <quantity>1</quantity>
77
+ <name>3One</name>
78
+ </i>
79
+ </items>
80
+ </order>
81
+
82
+ </someroot>
@@ -0,0 +1,7 @@
1
+ class Contact < XmlResource::Base
2
+ has_attributes :first_name, :last_name, :type => :string
3
+
4
+ def name
5
+ "#{first_name} #{last_name}"
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ class Item < XmlResource::Base
2
+ self.root = 'i'
3
+
4
+ has_attribute :quantity, :type => :float
5
+ has_attribute :name
6
+ end
@@ -0,0 +1,11 @@
1
+ class Order < XmlResource::Base
2
+ has_attribute :id, :type => :integer
3
+ has_attribute :date, :type => :date, :xpath => '/info/date'
4
+ has_attribute :foobar, :xpath => '/info/foo[@type="bar"]'
5
+ has_attribute :shipping_cost, :type => :decimal
6
+
7
+ has_object :customer, :class_name => "Contact"
8
+ has_object :contact
9
+
10
+ has_collection :items, :class_name => "Item", :xpath => '/items'
11
+ end
@@ -0,0 +1,32 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+
3
+ require 'pathname'
4
+
5
+ if RUBY_VERSION >= '1.9'
6
+ require 'simplecov'
7
+ SimpleCov.start do
8
+ if artifacts_dir = ENV['CC_BUILD_ARTIFACTS']
9
+ coverage_dir Pathname.new(artifacts_dir).relative_path_from(Pathname.new(SimpleCov.root)).to_s
10
+ end
11
+ add_filter '/test/'
12
+ add_filter 'vendor'
13
+ end
14
+
15
+ SimpleCov.at_exit do
16
+ SimpleCov.result.format!
17
+ if result = SimpleCov.result
18
+ File.open(File.join(SimpleCov.coverage_path, 'coverage_percent.txt'), 'w') { |f| f << result.covered_percent.to_s }
19
+ end
20
+ end
21
+ end
22
+
23
+ require 'rubygems'
24
+ require 'bundler/setup'
25
+ Bundler.require(:default)
26
+ require 'test/unit'
27
+
28
+ require 'xml_resource'
29
+
30
+ require File.expand_path('../models/item', __FILE__)
31
+ require File.expand_path('../models/contact', __FILE__)
32
+ require File.expand_path('../models/order', __FILE__)
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xml_resource
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matthias Grosser
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: &2151918380 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2151918380
25
+ - !ruby/object:Gem::Dependency
26
+ name: hpricot
27
+ requirement: &2151917700 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2151917700
36
+ - !ruby/object:Gem::Dependency
37
+ name: debugger
38
+ requirement: &2151917100 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2151917100
47
+ description: ''
48
+ email:
49
+ - mtgrosser@gmx.net
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/xml_resource/base.rb
55
+ - lib/xml_resource/version.rb
56
+ - lib/xml_resource.rb
57
+ - MIT-LICENSE
58
+ - Rakefile
59
+ - README.md
60
+ - test/cases/xml_resource_test.rb
61
+ - test/data/orders.xml
62
+ - test/models/contact.rb
63
+ - test/models/item.rb
64
+ - test/models/order.rb
65
+ - test/test_helper.rb
66
+ homepage: http://rubygems.org/gems/xml_resource
67
+ licenses: []
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 1.8.11
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Turn XML into Ruby objects
90
+ test_files:
91
+ - test/cases/xml_resource_test.rb
92
+ - test/data/orders.xml
93
+ - test/models/contact.rb
94
+ - test/models/item.rb
95
+ - test/models/order.rb
96
+ - test/test_helper.rb