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,43 @@
1
+ require "spec_helper"
2
+
3
+ module Sheep
4
+ class Item
5
+ include HappyMapper
6
+ end
7
+
8
+ class Navigator
9
+ include HappyMapper
10
+ tag 'navigator'
11
+
12
+ # This is purposefully set to have the name 'items' with the tag 'item'.
13
+ # The idea is that it should not find the empty items contained within the
14
+ # xml and return an empty array. This exercises the order of how nodes
15
+ # are searched for within an XML document.
16
+ has_many :items, Item, tag: 'item'
17
+
18
+ has_many :items_with_a_different_name, Item, tag: 'item'
19
+
20
+ end
21
+ end
22
+
23
+ describe "emptyness" do
24
+ let(:xml) do
25
+ <<-EOF
26
+ <navigator>
27
+ <items/>
28
+ </navigator>
29
+ EOF
30
+ end
31
+
32
+ let(:navigator) do
33
+ Sheep::Navigator.parse(xml)
34
+ end
35
+
36
+ it "returns an empty array" do
37
+ navigator.items_with_a_different_name.should be_empty
38
+ end
39
+
40
+ it "returns an empty array" do
41
+ navigator.items.should be_empty
42
+ end
43
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ class CatalogTree
4
+ include HappyMapper
5
+
6
+ tag 'CatalogTree'
7
+ register_namespace 'xmlns', 'urn:eventis:prodis:onlineapi:1.0'
8
+ register_namespace 'xsi', 'http://www.w3.org/2001/XMLSchema-instance'
9
+ register_namespace 'xsd', 'http://www.w3.org/2001/XMLSchema'
10
+
11
+ attribute :code, String
12
+
13
+ has_many :nodes, 'CatalogNode', :tag => 'Node', :xpath => '.'
14
+
15
+ end
16
+
17
+
18
+ class CatalogNode
19
+ include HappyMapper
20
+
21
+ tag 'Node'
22
+
23
+ attribute :back_office_id, String, :tag => 'vodBackOfficeId'
24
+
25
+ has_one :name, String, :tag => 'Name'
26
+ # other important fields
27
+
28
+ has_many :translations, 'CatalogNode::Translations', :tag => 'Translation', :xpath => 'child::*'
29
+
30
+ class Translations
31
+ include HappyMapper
32
+ tag 'Translation'
33
+
34
+ attribute :language, String, :tag => 'Language'
35
+ has_one :name, String, :tag => 'Name'
36
+
37
+ end
38
+
39
+ has_many :nodes, CatalogNode, :tag => 'Node', :xpath => 'child::*'
40
+
41
+ end
42
+
43
+ describe HappyMapper do
44
+
45
+ it "should not be nil" do
46
+ catalog_tree.should_not be_nil
47
+ end
48
+
49
+ it "should have the attribute code" do
50
+ catalog_tree.code.should == "NLD"
51
+ end
52
+
53
+ it "should have many nodes" do
54
+ catalog_tree.nodes.should_not be_empty
55
+ catalog_tree.nodes.length.should == 2
56
+ end
57
+
58
+ context "first node" do
59
+
60
+ it "should have a name" do
61
+ first_node.name.should == "Parent 1"
62
+ end
63
+
64
+ it "should have translations" do
65
+ first_node.translations.length.should == 2
66
+
67
+ first_node.translations.first.language.should == "en-GB"
68
+
69
+ first_node.translations.last.name.should == "Parent 1 de"
70
+ end
71
+
72
+ it "should have subnodes" do
73
+ first_node.nodes.should be_kind_of(Enumerable)
74
+ first_node.nodes.should_not be_empty
75
+ first_node.nodes.length.should == 1
76
+ end
77
+
78
+ it "first node - first node name" do
79
+ first_node.nodes.first.name.should == "First"
80
+ end
81
+
82
+ def first_node
83
+ @first_node = catalog_tree.nodes.first
84
+ end
85
+
86
+ end
87
+
88
+
89
+ def catalog_tree ; @catalog_tree ; end
90
+
91
+ before(:all) do
92
+ xml_reference = "#{File.dirname(__FILE__)}/fixtures/inagy.xml"
93
+ @catalog_tree = CatalogTree.parse(File.read(xml_reference), :single => true)
94
+ end
95
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Using inheritance to share elements and attributes" do
4
+
5
+ class Genetics
6
+ include HappyMapper
7
+ content :dna, String
8
+ end
9
+
10
+ class Parent
11
+ include HappyMapper
12
+ attribute :love, Integer
13
+ element :genetics, Genetics
14
+ end
15
+
16
+ class Child < Parent
17
+ include HappyMapper
18
+ attribute :naivety, String
19
+ has_many :immunities, String
20
+ end
21
+
22
+ class Overwrite < Parent
23
+ include HappyMapper
24
+
25
+ attribute :love, String
26
+ element :genetics, Integer
27
+ end
28
+
29
+ describe "Overwrite" do
30
+ let(:subject) do
31
+ xml = '<overwrite love="love" naivety="trusting"><genetics>1001</genetics><immunities>Chicken Pox</immunities></overwrite>'
32
+ Overwrite.parse(xml, single: true)
33
+ end
34
+
35
+ it 'overrides the parent elements and attributes' do
36
+ expect(Overwrite.attributes.count).to be == Parent.attributes.count
37
+ expect(Overwrite.elements.count).to be == Parent.elements.count
38
+ end
39
+
40
+ context "when parsing xml" do
41
+ it 'parses the new overwritten attribut' do
42
+ expect(subject.love).to be == "love"
43
+ end
44
+
45
+ it 'parses the new overwritten element' do
46
+ expect(subject.genetics).to be == 1001
47
+ end
48
+ end
49
+
50
+ context "when saving to xml" do
51
+ subject do
52
+ overwrite = Overwrite.new
53
+ overwrite.genetics = 1
54
+ overwrite.love = "love"
55
+ Nokogiri::XML(overwrite.to_xml).root
56
+ end
57
+
58
+ it 'has only 1 genetics element' do
59
+ expect(subject.xpath('//genetics').count).to be == 1
60
+ end
61
+
62
+ it 'has only 1 love attribute' do
63
+ expect(subject.xpath('@love').text).to be == "love"
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "Child", "a subclass of the Parent" do
69
+ let(:subject) do
70
+ xml = '<child love="99" naivety="trusting"><genetics>ABBA</genetics><immunities>Chicken Pox</immunities></child>'
71
+ Child.parse(xml)
72
+ end
73
+
74
+ context "when parsing xml" do
75
+ it 'should be possible to deserialize XML into a Child class instance' do
76
+ expect(subject.love).to eq 99
77
+ expect(subject.genetics.dna).to eq "ABBA"
78
+ expect(subject.naivety).to eq "trusting"
79
+ expect(subject.immunities).to have(1).item
80
+ end
81
+ end
82
+
83
+ context "when saving to xml" do
84
+ let(:subject) do
85
+ child = Child.new
86
+ child.love = 100
87
+ child.naivety = 'Bright Eyed'
88
+ child.immunities = [ "Small Pox", "Chicken Pox", "Mumps" ]
89
+ genetics = Genetics.new
90
+ genetics.dna = "GATTACA"
91
+ child.genetics = genetics
92
+ Nokogiri::XML(child.to_xml).root
93
+ end
94
+
95
+ it "saves both the Child and Parent attributes" do
96
+ expect(subject.xpath("@naivety").text).to eq "Bright Eyed"
97
+ expect(subject.xpath("@love").text).to eq "100"
98
+ end
99
+
100
+ it "saves both the Child and Parent elements" do
101
+ expect(subject.xpath("genetics").text).to eq "GATTACA"
102
+ expect(subject.xpath("immunities")).to have(3).items
103
+ end
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe "A document with mixed namespaces" do
4
+
5
+ #
6
+ # Note that the parent element of the xml has the namespacing. The elements
7
+ # contained within the xml do not share the parent element namespace so a
8
+ # user of the library would likely need to clear the namespace on each of
9
+ # these child elements.
10
+ #
11
+ let(:xml_document) do
12
+ %{<prefix:address location='home' xmlns:prefix="http://www.unicornland.com/prefix"
13
+ xmlns:different="http://www.trollcountry.com/different">
14
+ <street>Milchstrasse</street>
15
+ <street>Another Street</street>
16
+ <housenumber>23</housenumber>
17
+ <different:postcode>26131</different:postcode>
18
+ <city>Oldenburg</city>
19
+ </prefix:address>}
20
+ end
21
+
22
+ module MixedNamespaces
23
+ class Address
24
+ include HappyMapper
25
+
26
+ namespace :prefix
27
+ tag :address
28
+
29
+ # Here each of the elements have their namespace set to nil to reset their
30
+ # namespace so that it is not the same as the prefix namespace
31
+
32
+ has_many :streets, String, tag: 'street', namespace: nil
33
+
34
+ has_one :house_number, String, tag: 'housenumber', namespace: nil
35
+ has_one :postcode, String, namespace: 'different'
36
+ has_one :city, String, namespace: nil
37
+ end
38
+ end
39
+
40
+ let(:address) do
41
+ MixedNamespaces::Address.parse(xml_document, single: true)
42
+ end
43
+
44
+
45
+ it "has the correct streets" do
46
+ expect(address.streets).to eq [ "Milchstrasse", "Another Street" ]
47
+ end
48
+
49
+ it "house number" do
50
+ expect(address.house_number).to eq "23"
51
+ end
52
+
53
+ it "postcode" do
54
+ expect(address.postcode).to eq "26131"
55
+ end
56
+
57
+ it "city" do
58
+ expect(address.city).to eq "Oldenburg"
59
+ end
60
+
61
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Updating existing objects with .parse and #parse" do
4
+
5
+ let(:subject) { ParseInstanceSpec::Root.parse(parse_instance_initial_xml) }
6
+
7
+ let(:parse_instance_initial_xml) do
8
+ %{<root attr1="initial">
9
+ <item attr1="initial">
10
+ <description>initial</description>
11
+ <subitem attr1="initial">
12
+ <name>initial</name>
13
+ </subitem>
14
+ <subitem attr1="initial">
15
+ <name>initial</name>
16
+ </subitem>
17
+ </item>
18
+ <item attr1="initial">
19
+ <description>initial</description>
20
+ <subitem attr1="initial">
21
+ <name>initial</name>
22
+ </subitem>
23
+ <subitem attr1="initial">
24
+ <name>initial</name>
25
+ </subitem>
26
+ </item>
27
+ </root>}
28
+ end
29
+
30
+ let(:parse_instance_updated_xml) do
31
+ %{<root attr1="updated">
32
+ <item attr1="updated">
33
+ <description>updated</description>
34
+ <subitem attr1="updated">
35
+ <name>updated</name>
36
+ </subitem>
37
+ <subitem attr1="updated">
38
+ <name>updated</name>
39
+ </subitem>
40
+ </item>
41
+ <item attr1="updated">
42
+ <description>updated</description>
43
+ <subitem attr1="updated">
44
+ <name>updated</name>
45
+ </subitem>
46
+ <subitem attr1="updated">
47
+ <name>updated</name>
48
+ </subitem>
49
+ </item>
50
+ </root>}
51
+ end
52
+
53
+ module ParseInstanceSpec
54
+ class SubItem
55
+ include HappyMapper
56
+ tag 'subitem'
57
+ attribute :attr1, String
58
+ element :name, String
59
+ end
60
+ class Item
61
+ include HappyMapper
62
+ tag 'item'
63
+ attribute :attr1, String
64
+ element :description, String
65
+ has_many :sub_items, SubItem
66
+ end
67
+ class Root
68
+ include HappyMapper
69
+ tag 'root'
70
+ attribute :attr1, String
71
+ has_many :items, Item
72
+ end
73
+ end
74
+
75
+ def item_is_correctly_defined(item,value='initial')
76
+ expect(item.attr1).to eq value
77
+ expect(item.description).to eq value
78
+ expect(item.sub_items[0].attr1).to eq value
79
+ expect(item.sub_items[0].name).to eq value
80
+ expect(item.sub_items[1].attr1).to eq value
81
+ expect(item.sub_items[1].name).to eq value
82
+ end
83
+
84
+ it 'initial values are correct' do
85
+ subject.attr1.should == 'initial'
86
+ item_is_correctly_defined( subject.items[0] )
87
+ item_is_correctly_defined( subject.items[1] )
88
+ end
89
+
90
+
91
+ describe ".parse", "specifying an existing object to update" do
92
+ it 'all fields are correct' do
93
+ ParseInstanceSpec::Root.parse(parse_instance_updated_xml, :update => subject)
94
+ expect(subject.attr1).to eq 'updated'
95
+
96
+ item_is_correctly_defined( subject.items[0], 'updated' )
97
+ item_is_correctly_defined( subject.items[1], 'updated' )
98
+ end
99
+ end
100
+
101
+ describe "#parse" do
102
+ it "all fields are correct" do
103
+ subject.parse(parse_instance_updated_xml)
104
+ expect(subject.attr1).to eq 'updated'
105
+
106
+ item_is_correctly_defined( subject.items[0], 'updated' )
107
+ item_is_correctly_defined( subject.items[1], 'updated' )
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+
3
+ require 'happymapper'
4
+
5
+ def fixture_file(filename)
6
+ File.read(File.dirname(__FILE__) + "/fixtures/#{filename}")
7
+ end
@@ -0,0 +1,200 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Saving #to_xml" do
4
+
5
+ module ToXML
6
+ class Address
7
+ include HappyMapper
8
+
9
+ tag 'address'
10
+
11
+ attribute :location, String, :on_save => :when_saving_location
12
+
13
+ element :street, String
14
+ element :postcode, String
15
+ element :city, String
16
+
17
+ element :housenumber, String
18
+
19
+ attribute :modified, Boolean, :read_only => true
20
+ element :temporary, Boolean, :read_only => true
21
+ #
22
+ # to_xml will default to the attr_accessor method and not the attribute,
23
+ # allowing for that to be overwritten
24
+ #
25
+ def housenumber
26
+ "[#{@housenumber}]"
27
+ end
28
+
29
+ def when_saving_location(loc)
30
+ loc + "-live"
31
+ end
32
+
33
+ #
34
+ # Write a empty element even if this is not specified
35
+ #
36
+ element :description, String, :state_when_nil => true
37
+
38
+ #
39
+ # Perform the on_save operation when saving
40
+ #
41
+ has_one :date_created, Time, :on_save => lambda {|time| DateTime.parse(time).strftime("%T %D") if time }
42
+
43
+
44
+ #
45
+ # Execute the method with the same name
46
+
47
+ #
48
+ # Write multiple elements and call on_save when saving
49
+ #
50
+ has_many :dates_updated, Time, :on_save => lambda {|times|
51
+ times.compact.map {|time| DateTime.parse(time).strftime("%T %D") } if times }
52
+
53
+ #
54
+ # Class composition
55
+ #
56
+ element :country, 'Country', :tag => 'country'
57
+
58
+ attribute :occupied, Boolean
59
+
60
+ def initialize(parameters)
61
+ parameters.each_pair do |property,value|
62
+ send("#{property}=",value) if respond_to?("#{property}=")
63
+ end
64
+ @modified = @temporary = true
65
+ end
66
+
67
+ end
68
+
69
+ #
70
+ # Country is composed above the in Address class. Here is a demonstration
71
+ # of how to_xml will handle class composition as well as utilizing the tag
72
+ # value.
73
+ #
74
+ class Country
75
+ include HappyMapper
76
+
77
+ attribute :code, String, :tag => 'countryCode'
78
+ has_one :name, String, :tag => 'countryName'
79
+ has_one :description, 'Description', :tag => 'description'
80
+
81
+ #
82
+ # This inner-class here is to demonstrate saving a text node
83
+ # and optional attributes
84
+ #
85
+ class Description
86
+ include HappyMapper
87
+ content :description, String
88
+ attribute :category, String, :tag => 'category'
89
+ attribute :rating, String, :tag => 'rating', :state_when_nil => true
90
+
91
+ def initialize(desc)
92
+ @description = desc
93
+ end
94
+ end
95
+
96
+ def initialize(parameters)
97
+ parameters.each_pair do |property,value|
98
+ send("#{property}=",value) if respond_to?("#{property}=")
99
+ end
100
+ end
101
+
102
+ end
103
+ end
104
+
105
+ let(:subject) do
106
+ address = ToXML::Address.new 'street' => 'Mockingbird Lane',
107
+ 'location' => 'Home',
108
+ 'housenumber' => '1313',
109
+ 'postcode' => '98103',
110
+ 'city' => 'Seattle',
111
+ 'country' => ToXML::Country.new(:name => 'USA', :code => 'us', :empty_code => nil,
112
+ :description => ToXML::Country::Description.new("A lovely country") ),
113
+ 'date_created' => '2011-01-01 15:00:00',
114
+ 'occupied' => false
115
+
116
+ address.dates_updated = ["2011-01-01 16:01:00","2011-01-02 11:30:01"]
117
+
118
+ Nokogiri::XML(address.to_xml).root
119
+ end
120
+
121
+ it "saves elements" do
122
+ elements = { 'street' => 'Mockingbird Lane', 'postcode' => '98103', 'city' => 'Seattle' }
123
+ elements.each_pair do |property,value|
124
+ expect(subject.xpath("#{property}").text).to eq value
125
+ end
126
+ end
127
+
128
+ it "saves attributes" do
129
+ expect(subject.xpath('@location').text).to eq "Home-live"
130
+ end
131
+
132
+ it 'saves attributes that are Boolean and have a value of false' do
133
+ expect(subject.xpath('@occupied').text).to eq "false"
134
+ end
135
+
136
+ context "when an element has a 'read_only' parameter" do
137
+ it "does not save elements" do
138
+ expect(subject.xpath('temporary')).to be_empty
139
+ end
140
+ end
141
+
142
+ context "when an attribute has a 'read_only' parameter" do
143
+ it "does not save attributes" do
144
+ expect(subject.xpath("@modified")).to be_empty
145
+ end
146
+ end
147
+
148
+ context "when an element has a 'state_when_nil' parameter" do
149
+ it "saves an empty element" do
150
+ expect(subject.xpath('description').text).to eq ""
151
+ end
152
+ end
153
+
154
+ context "when an element has a 'on_save' parameter" do
155
+ context "with a symbol which represents a function" do
156
+ it "saves the element with the result of the function" do
157
+ expect(subject.xpath("housenumber").text).to eq "[1313]"
158
+ end
159
+ end
160
+
161
+ context "with a lambda" do
162
+ it "saves the result of the lambda" do
163
+ expect(subject.xpath('date_created').text).to eq "15:00:00 01/01/11"
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 results" do
171
+ dates_updated = subject.xpath('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 attribute has a 'on_save' parameter" do
180
+ context "with a symbol which represents a function" do
181
+ it "saves the result" do
182
+ expect(subject.xpath('@location').text).to eq "Home-live"
183
+ end
184
+ end
185
+ end
186
+
187
+ context "when an element type is a HappyMapper subclass" do
188
+ it "saves attributes" do
189
+ expect(subject.xpath('country/@countryCode').text).to eq "us"
190
+ end
191
+
192
+ it "saves elements" do
193
+ expect(subject.xpath('country/countryName').text).to eq "USA"
194
+ end
195
+
196
+ it "saves elements" do
197
+ expect(subject.xpath('country/description').text).to eq "A lovely country"
198
+ end
199
+ end
200
+ end