xmlmapper 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +35 -0
  3. data/README.md +605 -0
  4. data/lib/happymapper.rb +776 -0
  5. data/lib/happymapper/anonymous_mapper.rb +114 -0
  6. data/lib/happymapper/attribute.rb +21 -0
  7. data/lib/happymapper/element.rb +55 -0
  8. data/lib/happymapper/item.rb +160 -0
  9. data/lib/happymapper/supported_types.rb +140 -0
  10. data/lib/happymapper/text_node.rb +8 -0
  11. data/lib/happymapper/version.rb +3 -0
  12. data/lib/xmlmapper.rb +1 -0
  13. data/spec/attribute_default_value_spec.rb +50 -0
  14. data/spec/attributes_spec.rb +36 -0
  15. data/spec/fixtures/address.xml +9 -0
  16. data/spec/fixtures/ambigous_items.xml +22 -0
  17. data/spec/fixtures/analytics.xml +61 -0
  18. data/spec/fixtures/analytics_profile.xml +127 -0
  19. data/spec/fixtures/atom.xml +19 -0
  20. data/spec/fixtures/commit.xml +52 -0
  21. data/spec/fixtures/current_weather.xml +89 -0
  22. data/spec/fixtures/current_weather_missing_elements.xml +18 -0
  23. data/spec/fixtures/default_namespace_combi.xml +6 -0
  24. data/spec/fixtures/dictionary.xml +20 -0
  25. data/spec/fixtures/family_tree.xml +21 -0
  26. data/spec/fixtures/inagy.xml +85 -0
  27. data/spec/fixtures/lastfm.xml +355 -0
  28. data/spec/fixtures/multiple_namespaces.xml +170 -0
  29. data/spec/fixtures/multiple_primitives.xml +5 -0
  30. data/spec/fixtures/optional_attributes.xml +6 -0
  31. data/spec/fixtures/pita.xml +133 -0
  32. data/spec/fixtures/posts.xml +23 -0
  33. data/spec/fixtures/product_default_namespace.xml +18 -0
  34. data/spec/fixtures/product_no_namespace.xml +10 -0
  35. data/spec/fixtures/product_single_namespace.xml +10 -0
  36. data/spec/fixtures/quarters.xml +19 -0
  37. data/spec/fixtures/radar.xml +21 -0
  38. data/spec/fixtures/set_config_options.xml +3 -0
  39. data/spec/fixtures/statuses.xml +422 -0
  40. data/spec/fixtures/subclass_namespace.xml +50 -0
  41. data/spec/fixtures/wrapper.xml +11 -0
  42. data/spec/happymapper/attribute_spec.rb +12 -0
  43. data/spec/happymapper/element_spec.rb +9 -0
  44. data/spec/happymapper/item_spec.rb +115 -0
  45. data/spec/happymapper/text_node_spec.rb +9 -0
  46. data/spec/happymapper_parse_spec.rb +113 -0
  47. data/spec/happymapper_spec.rb +1116 -0
  48. data/spec/has_many_empty_array_spec.rb +43 -0
  49. data/spec/ignay_spec.rb +95 -0
  50. data/spec/inheritance_spec.rb +107 -0
  51. data/spec/mixed_namespaces_spec.rb +61 -0
  52. data/spec/parse_with_object_to_update_spec.rb +111 -0
  53. data/spec/spec_helper.rb +7 -0
  54. data/spec/to_xml_spec.rb +200 -0
  55. data/spec/to_xml_with_namespaces_spec.rb +231 -0
  56. data/spec/wilcard_tag_name_spec.rb +96 -0
  57. data/spec/wrap_spec.rb +82 -0
  58. data/spec/xpath_spec.rb +89 -0
  59. metadata +182 -0
@@ -0,0 +1,231 @@
1
+ require 'spec_helper'
2
+
3
+ module ToXMLWithNamespaces
4
+
5
+ #
6
+ # Similar example as the to_xml but this time with namespacing
7
+ #
8
+ class Address
9
+ include HappyMapper
10
+
11
+ register_namespace 'address', 'http://www.company.com/address'
12
+ register_namespace 'country', 'http://www.company.com/country'
13
+
14
+ tag 'Address'
15
+ namespace 'address'
16
+
17
+ element :country, 'Country', :tag => 'country', :namespace => 'country'
18
+
19
+ attribute :location, String, :on_save => :when_saving_location
20
+
21
+ element :street, String
22
+ element :postcode, String
23
+ element :city, String
24
+
25
+ element :housenumber, String
26
+
27
+ #
28
+ # to_xml will default to the attr_accessor method and not the attribute,
29
+ # allowing for that to be overwritten
30
+ #
31
+ def housenumber
32
+ "[#{@housenumber}]"
33
+ end
34
+
35
+ def when_saving_location(loc)
36
+ loc + '-live'
37
+ end
38
+
39
+ #
40
+ # Write a empty element even if this is not specified
41
+ #
42
+ element :description, String, :state_when_nil => true
43
+
44
+ #
45
+ # Perform the on_save operation when saving
46
+ #
47
+ has_one :date_created, Time, :on_save => lambda {|time| DateTime.parse(time).strftime("%T %D") if time }
48
+
49
+ #
50
+ # Write multiple elements and call on_save when saving
51
+ #
52
+ has_many :dates_updated, Time, :on_save => lambda {|times|
53
+ times.compact.map {|time| DateTime.parse(time).strftime("%T %D") } if times }
54
+
55
+ #
56
+ # Class composition
57
+ #
58
+
59
+ def initialize(parameters)
60
+ parameters.each_pair do |property,value|
61
+ send("#{property}=",value) if respond_to?("#{property}=")
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ #
68
+ # Country is composed above the in Address class. Here is a demonstration
69
+ # of how to_xml will handle class composition as well as utilizing the tag
70
+ # value.
71
+ #
72
+ class Country
73
+ include HappyMapper
74
+
75
+ register_namespace 'countryName', 'http://www.company.com/countryName'
76
+
77
+ attribute :code, String, :tag => 'countryCode'
78
+ has_one :name, String, :tag => 'countryName', :namespace => 'countryName'
79
+
80
+ def initialize(parameters)
81
+ parameters.each_pair do |property,value|
82
+ send("#{property}=",value) if respond_to?("#{property}=")
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+
89
+ #
90
+ # This class is an example of a class that has a default namespace
91
+ #xmlns="urn:eventis:prodis:onlineapi:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
92
+ #
93
+ class Recipe
94
+ include HappyMapper
95
+
96
+ # this is the default namespace of the document
97
+ register_namespace 'xmlns', 'urn:eventis:prodis:onlineapi:1.0'
98
+ register_namespace 'xsi', "http://www.w3.org/2001/XMLSchema-instance"
99
+ register_namespace 'xsd', "http://www.w3.org/2001/XMLSchema"
100
+
101
+ has_many :ingredients, String
102
+
103
+ def initialize(parameters)
104
+ parameters.each_pair {|property,value| send("#{property}=",value) if respond_to?("#{property}=") }
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "Saving #to_xml", "with xml namespaces" do
110
+
111
+ context "#to_xml", "with namespaces" do
112
+
113
+ let(:subject) do
114
+ address = ToXMLWithNamespaces::Address.new('street' => 'Mockingbird Lane',
115
+ 'location' => 'Home',
116
+ 'housenumber' => '1313',
117
+ 'postcode' => '98103',
118
+ 'city' => 'Seattle',
119
+ 'country' => ToXMLWithNamespaces::Country.new(:name => 'USA', :code => 'us'),
120
+ 'date_created' => '2011-01-01 15:00:00')
121
+
122
+
123
+ address.dates_updated = ["2011-01-01 16:01:00","2011-01-02 11:30:01"]
124
+
125
+ Nokogiri::XML(address.to_xml).root
126
+ end
127
+
128
+ it "saves elements" do
129
+ elements = { 'street' => 'Mockingbird Lane', 'postcode' => '98103', 'city' => 'Seattle' }
130
+
131
+ elements.each_pair do |property,value|
132
+ expect(subject.xpath("address:#{property}").text).to eq value
133
+ end
134
+ end
135
+
136
+ it "saves attributes" do
137
+ expect(subject.xpath('@location').text).to eq "Home-live"
138
+ end
139
+
140
+ context "when an element has a 'state_when_nil' parameter" do
141
+ it "saves an empty element" do
142
+ expect(subject.xpath('address:description').text).to eq ""
143
+ end
144
+ end
145
+
146
+ context "when an element has a 'on_save' parameter" do
147
+ context "with a symbol which represents a function" do
148
+ it "saves the element with the result of a function call and not the value of the instance variable" do
149
+ expect(subject.xpath("address:housenumber").text).to eq "[1313]"
150
+ end
151
+ end
152
+
153
+ context "with a lambda" do
154
+ it "saves the results" do
155
+ expect(subject.xpath('address:date_created').text).to eq "15:00:00 01/01/11"
156
+ end
157
+ end
158
+ end
159
+
160
+ context "when an attribute has a 'on_save' parameter" do
161
+ context "with a lambda" do
162
+ it "saves the result" do
163
+ expect(subject.xpath('@location').text).to eq "Home-live"
164
+ end
165
+ end
166
+ end
167
+
168
+ context "when a has_many has a 'on_save' parameter" do
169
+ context "with a lambda" do
170
+ it "saves the result" do
171
+ dates_updated = subject.xpath('address:dates_updated')
172
+ expect(dates_updated.length).to eq 2
173
+ expect(dates_updated.first.text).to eq "16:01:00 01/01/11"
174
+ expect(dates_updated.last.text).to eq "11:30:01 01/02/11"
175
+ end
176
+ end
177
+ end
178
+
179
+ context "when an element type is a HappyMapper subclass" do
180
+ it "saves attributes" do
181
+ expect(subject.xpath('country:country/@countryCode').text).to eq "us"
182
+ end
183
+
184
+ it "saves elements" do
185
+ expect(subject.xpath('country:country/countryName:countryName').text).to eq "USA"
186
+ end
187
+ end
188
+ end
189
+
190
+ context "with a default namespace" do
191
+ it "writes the default namespace to xml without repeating xmlns" do
192
+ recipe = ToXMLWithNamespaces::Recipe.new(:ingredients => ['One Cup Flour', 'Two Scoops of Lovin'])
193
+ expect(recipe.to_xml).to match /xmlns=\"urn:eventis:prodis:onlineapi:1\.0\"/
194
+ end
195
+ end
196
+
197
+ context 'namespace supplied by element declaration trumps namespace ' \
198
+ 'specified by element class' do
199
+
200
+ let(:expected_xml) do
201
+ <<-XML.gsub(/^\s*\|/, '')
202
+ |<?xml version="1.0"?>
203
+ |<coffeemachine xmlns:coffee="http://coffee.org/Coffee/0.1" xmlns:beverage="http://beverages.org/Beverage/0.1">
204
+ | <beverage:beverage name="coffee"/>
205
+ |</coffeemachine>
206
+ XML
207
+ end
208
+
209
+ class Beverage
210
+ include HappyMapper
211
+ namespace 'coffee'
212
+
213
+ attribute :name, String
214
+ end
215
+
216
+ class CoffeeMachine
217
+ include HappyMapper
218
+ register_namespace 'coffee', "http://coffee.org/Coffee/0.1"
219
+ register_namespace 'beverage', "http://beverages.org/Beverage/0.1"
220
+
221
+ element :beverage, 'beverage', namespace: 'beverage'
222
+ end
223
+
224
+ it 'uses the element declaration namespace on the element' do
225
+ machine = CoffeeMachine.new
226
+ machine.beverage = Beverage.new.tap {|obj| obj.name = 'coffee'}
227
+ machine.to_xml.should be == expected_xml
228
+ end
229
+ end
230
+
231
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Wildcard Root Tag" do
4
+
5
+ generic_class_xml = %{
6
+ <root>
7
+ <description>some description</description>
8
+ <blarg name='blargname1' href='http://blarg.com'/>
9
+ <blarg name='blargname2' href='http://blarg.com'/>
10
+ <jello name='jelloname' href='http://jello.com'/>
11
+ <subelement>
12
+ <jello name='subjelloname' href='http://ohnojello.com' other='othertext'/>
13
+ </subelement>
14
+ </root>}
15
+
16
+ module GenericBase
17
+ class Base
18
+ include Comparable
19
+ include HappyMapper
20
+
21
+ def initialize(params = {})
22
+ @name = params[:name]
23
+ @href = params[:href]
24
+ @other = params[:other]
25
+ end
26
+
27
+ tag '*'
28
+ attribute :name, String
29
+ attribute :href, String
30
+ attribute :other, String
31
+
32
+ def <=>(compared)
33
+ name <=> compared.name && href <=> compared.href && other <=> compared.other
34
+ end
35
+ end
36
+ class Sub
37
+ include HappyMapper
38
+ tag 'subelement'
39
+ has_one :jello, Base, :tag => 'jello'
40
+ end
41
+ class Root
42
+ include HappyMapper
43
+ tag 'root'
44
+ element :description, String
45
+ has_many :blargs, Base, :tag => 'blarg', :xpath => '.'
46
+ has_many :jellos, Base, :tag => 'jello', :xpath => '.'
47
+ has_many :subjellos, Base, :tag => 'jello', :xpath => 'subelement/.', :read_only => true
48
+ has_one :sub_element, Sub
49
+ end
50
+ end
51
+
52
+ describe "can have generic classes using tag '*'" do
53
+
54
+ let(:subject) { GenericBase::Root.parse(generic_class_xml) }
55
+ let(:xml) { Nokogiri::XML(subject.to_xml) }
56
+
57
+ it 'should map different elements to same class' do
58
+ subject.blargs.should_not be_nil
59
+ subject.jellos.should_not be_nil
60
+ end
61
+
62
+ it 'should filter on xpath appropriately' do
63
+ subject.blargs.should have(2).items
64
+ subject.jellos.should have(1).items
65
+ subject.subjellos.should have(1).items
66
+ end
67
+
68
+ def base_with(name,href,other)
69
+ GenericBase::Base.new(:name => name,:href => href,:other => other)
70
+ end
71
+
72
+ it 'should parse correct values onto generic class' do
73
+ expect(subject.blargs[0]).to eq base_with('blargname1','http://blarg.com',nil)
74
+ expect(subject.blargs[1]).to eq base_with('blargname2','http://blarg.com',nil)
75
+ expect(subject.jellos[0]).to eq base_with('jelloname','http://jello.com',nil)
76
+ expect(subject.subjellos[0]).to eq base_with('subjelloname','http://ohnojello.com','othertext')
77
+ end
78
+
79
+ def validate_xpath(xpath,name,href,other)
80
+ expect(xml.xpath("#{xpath}/@name").text).to eq name
81
+ expect(xml.xpath("#{xpath}/@href").text).to eq href
82
+ expect(xml.xpath("#{xpath}/@other").text).to eq other
83
+ end
84
+
85
+ it 'should #to_xml using parent element tag name' do
86
+ xml.xpath('/root/description').text.should == 'some description'
87
+ validate_xpath("/root/blarg[1]","blargname1","http://blarg.com","")
88
+ validate_xpath("/root/blarg[2]","blargname2","http://blarg.com","")
89
+ validate_xpath("/root/jello[1]","jelloname","http://jello.com","")
90
+ end
91
+
92
+ it "should properly respect child HappyMapper tags if tag isn't provided on the element defintion" do
93
+ xml.xpath('root/subelement').should have(1).item
94
+ end
95
+ end
96
+ end
data/spec/wrap_spec.rb ADDED
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe "wrap which allows you to specify a wrapper element" do
4
+
5
+ module Wrap
6
+ class SubClass
7
+ include HappyMapper
8
+ tag 'subclass'
9
+ attribute :myattr, String
10
+ has_many :items, String, :tag => 'item'
11
+ end
12
+ class Root
13
+ include HappyMapper
14
+ tag 'root'
15
+ attribute :attr1, String
16
+ element :name, String
17
+ wrap 'mywraptag' do
18
+ element :description, String
19
+ has_one :subclass, SubClass
20
+ end
21
+ element :number, Integer
22
+ end
23
+ end
24
+
25
+ describe ".parse" do
26
+ context "when given valid XML" do
27
+ let(:subject) { Wrap::Root.parse fixture_file('wrapper.xml') }
28
+
29
+ it 'sets the values correctly' do
30
+ expect(subject.attr1).to eq 'somevalue'
31
+ expect(subject.name).to eq 'myname'
32
+ expect(subject.description).to eq 'some description'
33
+ expect(subject.subclass.myattr).to eq 'attrvalue'
34
+ expect(subject.subclass.items).to have(2).items
35
+ expect(subject.subclass.items[0]).to eq 'item1'
36
+ expect(subject.subclass.items[1]).to eq 'item2'
37
+ expect(subject.number).to eq 12345
38
+ end
39
+ end
40
+
41
+ context "when initialized without XML" do
42
+ let(:subject) { Wrap::Root.new }
43
+
44
+ it "anonymous classes are created so nil class values does not occur" do
45
+ expect { subject.description = 'anything' }.to_not raise_error
46
+ end
47
+ end
48
+ end
49
+
50
+ describe ".to_xml" do
51
+ let(:subject) do
52
+ root = Wrap::Root.new
53
+ root.attr1 = 'somevalue'
54
+ root.name = 'myname'
55
+ root.description = 'some description'
56
+ root.number = 12345
57
+
58
+ subclass = Wrap::SubClass.new
59
+ subclass.myattr = 'attrvalue'
60
+ subclass.items = []
61
+ subclass.items << 'item1'
62
+ subclass.items << 'item2'
63
+
64
+ root.subclass = subclass
65
+
66
+ root
67
+ end
68
+
69
+ it "generates the correct xml" do
70
+ xml = Nokogiri::XML(subject.to_xml)
71
+ expect(xml.xpath('/root/@attr1').text).to eq 'somevalue'
72
+ expect(xml.xpath('/root/name').text).to eq 'myname'
73
+ expect(xml.xpath('/root/mywraptag/description').text).to eq 'some description'
74
+ expect(xml.xpath('/root/mywraptag/subclass/@myattr').text).to eq 'attrvalue'
75
+ expect(xml.xpath('/root/mywraptag/subclass/item')).to have(2).items
76
+ expect(xml.xpath('/root/mywraptag/subclass/item[1]').text).to eq 'item1'
77
+ expect(xml.xpath('/root/mywraptag/subclass/item[2]').text).to eq 'item2'
78
+ expect(xml.xpath('/root/number').text).to eq '12345'
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Specifying elements and attributes with an xpath" do
4
+
5
+ class Item
6
+ include HappyMapper
7
+
8
+ tag 'item'
9
+ namespace 'amazing'
10
+
11
+ element :title, String
12
+ attribute :link, String, :xpath => 'amazing:link/@href'
13
+ has_one :different_link, String, :xpath => 'different:link/@href'
14
+ element :detail, String, :xpath => 'amazing:subitem/amazing:detail'
15
+ has_many :more_details_text, String, :xpath => 'amazing:subitem/amazing:more'
16
+ has_many :more_details, String, :xpath => 'amazing:subitem/amazing:more/@first|amazing:subitem/amazing:more/@alternative'
17
+ has_many :more_details_alternative, String, :xpath => 'amazing:subitem/amazing:more/@*'
18
+
19
+ has_one :baby, 'Baby', :name => 'baby', :namespace => 'amazing'
20
+
21
+ end
22
+
23
+ class Baby
24
+ include HappyMapper
25
+
26
+ has_one :name, String
27
+ end
28
+
29
+ let(:subject) { Item.parse(xml_string,:single => true) }
30
+
31
+ let(:xml_string) do
32
+ %{
33
+ <rss>
34
+ <amazing:item xmlns:amazing="http://www.amazing.com/amazing" xmlns:different="http://www.different.com/different">
35
+ <amazing:title>Test XML</amazing:title>
36
+ <different:link href="different_link" />
37
+ <amazing:link href="link_to_resources" />
38
+ <amazing:subitem>
39
+ <amazing:detail>I want to parse this</amazing:detail>
40
+ <amazing:more first="this one">more 1</amazing:more>
41
+ <amazing:more alternative="another one">more 2</amazing:more>
42
+ </amazing:subitem>
43
+ <amazing:baby>
44
+ <amazing:name>Jumbo</amazing:name>
45
+ </amazing:baby>
46
+ </amazing:item>
47
+ </rss>
48
+ }
49
+ end
50
+
51
+ it "should have a title" do
52
+ expect(subject.title).to eq "Test XML"
53
+ end
54
+
55
+ it "should find the link href value" do
56
+ expect(subject.link).to eq 'link_to_resources'
57
+ end
58
+
59
+ it "should find the link href value" do
60
+ expect(subject.different_link).to eq 'different_link'
61
+ end
62
+
63
+ it "should find this subitem based on the xpath" do
64
+ expect(subject.detail).to eq 'I want to parse this'
65
+ end
66
+
67
+ it "should find the subitems based on the xpath" do
68
+ expect(subject.more_details_text).to have(2).items
69
+ expect(subject.more_details_text.first).to eq "more 1"
70
+ expect(subject.more_details_text.last).to eq "more 2"
71
+ end
72
+
73
+ it "should find the subitems based on the xpath" do
74
+ expect(subject.more_details).to have(2).items
75
+ expect(subject.more_details.first).to eq "this one"
76
+ expect(subject.more_details.last).to eq "another one"
77
+ end
78
+
79
+ it "should find the subitems based on the xpath" do
80
+ expect(subject.more_details_alternative).to have(2).items
81
+ expect(subject.more_details_alternative.first).to eq "this one"
82
+ expect(subject.more_details_alternative.last).to eq "another one"
83
+ end
84
+
85
+ it "should have a baby name" do
86
+ expect(subject.baby.name).to eq "Jumbo"
87
+ end
88
+
89
+ end