xmlmapper 0.5.9

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