objectify-xml 0.2.0

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.
@@ -0,0 +1,18 @@
1
+ module Objectify
2
+ class ElementParser < Xml
3
+ def primary_xml_element(xml)
4
+ xml.attributes.keys.each do |name|
5
+ method = "#{ name }="
6
+ if respond_to? method
7
+ send(method, xml_text_to_value(xml[name]))
8
+ end
9
+ end
10
+ if respond_to? :inner_html=
11
+ self.inner_html = xml.inner_html
12
+ end
13
+ if respond_to? :inner_text=
14
+ self.inner_text = xml.inner_text
15
+ end
16
+ end
17
+ end
18
+ end
data/spec/atom_spec.rb ADDED
@@ -0,0 +1,63 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ # This is not a validation of my definition of Atom, just a sort of integration test.
4
+ describe Objectify::Atom do
5
+ before :all do
6
+ @feed = Objectify::Atom::Feed.new(sample_feed('wikipedia.atom'))
7
+ end
8
+
9
+ describe 'the feed' do
10
+ it 'should have 2 links' do
11
+ @feed.links.length.should == 2
12
+ end
13
+
14
+ it 'should have 50 entries' do
15
+ @feed.entries.length.should == 50
16
+ end
17
+
18
+ it 'should be generated by Mediawiki' do
19
+ @feed.generator.inner_text.should == 'MediaWiki 1.15alpha'
20
+ end
21
+ end
22
+
23
+ describe 'the first link' do
24
+ it 'should have all attributes' do
25
+ link = @feed.links.first
26
+ link.rel.should == 'self'
27
+ link.type.should == 'application/atom+xml'
28
+ # the href has been unescaped.
29
+ link.href.should == "http://en.wikipedia.org/w/index.php?title=Special:RecentChanges&feed=atom"
30
+ end
31
+ end
32
+
33
+ describe 'the first entry' do
34
+ before do
35
+ @entry = @feed.entries.first
36
+ end
37
+
38
+ it 'should be updated at...' do
39
+ @entry.updated.should == DateTime.parse('2009-02-23T19:08:18')
40
+ end
41
+
42
+ it 'should be titled ...' do
43
+ @entry.title.should == 'Audio commentary'
44
+ end
45
+
46
+ it 'should have a summary' do
47
+ @entry.summary.length.should == 5692
48
+ end
49
+
50
+ it 'should inspect nicely' do
51
+ @entry.inspect.should == '<Objectify::Atom::Entry title, author:Objectify::Atom::Author, id, summary, links:1, updated>'
52
+ end
53
+
54
+ it 'should have the author name' do
55
+ @entry.author.name.should == 'Richiekim'
56
+ end
57
+
58
+ it 'should be nil if the element was not included' do
59
+ @entry.category.should be_nil
60
+ @entry.published.should be_nil
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,156 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Objectify::DocumentParser do
4
+ class D < Objectify::DocumentParser
5
+ def initialize; end
6
+ public *private_instance_methods.map { |s| s.to_sym }
7
+ end
8
+
9
+ before do
10
+ @author = Nokogiri::XML('<author><name>Joe</name></author>').child
11
+ end
12
+
13
+ describe 'primary_xml_element' do
14
+ it 'should call parse_xml' do
15
+ d = D.new
16
+ d.expects(:parse_xml).with(@author.child)
17
+ d.primary_xml_element(@author)
18
+ end
19
+ end
20
+
21
+ describe 'xml helpers' do
22
+ before do
23
+ @title = Nokogiri::XML('<title>Joe</title>').child
24
+ @isbn = Nokogiri::XML('<entry xmlns:book="http://example.com"><book:isbn>123</book:isbn></entry>').child.child
25
+ @generator = Nokogiri::XML('<generator type="friendly">Mr. Happy</title>').child
26
+ end
27
+
28
+ describe 'qualified_name' do
29
+ it 'should return name with just name' do
30
+ D.new.qualified_name(@author).should == 'author'
31
+ end
32
+
33
+ it 'should return namespace:name with name and namespace' do
34
+ D.new.qualified_name(@isbn).should == 'book:isbn'
35
+ end
36
+ end
37
+
38
+ describe 'delegate methods' do
39
+ it 'attribute_type' do
40
+ D.expects(:attribute_type).with('title').returns(:result)
41
+ D.new.attribute_type(@title).should == :result
42
+ end
43
+ it 'flatten?' do
44
+ D.expects(:flatten?).with('title').returns(:result)
45
+ D.new.flatten?(@title).should == :result
46
+ end
47
+ it 'collection?' do
48
+ D.expects(:collection?).with('title').returns(:result)
49
+ D.new.collection?(@title).should == :result
50
+ end
51
+ it 'attribute' do
52
+ D.expects(:find_attribute).with('book:isbn', 'book', 'isbn').returns(:result)
53
+ D.new.attribute(@isbn).should == :result
54
+ end
55
+ end
56
+
57
+ describe 'namespace?' do
58
+ it 'should delegate if the element has a namespace' do
59
+ d = D.new
60
+ d.class.expects(:namespace?).with('book').returns(:result)
61
+ d.namespace?(@isbn).should == :result
62
+ end
63
+
64
+ it 'should just return true if no namespace' do
65
+ d = D.new
66
+ d.class.expects(:namespace?).never
67
+ d.namespace?(@title).should be_true
68
+ end
69
+ end
70
+
71
+ describe 'parse_xml' do
72
+ it 'should call read_xml_element on each xml sibling node' do
73
+ xml2 = mock('xml2', :next => nil)
74
+ xml = mock('xml1', :next => xml2)
75
+ d = D.new
76
+ d.expects(:read_xml_element).with(xml)
77
+ d.expects(:read_xml_element).with(xml2)
78
+ d.parse_xml(xml)
79
+ end
80
+ end
81
+
82
+ describe 'read_xml_element' do
83
+ class FlattenAuthor < D
84
+ flatten :author
85
+ attribute :name
86
+ end
87
+
88
+ it 'should skip text elements' do
89
+ d = D.new
90
+ d.expects(:set_attribute).never
91
+ @title.child.should be_an_instance_of(Nokogiri::XML::Text)
92
+ d.read_xml_element(@title.child)
93
+ end
94
+
95
+ it 'should skip elements in the wrong namespace' do
96
+ d = D.new
97
+ d.expects(:set_attribute).never
98
+ d.read_xml_element(@isbn)
99
+ end
100
+
101
+ it 'should call parse_xml on the first child node of a flattened element' do
102
+ e = FlattenAuthor.new
103
+ e.flatten?(@author).should be_true
104
+ e.expects(:parse_xml).with(@author.child)
105
+ e.read_xml_element(@author)
106
+ end
107
+
108
+ describe 'with an attribute type' do
109
+ class HasAuthor < D
110
+ has_one 'author', Objectify::Atom::Author, 'author'
111
+ end
112
+ it 'should set the attribute' do
113
+ e = HasAuthor.new
114
+ e.attribute_type(@author).should == Objectify::Atom::Author
115
+ e.expects(:author=).with(instance_of(Objectify::Atom::Author))
116
+ e.read_xml_element(@author)
117
+ end
118
+ end
119
+
120
+ describe 'without an attribute type' do
121
+ it 'should set the attribute' do
122
+ e = FlattenAuthor.new
123
+ e.expects(:xml_text_to_value).with('Joe').returns(43)
124
+ e.expects(:name=).with(43)
125
+ e.read_xml_element(@author)
126
+ end
127
+ end
128
+ end
129
+
130
+ describe 'set_attribute' do
131
+ it 'should do nothing if the attribute is not found' do
132
+ d = D.new
133
+ d.expects(:attribute).with(:xml).returns(nil)
134
+ d.set_attribute(:xml) do
135
+ fail "Shouldn't get here"
136
+ end
137
+ end
138
+ it 'should yield and append the result to a collection' do
139
+ d = D.new
140
+ d.expects(:attribute).with(:xml).returns('attr_name')
141
+ d.expects(:collection?).with(:xml).returns(true)
142
+ array = []
143
+ d.expects(:attr_name).returns(array)
144
+ d.set_attribute(:xml) { :value }
145
+ array.should == [:value]
146
+ end
147
+ it 'should yield and set the result if not a collection' do
148
+ d = D.new
149
+ d.expects(:attribute).with(:xml).returns('attr_name')
150
+ d.expects(:collection?).with(:xml).returns(false)
151
+ d.expects(:attr_name=).with(:value)
152
+ d.set_attribute(:xml) { :value }
153
+ end
154
+ end
155
+ end
156
+ end
data/spec/dsl_spec.rb ADDED
@@ -0,0 +1,164 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Objectify::Xml::Dsl do
4
+ A = Objectify::Atom
5
+
6
+ class D < Objectify::DocumentParser
7
+ def initialize; end
8
+ end
9
+
10
+ class MyAuthor < A::Author
11
+ attributes :weight
12
+ attribute 'attr', 'ns:realname'
13
+ attribute 'attrs', nil, true
14
+ attribute 'ns_something'
15
+ flatten :nest
16
+ namespace 'ns', 'http://example.com'
17
+ namespaces :blog
18
+ default_namespace 'http://place.com'
19
+ has_one :link, 'Objectify::Atom::Link', 'link'
20
+ end
21
+
22
+ it 'should init the metadata' do
23
+ D.metadata.each do |k, v|
24
+ v.should_not be_nil
25
+ end
26
+ end
27
+ describe 'inheritance' do
28
+ it 'should include the parent metadata' do
29
+ MyAuthor.metadata[:attributes].should include('name')
30
+ end
31
+ it 'should include the new metadata' do
32
+ MyAuthor.metadata[:attributes].should include('weight')
33
+ end
34
+ it "should not affect the parent's metadata" do
35
+ A::Author.metadata[:attributes].should include('name')
36
+ A::Author.metadata[:attributes].should_not include('weight')
37
+ end
38
+ end
39
+
40
+ describe 'has_one' do
41
+ it 'should set the metadata correctly' do
42
+ A::Feed.metadata[:attributes].should_not include('generator')
43
+ A::Feed.metadata[:qualified_attributes].should include('generator')
44
+ A::Feed.metadata[:collections].should_not include('generator')
45
+ A::Feed.metadata[:types]['generator'].should == A::Generator
46
+ end
47
+ end
48
+
49
+ describe 'has_many' do
50
+ it 'should set the metadata correctly' do
51
+ A::Feed.metadata[:attributes].should_not include('links')
52
+ A::Feed.metadata[:qualified_attributes].should include('link')
53
+ A::Feed.metadata[:collections].should include('link')
54
+ A::Feed.metadata[:types]['link'].to_s.should match(/Link/)
55
+ end
56
+ end
57
+
58
+ describe 'attributes' do
59
+ it 'should call attribute for each item' do
60
+ D.expects(:attribute).with(:one)
61
+ D.expects(:attribute).with(:two)
62
+ D.attributes :one, :two
63
+ end
64
+ end
65
+
66
+ describe 'attribute' do
67
+ it 'should set up an attribute with namespace' do
68
+ #attribute 'attr', 'ns:realname'
69
+ MyAuthor.metadata[:attributes].should_not include('attr')
70
+ MyAuthor.metadata[:qualified_attributes]['ns:realname'].should == 'attr'
71
+ MyAuthor.metadata[:collections].should_not include('ns:realname')
72
+ MyAuthor.metadata[:collections].should_not include('attr')
73
+ MyAuthor.metadata[:types]['ns:realname'].should be_nil
74
+ MyAuthor.metadata[:types]['attr'].should be_nil
75
+ d = MyAuthor.new('')
76
+ d.attr.should be_nil
77
+ d.attr = true
78
+ d.attr.should be_true
79
+ end
80
+ it 'should set up a collection attribute' do
81
+ #attribute 'attr2', nil, true
82
+ MyAuthor.metadata[:attributes].should include('attrs')
83
+ MyAuthor.metadata[:qualified_attributes].keys.should_not include('attrs')
84
+ MyAuthor.metadata[:qualified_attributes].values.should_not include('attrs')
85
+ MyAuthor.metadata[:collections].should include('attrs')
86
+ MyAuthor.metadata[:types]['attr'].should be_nil
87
+ d = MyAuthor.new('')
88
+ d.attrs.should == []
89
+ d.attrs = [:something]
90
+ d.attrs.should == [:something]
91
+ end
92
+ end
93
+
94
+ describe 'find_attribute' do
95
+ it 'should find the attribute with namespace' do
96
+ MyAuthor.find_attribute('ns:realname', 'ns', 'realname').should == 'attr'
97
+ end
98
+ it 'should find the attribute' do
99
+ MyAuthor.find_attribute('attrs', nil, 'attrs').should == 'attrs'
100
+ end
101
+ it 'should pluralize and find the attribute' do
102
+ A::Entry.find_attribute('link', nil, 'link').should == 'links'
103
+ end
104
+ it 'should not find attr with a wrong namespace' do
105
+ MyAuthor.find_attribute('attr', nil, 'attr').should be_nil
106
+ end
107
+ it 'should find the attribute with implicit namespaced name' do
108
+ MyAuthor.find_attribute('ns:something', 'ns', 'something').should == 'ns_something'
109
+ end
110
+ end
111
+
112
+ describe 'flatten' do
113
+ it 'should set the metadata' do
114
+ MyAuthor.metadata[:flatten].should include('nest')
115
+ end
116
+ end
117
+
118
+ describe 'flatten?' do
119
+ it 'should work' do
120
+ MyAuthor.flatten?('nest').should be_true
121
+ MyAuthor.flatten?('attr').should be_false
122
+ end
123
+ end
124
+
125
+ describe 'namespace, namespaces and default_namespace' do
126
+ it 'should define the namespaces' do
127
+ MyAuthor.metadata[:namespaces].keys.should include('ns')
128
+ MyAuthor.metadata[:namespaces].keys.should include('blog')
129
+ MyAuthor.metadata[:namespaces].keys.should include('')
130
+ end
131
+ end
132
+
133
+ describe 'find_namespace' do
134
+ it 'should find a namespace' do
135
+ MyAuthor.find_namespace('blog').should be_nil
136
+ MyAuthor.find_namespace.should == 'http://place.com'
137
+ MyAuthor.find_namespace('').should == 'http://place.com'
138
+ MyAuthor.find_namespace('ns').should == 'http://example.com'
139
+ end
140
+ end
141
+
142
+ describe 'attribute_type' do
143
+ it 'should parse a symbol type definition in module scope' do
144
+ A::Feed.attribute_type('link').should == A::Link
145
+ end
146
+ it 'should parse a string type definition' do
147
+ MyAuthor.attribute_type('link').should == A::Link
148
+ end
149
+ end
150
+
151
+ describe 'set_type' do
152
+ it 'should work' do
153
+ MyAuthor.metadata[:types]['link'].to_s.should == 'Objectify::Atom::Link'
154
+ end
155
+ end
156
+
157
+ describe 'collection?' do
158
+ it 'should work' do
159
+ MyAuthor.collection?('ns:realname').should be_false
160
+ MyAuthor.collection?('attr').should be_false
161
+ MyAuthor.collection?('attrs').should be_true
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,38 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Objectify::ElementParser do
4
+ class TestElement < Objectify::ElementParser
5
+ attr_accessor :attr
6
+
7
+ def initialize(*args)
8
+ yield self if block_given?
9
+ super
10
+ end
11
+ end
12
+
13
+ class TestBodyElement < Objectify::ElementParser
14
+ attr_accessor :attr, :inner_html, :inner_text
15
+ end
16
+
17
+ describe 'primary_xml_element' do
18
+ before do
19
+ @xml = Nokogiri::XML('<element attr="value" attr2="value2"><div>body</div></element>').child
20
+ end
21
+ it 'should call xml_text_to_value for defined attributes only' do
22
+ TestElement.new(@xml) do |te|
23
+ te.expects(:xml_text_to_value).with('value')
24
+ te.expects(:xml_text_to_value).with('value2').never
25
+ end
26
+ end
27
+ it 'should set inner_html and inner_text if the methods are defined' do
28
+ e = TestBodyElement.new(@xml)
29
+ e.inner_html.should == '<div>body</div>'
30
+ e.inner_text.should == 'body'
31
+ end
32
+ it 'should not set inner_html and inner_text if the methods are not defined' do
33
+ @xml.expects(:inner_html).never
34
+ @xml.expects(:inner_text).never
35
+ e = TestElement.new(@xml)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,81 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Objectify::Xml do
4
+ class Xml < Objectify::Xml
5
+ def initialize(*args)
6
+ yield self if block_given?
7
+ super
8
+ end
9
+ public :xml_text_to_value
10
+ end
11
+
12
+ describe 'initialize' do
13
+ describe 'with nil' do
14
+ it 'should handle nil string' do
15
+ Xml.new(nil) do |x|
16
+ x.expects(:primary_xml_element).never
17
+ end
18
+ end
19
+ it 'should assign the parent' do
20
+ x = Xml.new(nil, :parent)
21
+ x.parent.should == :parent
22
+ end
23
+ it 'should initialize the attributes' do
24
+ x = Xml.new(nil)
25
+ x.attributes.should == {}
26
+ end
27
+ end
28
+
29
+ describe 'with multiple nodes before the first element' do
30
+ it 'should call parse_xml with the first element' do
31
+ xml_string = <<-exml
32
+ <?xml version="1.0"?>
33
+ <?xml-stylesheet type="text/css" href="http://en.wikipedia.org/skins-1.5/common/feed.css?206xx"?>
34
+ <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"></feed>
35
+ exml
36
+ xml = Nokogiri::XML(xml_string).child.next
37
+ x = Xml.new(xml_string) do |x|
38
+ def x.primary_xml_element(xml)
39
+ xml.name.should == 'feed'
40
+ @parent = :called
41
+ end
42
+ end
43
+ x.parent.should == :called
44
+ end
45
+ end
46
+
47
+ describe 'with ""' do
48
+ it 'should not call primary_xml_element' do
49
+ Xml.new('') do |x|
50
+ x.expects(:primary_xml_element).never
51
+ end
52
+ end
53
+ end
54
+ describe 'with valid xml' do
55
+ it 'should call primary_xml_element with a Nokogiri XML object' do
56
+ Xml.new('<foo><bar>no</bar></foo>') do |x|
57
+ x.expects(:primary_xml_element)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ describe 'inspect' do
64
+ it 'should work' do
65
+ f = Objectify::Atom::Feed.new(sample_feed('wikipedia.atom'))
66
+ f.inspect.should == '<Objectify::Atom::Feed title, id, subtitle, links:2, generator:Objectify::Atom::Generator, entries:50, updated>'
67
+ end
68
+ end
69
+
70
+ describe 'xml_text_to_value' do
71
+ it('should cast true') { Xml.new('').xml_text_to_value('true').should == true }
72
+ it('should cast false') { Xml.new('').xml_text_to_value('false').should == false }
73
+ it('should cast integer') { Xml.new('').xml_text_to_value('12').should == 12 }
74
+ it('should cast float') { Xml.new('').xml_text_to_value('3.5').should == 3.5 }
75
+ it('should cast date1') { Xml.new('').xml_text_to_value('2009-02-01T11:32:11.03Z').should be_an_instance_of(DateTime) }
76
+ it('should cast date2') { Xml.new('').xml_text_to_value('2009-02-01T11:32:11+400Z').should be_an_instance_of(DateTime) }
77
+ it('should cast date3') { Xml.new('').xml_text_to_value('2009-02-01T11:32:11').should be_an_instance_of(DateTime) }
78
+ it('should cast date4') { Xml.new('').xml_text_to_value('2009-02-01').should be_an_instance_of(DateTime) }
79
+ it('should not cast anything else') { Xml.new('').xml_text_to_value('anything').should == 'anything' }
80
+ end
81
+ end