roxml 2.4.3 → 2.5.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.
- data/History.txt +54 -0
- data/Manifest.txt +9 -6
- data/README.rdoc +24 -17
- data/Rakefile +2 -1
- data/TODO +30 -31
- data/examples/active_record.rb +69 -0
- data/examples/amazon.rb +1 -1
- data/examples/current_weather.rb +1 -1
- data/examples/posts.rb +8 -8
- data/examples/twitter.rb +2 -2
- data/examples/xml/active_record.xml +70 -0
- data/lib/roxml.rb +174 -174
- data/lib/roxml/definition.rb +165 -89
- data/lib/roxml/extensions/deprecation.rb +5 -0
- data/lib/roxml/extensions/string/conversions.rb +2 -3
- data/lib/roxml/hash_definition.rb +26 -25
- data/lib/roxml/xml.rb +15 -6
- data/lib/roxml/xml/parsers/libxml.rb +9 -6
- data/lib/roxml/xml/parsers/rexml.rb +1 -1
- data/lib/roxml/xml/references.rb +14 -17
- data/roxml.gemspec +8 -5
- data/spec/definition_spec.rb +563 -0
- data/spec/examples/active_record_spec.rb +43 -0
- data/spec/roxml_spec.rb +372 -0
- data/spec/shared_specs.rb +15 -0
- data/spec/spec_helper.rb +21 -4
- data/spec/string_spec.rb +15 -0
- data/spec/xml/parser_spec.rb +22 -0
- data/test/fixtures/book_valid.xml +1 -1
- data/test/fixtures/person_with_guarded_mothers.xml +3 -3
- data/test/mocks/mocks.rb +57 -45
- data/test/unit/definition_test.rb +161 -12
- data/test/unit/deprecations_test.rb +97 -0
- data/test/unit/to_xml_test.rb +30 -1
- data/test/unit/xml_bool_test.rb +15 -3
- data/test/unit/xml_construct_test.rb +6 -6
- data/test/unit/xml_hash_test.rb +18 -0
- data/test/unit/xml_initialize_test.rb +6 -3
- data/test/unit/xml_object_test.rb +66 -5
- data/test/unit/xml_text_test.rb +3 -0
- metadata +23 -15
- data/test/unit/array_test.rb +0 -16
- data/test/unit/freeze_test.rb +0 -71
- data/test/unit/inheritance_test.rb +0 -63
- data/test/unit/overriden_output_test.rb +0 -33
- data/test/unit/roxml_test.rb +0 -60
- data/test/unit/string_test.rb +0 -11
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require example('active_record')
|
3
|
+
|
4
|
+
describe ROXML, "under ActiveRecord" do
|
5
|
+
before do
|
6
|
+
@route = Route.from_xml(xml_for('active_record'))
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:all) do
|
10
|
+
db = File.dirname(__FILE__) + "/../../examples/active_record/active_record.sqlite3"
|
11
|
+
if File.exists?(db)
|
12
|
+
FileUtils.rm(db)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be parsed" do
|
17
|
+
@route.should_not == nil
|
18
|
+
@route.should be_an_instance_of(Route)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "xml attributes" do
|
22
|
+
it "should extract xml attributes" do
|
23
|
+
@route.totalHg.should == "640"
|
24
|
+
@route.lonlatx.should == "357865"
|
25
|
+
@route.lonlaty.should == "271635"
|
26
|
+
@route.grcenter.should == "SH 71635 57865"
|
27
|
+
@route.totalMins.should == "235.75000000000003"
|
28
|
+
@route.totalDist.should == "11185.321521477119"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "xml sub-objects" do
|
33
|
+
it "should extract xml sub-objects" do
|
34
|
+
@route.should have(6).waypoints
|
35
|
+
@route.waypoints.each {|waypoint| waypoint.should be_an_instance_of(Waypoint)}
|
36
|
+
end
|
37
|
+
it "should be usable as a ActiveRecord object" do
|
38
|
+
Waypoint.count.should == 0
|
39
|
+
@route.save!
|
40
|
+
Waypoint.count.should == 6
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/roxml_spec.rb
ADDED
@@ -0,0 +1,372 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe ROXML, "#from_xml" do
|
4
|
+
describe "from_xml call", :shared => true do
|
5
|
+
it "should fetch values" do
|
6
|
+
book = BookWithContributors.from_xml(@path)
|
7
|
+
book.title.should == "Programming Ruby - 2nd Edition"
|
8
|
+
book.contributors.map(&:name).should == ["David Thomas","Andrew Hunt","Chad Fowler"]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "called with PathName" do
|
13
|
+
before do
|
14
|
+
@path = Pathname.new(fixture_path(:book_with_contributors))
|
15
|
+
end
|
16
|
+
it_should_behave_like "from_xml call"
|
17
|
+
end
|
18
|
+
|
19
|
+
context "called with File" do
|
20
|
+
before do
|
21
|
+
@path = File.new(fixture_path(:book_with_contributors))
|
22
|
+
end
|
23
|
+
it_should_behave_like "from_xml call"
|
24
|
+
end
|
25
|
+
|
26
|
+
context "called with URI" do
|
27
|
+
before do
|
28
|
+
require 'uri'
|
29
|
+
@path = URI.parse("file://#{File.expand_path(File.expand_path(fixture_path(:book_with_contributors)))}")
|
30
|
+
end
|
31
|
+
it_should_behave_like "from_xml call"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe ROXML, "#xml" do
|
36
|
+
class DescriptionReadonly
|
37
|
+
include ROXML
|
38
|
+
|
39
|
+
xml_reader :writable, :from => :content
|
40
|
+
xml_reader :readonly, :from => :content, :frozen => true
|
41
|
+
end
|
42
|
+
|
43
|
+
class Contributor
|
44
|
+
include ROXML
|
45
|
+
|
46
|
+
xml_reader :role, :from => :attr
|
47
|
+
xml_reader :name
|
48
|
+
end
|
49
|
+
|
50
|
+
class BookWithContributions
|
51
|
+
include ROXML
|
52
|
+
|
53
|
+
xml_name :book
|
54
|
+
xml_reader :isbn, :from => :attr
|
55
|
+
xml_reader :title
|
56
|
+
xml_reader :description, DescriptionReadonly
|
57
|
+
xml_reader :contributions, [Contributor], :from => 'contributor', :in => "contributions"
|
58
|
+
end
|
59
|
+
|
60
|
+
class BookWithContributionsReadonly
|
61
|
+
include ROXML
|
62
|
+
|
63
|
+
xml_name :book
|
64
|
+
xml_reader :isbn, :from => :attr, :frozen => true
|
65
|
+
xml_reader :title, :frozen => true
|
66
|
+
xml_reader :description, DescriptionReadonly, :frozen => true
|
67
|
+
xml_reader :contributions, [Contributor], :from => 'contributor', :in => "contributions", :frozen => true
|
68
|
+
end
|
69
|
+
|
70
|
+
before do
|
71
|
+
@writable = BookWithContributions.from_xml(fixture(:book_with_contributions))
|
72
|
+
@readonly = BookWithContributionsReadonly.from_xml(fixture(:book_with_contributions))
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should raise on duplicate accessor name" do
|
76
|
+
proc do
|
77
|
+
Class.new do
|
78
|
+
include ROXML
|
79
|
+
|
80
|
+
xml_reader :id
|
81
|
+
xml_accessor :id
|
82
|
+
end
|
83
|
+
end.should raise_error(RuntimeError)
|
84
|
+
end
|
85
|
+
|
86
|
+
class OctalInteger
|
87
|
+
def self.from_xml(val)
|
88
|
+
new(Integer(val.content))
|
89
|
+
end
|
90
|
+
|
91
|
+
def initialize(value)
|
92
|
+
@val = value
|
93
|
+
end
|
94
|
+
|
95
|
+
def ==(other)
|
96
|
+
@val == other
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_xml
|
100
|
+
sprintf("%#o", @val)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "overriding output" do
|
105
|
+
class BookWithOctalPages
|
106
|
+
include ROXML
|
107
|
+
|
108
|
+
xml_accessor :pages_with_as, :as => Integer, :to_xml => proc {|val| sprintf("%#o", val) }, :required => true
|
109
|
+
xml_accessor :pages_with_type, OctalInteger, :required => true
|
110
|
+
end
|
111
|
+
|
112
|
+
# to_xml_test :book_with_octal_pages
|
113
|
+
|
114
|
+
describe "with :to_xml option" do
|
115
|
+
it "should output with to_xml filtering"
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "with #to_xml on the object" do
|
119
|
+
it "should output with to_xml filtering"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "overriding input" do
|
124
|
+
before do
|
125
|
+
@book_with_octal_pages_xml = %{
|
126
|
+
<book>
|
127
|
+
<pages>0357</pages>
|
128
|
+
</book>
|
129
|
+
}
|
130
|
+
|
131
|
+
@expected_pages = 239
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "with :as block shorthand" do
|
135
|
+
class BookWithOctalPagesBlockShorthand
|
136
|
+
include ROXML
|
137
|
+
|
138
|
+
xml_accessor :pages, :as => Integer, :required => true
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should apply filtering on input" do
|
142
|
+
book = BookWithOctalPagesBlockShorthand.from_xml(@book_with_octal_pages_xml)
|
143
|
+
book.pages.should == @expected_pages
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "with #from_xml defined on the object" do
|
148
|
+
class BookWithOctalPagesType
|
149
|
+
include ROXML
|
150
|
+
|
151
|
+
xml_accessor :pages, OctalInteger, :required => true
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should apply filtering on input" do
|
155
|
+
book = BookWithOctalPagesType.from_xml(@book_with_octal_pages_xml)
|
156
|
+
book.pages.should == @expected_pages
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "attribute reference" do
|
162
|
+
before do
|
163
|
+
@frozen = @readonly.isbn
|
164
|
+
@unfrozen = @writable.isbn
|
165
|
+
end
|
166
|
+
|
167
|
+
it_should_behave_like "freezable xml reference"
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "text reference" do
|
171
|
+
before do
|
172
|
+
@frozen = @readonly.title
|
173
|
+
@unfrozen = @writable.title
|
174
|
+
end
|
175
|
+
|
176
|
+
it_should_behave_like "freezable xml reference"
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "object reference" do
|
180
|
+
before do
|
181
|
+
@frozen = @readonly.description
|
182
|
+
@unfrozen = @writable.description
|
183
|
+
end
|
184
|
+
|
185
|
+
it_should_behave_like "freezable xml reference"
|
186
|
+
|
187
|
+
describe "indirect reference via an object" do
|
188
|
+
it "does not inherit the frozen status from its parent" do
|
189
|
+
@frozen.writable.frozen?.should be_false
|
190
|
+
@frozen.readonly.frozen?.should be_true
|
191
|
+
|
192
|
+
@unfrozen.writable.frozen?.should be_false
|
193
|
+
@unfrozen.readonly.frozen?.should be_true
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "array reference" do
|
199
|
+
before do
|
200
|
+
@frozen = @readonly.contributions
|
201
|
+
@unfrozen = @writable.contributions
|
202
|
+
end
|
203
|
+
|
204
|
+
it_should_behave_like "freezable xml reference"
|
205
|
+
|
206
|
+
it "should apply :frozen to the constituent elements" do
|
207
|
+
@frozen.all?(&:frozen?).should be_true
|
208
|
+
@unfrozen.any?(&:frozen?).should be_false
|
209
|
+
end
|
210
|
+
|
211
|
+
context "no elements are present in root, no :in is specified" do
|
212
|
+
class BookWithContributors
|
213
|
+
include ROXML
|
214
|
+
|
215
|
+
xml_name :book
|
216
|
+
xml_reader :isbn, :from => :attr
|
217
|
+
xml_reader :title
|
218
|
+
xml_reader :description
|
219
|
+
xml_reader :contributors, [Contributor]
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should look for elements :in the plural of name" do
|
223
|
+
book = BookWithContributors.from_xml(%{
|
224
|
+
<book isbn="0974514055">
|
225
|
+
<contributors>
|
226
|
+
<contributor role="author"><name>David Thomas</name></contributor>
|
227
|
+
<contributor role="supporting author"><name>Andrew Hunt</name></contributor>
|
228
|
+
<contributor role="supporting author"><name>Chad Fowler</name></contributor>
|
229
|
+
</contributors>
|
230
|
+
</book>
|
231
|
+
})
|
232
|
+
book.contributors.map(&:name).sort.should == ["David Thomas","Andrew Hunt","Chad Fowler"].sort
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "hash reference" do
|
238
|
+
class DictionaryOfGuardedNames
|
239
|
+
include ROXML
|
240
|
+
|
241
|
+
xml_name :dictionary
|
242
|
+
xml_reader :definitions, {:key => :name,
|
243
|
+
:value => :content}, :in => :definitions
|
244
|
+
end
|
245
|
+
|
246
|
+
class DictionaryOfGuardedNamesReadonly
|
247
|
+
include ROXML
|
248
|
+
|
249
|
+
xml_name :dictionary
|
250
|
+
xml_reader :definitions, {:key => :name,
|
251
|
+
:value => :content}, :in => :definitions, :frozen => true
|
252
|
+
end
|
253
|
+
|
254
|
+
before do
|
255
|
+
@frozen = DictionaryOfGuardedNamesReadonly.from_xml(fixture(:dictionary_of_guarded_names)).definitions
|
256
|
+
@unfrozen = DictionaryOfGuardedNames.from_xml(fixture(:dictionary_of_guarded_names)).definitions
|
257
|
+
end
|
258
|
+
|
259
|
+
it_should_behave_like "freezable xml reference"
|
260
|
+
|
261
|
+
it "should have frozen keys, as with all hashes" do
|
262
|
+
@frozen.keys.all?(&:frozen?).should be_true
|
263
|
+
@unfrozen.keys.all?(&:frozen?).should be_true
|
264
|
+
end
|
265
|
+
|
266
|
+
it "should apply :frozen to the constituent values" do
|
267
|
+
@frozen.values.all?(&:frozen?).should be_true
|
268
|
+
@unfrozen.values.any?(&:frozen?).should be_false
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
describe ROXML, "inheritance" do
|
274
|
+
class Book
|
275
|
+
include ROXML
|
276
|
+
|
277
|
+
xml_accessor :isbn, :attr => 'ISBN'
|
278
|
+
xml_reader :title
|
279
|
+
xml_reader :description, :as => :cdata
|
280
|
+
xml_reader :author
|
281
|
+
xml_accessor :pages, :text => 'pagecount', :as => Integer
|
282
|
+
end
|
283
|
+
|
284
|
+
class Measurement
|
285
|
+
include ROXML
|
286
|
+
|
287
|
+
xml_reader :units, :from => :attr
|
288
|
+
xml_reader :value, :from => :content, :as => Float
|
289
|
+
|
290
|
+
def initialize(value = 0, units = 'pixels')
|
291
|
+
@value = Float(value)
|
292
|
+
@units = units.to_s
|
293
|
+
normalize_hundredths
|
294
|
+
end
|
295
|
+
|
296
|
+
def to_s
|
297
|
+
"#{value} #{units}"
|
298
|
+
end
|
299
|
+
|
300
|
+
def ==(other)
|
301
|
+
other.units == @units && other.value == @value
|
302
|
+
end
|
303
|
+
|
304
|
+
private
|
305
|
+
def after_parse
|
306
|
+
normalize_hundredths
|
307
|
+
end
|
308
|
+
|
309
|
+
def normalize_hundredths
|
310
|
+
if @units.starts_with? 'hundredths-'
|
311
|
+
@value /= 100
|
312
|
+
@units = @units.split('hundredths-')[1]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class InheritedBookWithDepth < Book
|
318
|
+
xml_reader :depth, Measurement
|
319
|
+
end
|
320
|
+
|
321
|
+
before do
|
322
|
+
@book_xml = %{
|
323
|
+
<book ISBN="0201710897">
|
324
|
+
<title>The PickAxe</title>
|
325
|
+
<description><![CDATA[Probably the best Ruby book out there]]></description>
|
326
|
+
<author>David Thomas, Andrew Hunt, Dave Thomas</author>
|
327
|
+
<depth units="hundredths-meters">1130</depth>
|
328
|
+
<publisher>Pragmattic Programmers</publisher>
|
329
|
+
</book>
|
330
|
+
}
|
331
|
+
|
332
|
+
@parent = Book.from_xml(@book_xml)
|
333
|
+
@child = InheritedBookWithDepth.from_xml(@book_xml)
|
334
|
+
end
|
335
|
+
|
336
|
+
describe "parent" do
|
337
|
+
it "should include its attributes" do
|
338
|
+
@child.isbn.should == "0201710897"
|
339
|
+
@child.title.should == "The PickAxe"
|
340
|
+
@child.description.should == "Probably the best Ruby book out there"
|
341
|
+
@child.author.should == 'David Thomas, Andrew Hunt, Dave Thomas'
|
342
|
+
@child.pages.should == nil
|
343
|
+
end
|
344
|
+
|
345
|
+
it "should not include its child's attributes" do
|
346
|
+
@parent.should_not respond_to(:depth)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
describe "child" do
|
351
|
+
it "should include its parent's attributes" do
|
352
|
+
@child.isbn.should == @parent.isbn
|
353
|
+
@child.title.should == @parent.title
|
354
|
+
@child.description.should == @parent.description
|
355
|
+
@child.author.should == @parent.author
|
356
|
+
@child.pages.should == @parent.pages
|
357
|
+
end
|
358
|
+
|
359
|
+
it "should include its attributes" do
|
360
|
+
@child.depth.to_s.should == '11.3 meters'
|
361
|
+
end
|
362
|
+
|
363
|
+
it "should include parent's attributes added after declaration" do
|
364
|
+
Book.class_eval do
|
365
|
+
xml_reader :publisher, :require => true
|
366
|
+
end
|
367
|
+
|
368
|
+
book = InheritedBookWithDepth.from_xml(@book_xml)
|
369
|
+
book.publisher.should == "Pragmattic Programmers"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe "freezable xml reference", :shared => true do
|
4
|
+
describe "with :frozen option" do
|
5
|
+
it "should be frozen" do
|
6
|
+
@frozen.frozen?.should be_true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "without :frozen option" do
|
11
|
+
it "should not be frozen" do
|
12
|
+
@unfrozen.frozen?.should be_false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,13 +4,30 @@ module ROXML
|
|
4
4
|
SILENCE_XML_NAME_WARNING = true
|
5
5
|
end
|
6
6
|
|
7
|
-
DIR = Pathname.new(__FILE__).dirname
|
8
|
-
|
7
|
+
DIR = Pathname.new(__FILE__ + '../..').expand_path.dirname
|
8
|
+
LOAD_PATH = DIR.join('lib').to_s
|
9
|
+
$LOAD_PATH.unshift(LOAD_PATH) unless
|
10
|
+
$LOAD_PATH.include?(LOAD_PATH) || $LOAD_PATH.include?(File.expand_path(LOAD_PATH))
|
11
|
+
require 'roxml'
|
12
|
+
|
13
|
+
require File.join(File.dirname(__FILE__), 'shared_specs')
|
9
14
|
|
10
15
|
def example(name)
|
11
|
-
DIR.join("
|
16
|
+
DIR.join("examples/#{name}.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
def fixture(name)
|
20
|
+
File.read(fixture_path(name))
|
21
|
+
end
|
22
|
+
|
23
|
+
def xml_fixture(name)
|
24
|
+
ROXML::XML::Parser.parse_file(fixture_path(name)).root
|
25
|
+
end
|
26
|
+
|
27
|
+
def fixture_path(name)
|
28
|
+
"test/fixtures/#{name}.xml"
|
12
29
|
end
|
13
30
|
|
14
31
|
def xml_for(name)
|
15
|
-
DIR.join("
|
32
|
+
DIR.join("examples/xml/#{name}.xml")
|
16
33
|
end
|