saxophone 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +35 -0
- data/Gemfile +19 -0
- data/README.md +206 -0
- data/Rakefile +7 -0
- data/archive/HISTORY.md +77 -0
- data/lib/saxophone.rb +31 -0
- data/lib/saxophone/config/sax_ancestor.rb +17 -0
- data/lib/saxophone/config/sax_attribute.rb +18 -0
- data/lib/saxophone/config/sax_collection.rb +33 -0
- data/lib/saxophone/config/sax_element.rb +65 -0
- data/lib/saxophone/config/sax_element_value.rb +23 -0
- data/lib/saxophone/handlers/sax_abstract_handler.rb +200 -0
- data/lib/saxophone/handlers/sax_nokogiri_handler.rb +23 -0
- data/lib/saxophone/handlers/sax_oga_handler.rb +39 -0
- data/lib/saxophone/handlers/sax_ox_handler.rb +56 -0
- data/lib/saxophone/sax_config.rb +78 -0
- data/lib/saxophone/sax_configure.rb +33 -0
- data/lib/saxophone/sax_document.rb +137 -0
- data/lib/saxophone/version.rb +3 -0
- data/saxophone.gemspec +19 -0
- data/spec/fixtures/atom-content.html +15 -0
- data/spec/fixtures/atom.xml +165 -0
- data/spec/saxophone/sax_activerecord_spec.rb +33 -0
- data/spec/saxophone/sax_configure_spec.rb +51 -0
- data/spec/saxophone/sax_document_spec.rb +1218 -0
- data/spec/saxophone/sax_include_spec.rb +49 -0
- data/spec/spec_helper.rb +22 -0
- metadata +98 -0
@@ -0,0 +1,1218 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "Saxophone" do
|
4
|
+
describe "element" do
|
5
|
+
describe "when parsing a single element" do
|
6
|
+
before do
|
7
|
+
@klass = Class.new do
|
8
|
+
include Saxophone
|
9
|
+
element :title
|
10
|
+
ancestor :body
|
11
|
+
value :something, required: false
|
12
|
+
attribute :anything, required: true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "provides mass assignment through initialize method" do
|
17
|
+
document = @klass.new(title: "Title")
|
18
|
+
expect(document.title).to eq("Title")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "provides an accessor" do
|
22
|
+
document = @klass.new
|
23
|
+
document.title = "Title"
|
24
|
+
expect(document.title).to eq("Title")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "does not overwrites the getter is there is already one present" do
|
28
|
+
@klass = Class.new do
|
29
|
+
def title
|
30
|
+
"#{@title} ***"
|
31
|
+
end
|
32
|
+
|
33
|
+
include Saxophone
|
34
|
+
element :title
|
35
|
+
end
|
36
|
+
|
37
|
+
document = @klass.new
|
38
|
+
document.title = "Title"
|
39
|
+
expect(document.title).to eq("Title ***")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "does not overwrites the setter if there is already one present" do
|
43
|
+
@klass = Class.new do
|
44
|
+
def title=(val)
|
45
|
+
@title = "#{val} **"
|
46
|
+
end
|
47
|
+
|
48
|
+
include Saxophone
|
49
|
+
element :title
|
50
|
+
end
|
51
|
+
|
52
|
+
document = @klass.new
|
53
|
+
document.title = "Title"
|
54
|
+
expect(document.title).to eq("Title **")
|
55
|
+
end
|
56
|
+
|
57
|
+
it "does not overwrites the accessor when the element is not present" do
|
58
|
+
document = @klass.new
|
59
|
+
document.title = "Title"
|
60
|
+
document.parse("<foo></foo>")
|
61
|
+
expect(document.title).to eq("Title")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "overwrites the value when the element is present" do
|
65
|
+
document = @klass.new
|
66
|
+
document.title = "Old title"
|
67
|
+
document.parse("<title>New title</title>")
|
68
|
+
expect(document.title).to eq("New title")
|
69
|
+
end
|
70
|
+
|
71
|
+
it "saves the element text into an accessor" do
|
72
|
+
document = @klass.parse("<title>My Title</title>")
|
73
|
+
expect(document.title).to eq("My Title")
|
74
|
+
end
|
75
|
+
|
76
|
+
it "keeps the document encoding for elements" do
|
77
|
+
data = "<title>My Title</title>"
|
78
|
+
data.encode!("utf-8")
|
79
|
+
|
80
|
+
document = @klass.parse(data)
|
81
|
+
expect(document.title.encoding).to eq(data.encoding)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "saves cdata into an accessor" do
|
85
|
+
document = @klass.parse("<title><![CDATA[A Title]]></title>")
|
86
|
+
expect(document.title).to eq("A Title")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "saves the element text into an accessor when there are multiple elements" do
|
90
|
+
document = @klass.parse("<xml><title>My Title</title><foo>bar</foo></xml>")
|
91
|
+
expect(document.title).to eq("My Title")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "saves the first element text when there are multiple of the same element" do
|
95
|
+
document = @klass.parse("<xml><title>My Title</title><title>bar</title></xml>")
|
96
|
+
expect(document.title).to eq("My Title")
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "the introspection" do
|
100
|
+
it "allows to get column names" do
|
101
|
+
expect(@klass.column_names).to match_array([:title])
|
102
|
+
end
|
103
|
+
|
104
|
+
it "allows to get elements" do
|
105
|
+
expect(@klass.sax_config.top_level_elements.values.flatten.map(&:to_s)).to \
|
106
|
+
match_array(["name: title dataclass: setter: title= required: value: as:title collection: with: {}"])
|
107
|
+
end
|
108
|
+
|
109
|
+
it "allows to get ancestors" do
|
110
|
+
expect(@klass.sax_config.ancestors.map(&:column)).to \
|
111
|
+
match_array([:body])
|
112
|
+
end
|
113
|
+
|
114
|
+
it "allows to get values" do
|
115
|
+
expect(@klass.sax_config.top_level_element_value.map(&:column)).to \
|
116
|
+
match_array([:something])
|
117
|
+
expect(@klass.sax_config.top_level_element_value.map(&:required?)).to \
|
118
|
+
match_array([false])
|
119
|
+
end
|
120
|
+
|
121
|
+
it "allows to get attributes" do
|
122
|
+
expect(@klass.sax_config.top_level_attributes.map(&:column)).to \
|
123
|
+
match_array([:anything])
|
124
|
+
expect(@klass.sax_config.top_level_attributes.map(&:required?)).to \
|
125
|
+
match_array([true])
|
126
|
+
expect(@klass.sax_config.top_level_attributes.map(&:collection?)).to \
|
127
|
+
match_array([false])
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "the class attribute" do
|
132
|
+
before(:each) do
|
133
|
+
@klass = Class.new do
|
134
|
+
include Saxophone
|
135
|
+
element :date, class: DateTime
|
136
|
+
end
|
137
|
+
|
138
|
+
@document = @klass.new
|
139
|
+
@document.date = Time.now.iso8601
|
140
|
+
end
|
141
|
+
|
142
|
+
it "is available" do
|
143
|
+
expect(@klass.data_class(:date)).to eq(DateTime)
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "string" do
|
147
|
+
before do
|
148
|
+
class TestString
|
149
|
+
include Saxophone
|
150
|
+
element :number, class: String
|
151
|
+
end
|
152
|
+
|
153
|
+
class TestStringAttribute
|
154
|
+
include Saxophone
|
155
|
+
attribute :sub_number, class: String
|
156
|
+
end
|
157
|
+
|
158
|
+
class TestStringWithAttribute
|
159
|
+
include Saxophone
|
160
|
+
element :number, class: TestStringAttribute
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
it "is handled in an element" do
|
165
|
+
document = TestString.parse("<number>5.5</number>")
|
166
|
+
expect(document.number).to eq("5.5")
|
167
|
+
end
|
168
|
+
|
169
|
+
it "is handled in an attribute" do
|
170
|
+
document = TestStringWithAttribute.parse("<number sub_number='5.5'></number>")
|
171
|
+
expect(document.number.sub_number).to eq("5.5")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "integer" do
|
176
|
+
before do
|
177
|
+
class TestInteger
|
178
|
+
include Saxophone
|
179
|
+
element :number, class: Integer
|
180
|
+
end
|
181
|
+
|
182
|
+
class TestIntegerAttribute
|
183
|
+
include Saxophone
|
184
|
+
attribute :sub_number, class: Integer
|
185
|
+
end
|
186
|
+
|
187
|
+
class TestIntegerWithAttribute
|
188
|
+
include Saxophone
|
189
|
+
element :number, class: TestIntegerAttribute
|
190
|
+
end
|
191
|
+
|
192
|
+
class IntegerInsideAttribute
|
193
|
+
include Saxophone
|
194
|
+
element :number, value: :int_attr, as: :int_attr, class: Integer
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
it "is handled in an element" do
|
199
|
+
document = TestInteger.parse("<number>5</number>")
|
200
|
+
expect(document.number).to eq(5)
|
201
|
+
end
|
202
|
+
|
203
|
+
it "is handled in an attribute" do
|
204
|
+
document = TestIntegerWithAttribute.parse("<number sub_number='5'></number>")
|
205
|
+
expect(document.number.sub_number).to eq(5)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "is handled in an attribute with value option" do
|
209
|
+
document = IntegerInsideAttribute.parse("<number int_attr='2'></number>")
|
210
|
+
expect(document.int_attr).to eq(2)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe "float" do
|
215
|
+
before do
|
216
|
+
class TestFloat
|
217
|
+
include Saxophone
|
218
|
+
element :number, class: Float
|
219
|
+
end
|
220
|
+
|
221
|
+
class TestFloatAttribute
|
222
|
+
include Saxophone
|
223
|
+
attribute :sub_number, class: Float
|
224
|
+
end
|
225
|
+
|
226
|
+
class TestFloatWithAttribute
|
227
|
+
include Saxophone
|
228
|
+
element :number, class: TestFloatAttribute
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
it "is handled in an element with '.' delimiter" do
|
233
|
+
document = TestFloat.parse("<number>5.5</number>")
|
234
|
+
expect(document.number).to eq(5.5)
|
235
|
+
end
|
236
|
+
|
237
|
+
it "is handled in an element with ',' delimiter" do
|
238
|
+
document = TestFloat.parse("<number>5,5</number>")
|
239
|
+
expect(document.number).to eq(5.5)
|
240
|
+
end
|
241
|
+
|
242
|
+
it "is handled in an attribute" do
|
243
|
+
document = TestFloatWithAttribute.parse("<number sub_number='5.5'>5.5</number>")
|
244
|
+
expect(document.number.sub_number).to eq(5.5)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe "symbol" do
|
249
|
+
before do
|
250
|
+
class TestSymbol
|
251
|
+
include Saxophone
|
252
|
+
element :symbol, class: Symbol
|
253
|
+
end
|
254
|
+
|
255
|
+
class TestSymbolAttribute
|
256
|
+
include Saxophone
|
257
|
+
attribute :sub_symbol, class: Symbol
|
258
|
+
end
|
259
|
+
|
260
|
+
class TestSymbolWithAttribute
|
261
|
+
include Saxophone
|
262
|
+
element :symbol, class: TestSymbolAttribute
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
it "is handled in an element" do
|
267
|
+
document = TestSymbol.parse("<symbol>MY_SYMBOL_VALUE</symbol>")
|
268
|
+
expect(document.symbol).to eq(:my_symbol_value)
|
269
|
+
end
|
270
|
+
|
271
|
+
it "is handled in an attribute" do
|
272
|
+
document = TestSymbolWithAttribute.parse("<symbol sub_symbol='MY_SYMBOL_VALUE'></symbol>")
|
273
|
+
expect(document.symbol.sub_symbol).to eq(:my_symbol_value)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
describe "time" do
|
278
|
+
before do
|
279
|
+
class TestTime
|
280
|
+
include Saxophone
|
281
|
+
element :time, class: Time
|
282
|
+
end
|
283
|
+
|
284
|
+
class TestTimeAttribute
|
285
|
+
include Saxophone
|
286
|
+
attribute :sub_time, class: Time
|
287
|
+
end
|
288
|
+
|
289
|
+
class TestTimeWithAttribute
|
290
|
+
include Saxophone
|
291
|
+
element :time, class: TestTimeAttribute
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
it "is handled in an element" do
|
296
|
+
document = TestTime.parse("<time>1994-02-04T06:20:00Z</time>")
|
297
|
+
expect(document.time).to eq(Time.utc(1994, 2, 4, 6, 20, 0, 0))
|
298
|
+
end
|
299
|
+
|
300
|
+
it "is handled in an attribute" do
|
301
|
+
document = TestTimeWithAttribute.parse("<time sub_time='1994-02-04T06:20:00Z'>1994-02-04T06:20:00Z</time>")
|
302
|
+
expect(document.time.sub_time).to eq(Time.utc(1994, 2, 4, 6, 20, 0, 0))
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
describe "the default attribute" do
|
308
|
+
it "is available" do
|
309
|
+
@klass = Class.new do
|
310
|
+
include Saxophone
|
311
|
+
element :number, class: Integer, default: 0
|
312
|
+
end
|
313
|
+
|
314
|
+
document = @klass.parse("<no>number</no>")
|
315
|
+
expect(document.number).to eq(0)
|
316
|
+
|
317
|
+
document = @klass.parse("<number></number>")
|
318
|
+
expect(document.number).to eq(0)
|
319
|
+
end
|
320
|
+
|
321
|
+
it "can be a Boolean" do
|
322
|
+
@klass = Class.new do
|
323
|
+
include Saxophone
|
324
|
+
element(:bool, default: false) { |v| !!v }
|
325
|
+
end
|
326
|
+
|
327
|
+
document = @klass.parse("<no>bool</no>")
|
328
|
+
expect(document.bool).to be false
|
329
|
+
|
330
|
+
document = @klass.parse("<bool></bool>")
|
331
|
+
expect(document.bool).to be false
|
332
|
+
|
333
|
+
document = @klass.parse("<bool>1</bool>")
|
334
|
+
expect(document.bool).to be true
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
describe "the required attribute" do
|
339
|
+
it "is available" do
|
340
|
+
@klass = Class.new do
|
341
|
+
include Saxophone
|
342
|
+
element :date, required: true
|
343
|
+
end
|
344
|
+
expect(@klass.required?(:date)).to be_truthy
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe "the block" do
|
349
|
+
before do
|
350
|
+
class ElementBlockParser
|
351
|
+
include Saxophone
|
352
|
+
|
353
|
+
ancestor :parent do |parent|
|
354
|
+
parent.class.to_s
|
355
|
+
end
|
356
|
+
|
357
|
+
value :text do |text|
|
358
|
+
text.downcase
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
class BlockParser
|
363
|
+
include Saxophone
|
364
|
+
|
365
|
+
element :title do |title|
|
366
|
+
"#{title}!!!"
|
367
|
+
end
|
368
|
+
|
369
|
+
element :scope do |scope|
|
370
|
+
"#{title} #{scope}"
|
371
|
+
end
|
372
|
+
|
373
|
+
attribute :id do |id|
|
374
|
+
id.to_i
|
375
|
+
end
|
376
|
+
|
377
|
+
element :nested, class: ElementBlockParser
|
378
|
+
elements :message, as: :messages do |message|
|
379
|
+
"#{message}!"
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
it "has instance as a block context" do
|
385
|
+
document = BlockParser.parse("<root><title>SAX</title><scope>something</scope></root>")
|
386
|
+
expect(document.scope).to eq("SAX!!! something")
|
387
|
+
end
|
388
|
+
|
389
|
+
it "uses block for element" do
|
390
|
+
document = BlockParser.parse("<title>SAX</title>")
|
391
|
+
expect(document.title).to eq("SAX!!!")
|
392
|
+
end
|
393
|
+
|
394
|
+
it 'uses block for attribute' do
|
395
|
+
document = BlockParser.parse("<title id='345'>SAX</title>")
|
396
|
+
expect(document.id).to eq(345)
|
397
|
+
end
|
398
|
+
|
399
|
+
it "uses block for value" do
|
400
|
+
document = BlockParser.parse("<title><nested>tEst</nested></title>")
|
401
|
+
expect(document.nested.text).to eq("test")
|
402
|
+
end
|
403
|
+
|
404
|
+
it "uses block for ancestor" do
|
405
|
+
document = BlockParser.parse("<title><nested>SAX</nested></title>")
|
406
|
+
expect(document.nested.parent).to eq("BlockParser")
|
407
|
+
end
|
408
|
+
|
409
|
+
it "uses block for elements" do
|
410
|
+
document = BlockParser.parse("<title><message>hi</message><message>world</message></title>")
|
411
|
+
expect(document.messages).to eq(["hi!", "world!"])
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
describe "when parsing multiple elements" do
|
417
|
+
before do
|
418
|
+
@klass = Class.new do
|
419
|
+
include Saxophone
|
420
|
+
element :title
|
421
|
+
element :name
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
it "saves the element text for a second tag" do
|
426
|
+
document = @klass.parse("<xml><title>My Title</title><name>Paul</name></xml>")
|
427
|
+
expect(document.name).to eq("Paul")
|
428
|
+
expect(document.title).to eq("My Title")
|
429
|
+
end
|
430
|
+
|
431
|
+
it "does not overwrites the getter is there is already one present" do
|
432
|
+
@klass = Class.new do
|
433
|
+
def items
|
434
|
+
[]
|
435
|
+
end
|
436
|
+
|
437
|
+
include Saxophone
|
438
|
+
elements :items
|
439
|
+
end
|
440
|
+
|
441
|
+
document = @klass.new
|
442
|
+
document.items = [1, 2, 3, 4]
|
443
|
+
expect(document.items).to eq([])
|
444
|
+
end
|
445
|
+
|
446
|
+
it "does not overwrites the setter if there is already one present" do
|
447
|
+
@klass = Class.new do
|
448
|
+
def items=(val)
|
449
|
+
@items = [1, *val]
|
450
|
+
end
|
451
|
+
|
452
|
+
include Saxophone
|
453
|
+
elements :items
|
454
|
+
end
|
455
|
+
|
456
|
+
document = @klass.new
|
457
|
+
document.items = [2, 3]
|
458
|
+
expect(document.items).to eq([1, 2, 3])
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
describe "when using options for parsing elements" do
|
463
|
+
describe "using the 'as' option" do
|
464
|
+
before do
|
465
|
+
@klass = Class.new do
|
466
|
+
include Saxophone
|
467
|
+
element :description, as: :summary
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
it "provides an accessor using the 'as' name" do
|
472
|
+
document = @klass.new
|
473
|
+
document.summary = "a small summary"
|
474
|
+
expect(document.summary).to eq("a small summary")
|
475
|
+
end
|
476
|
+
|
477
|
+
it "saves the element text into the 'as' accessor" do
|
478
|
+
document = @klass.parse("<description>here is a description</description>")
|
479
|
+
expect(document.summary).to eq("here is a description")
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
describe "using the :with option" do
|
484
|
+
describe "and the :value option" do
|
485
|
+
before do
|
486
|
+
@klass = Class.new do
|
487
|
+
include Saxophone
|
488
|
+
element :link, value: :href, with: { foo: "bar" }
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
it "saves the value of a matching element" do
|
493
|
+
document = @klass.parse("<link href='test' foo='bar'>asdf</link>")
|
494
|
+
expect(document.link).to eq("test")
|
495
|
+
end
|
496
|
+
|
497
|
+
it "saves the value of the first matching element" do
|
498
|
+
document = @klass.parse("<xml><link href='first' foo='bar' /><link href='second' foo='bar' /></xml>")
|
499
|
+
expect(document.link).to eq("first")
|
500
|
+
end
|
501
|
+
|
502
|
+
describe "and the :as option" do
|
503
|
+
before do
|
504
|
+
@klass = Class.new do
|
505
|
+
include Saxophone
|
506
|
+
element :link, value: :href, as: :url, with: { foo: "bar" }
|
507
|
+
element :link, value: :href, as: :second_url, with: { asdf: "jkl" }
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
it "saves the value of the first matching element" do
|
512
|
+
document = @klass.parse("<xml><link href='first' foo='bar' /><link href='second' asdf='jkl' /><link href='second' foo='bar' /></xml>")
|
513
|
+
expect(document.url).to eq("first")
|
514
|
+
expect(document.second_url).to eq("second")
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
describe "with only one element" do
|
520
|
+
before do
|
521
|
+
@klass = Class.new do
|
522
|
+
include Saxophone
|
523
|
+
element :link, with: { foo: "bar" }
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
it "saves the text of an element that has matching attributes" do
|
528
|
+
document = @klass.parse("<link foo=\"bar\">match</link>")
|
529
|
+
expect(document.link).to eq("match")
|
530
|
+
end
|
531
|
+
|
532
|
+
it "does not saves the text of an element that doesn't have matching attributes" do
|
533
|
+
document = @klass.parse("<link>no match</link>")
|
534
|
+
expect(document.link).to be_nil
|
535
|
+
end
|
536
|
+
|
537
|
+
it "saves the text of an element that has matching attributes when it is the second of that type" do
|
538
|
+
document = @klass.parse("<xml><link>no match</link><link foo=\"bar\">match</link></xml>")
|
539
|
+
expect(document.link).to eq("match")
|
540
|
+
end
|
541
|
+
|
542
|
+
it "saves the text of an element that has matching attributes plus a few more" do
|
543
|
+
document = @klass.parse("<xml><link>no match</link><link asdf='jkl' foo='bar'>match</link>")
|
544
|
+
expect(document.link).to eq("match")
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
describe "with multiple elements of same tag" do
|
549
|
+
before do
|
550
|
+
@klass = Class.new do
|
551
|
+
include Saxophone
|
552
|
+
element :link, as: :first, with: { foo: "bar" }
|
553
|
+
element :link, as: :second, with: { asdf: "jkl" }
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
it "matches the first element" do
|
558
|
+
document = @klass.parse("<xml><link>no match</link><link foo=\"bar\">first match</link><link>no match</link></xml>")
|
559
|
+
expect(document.first).to eq("first match")
|
560
|
+
end
|
561
|
+
|
562
|
+
it "matches the second element" do
|
563
|
+
document = @klass.parse("<xml><link>no match</link><link foo='bar'>first match</link><link asdf='jkl'>second match</link><link>hi</link></xml>")
|
564
|
+
expect(document.second).to eq("second match")
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
describe "with only one element as a regular expression" do
|
569
|
+
before do
|
570
|
+
@klass = Class.new do
|
571
|
+
include Saxophone
|
572
|
+
element :link, with: { foo: /ar$/ }
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
it "saves the text of an element that has matching attributes" do
|
577
|
+
document = @klass.parse("<link foo=\"bar\">match</link>")
|
578
|
+
expect(document.link).to eq("match")
|
579
|
+
end
|
580
|
+
|
581
|
+
it "does not saves the text of an element that doesn't have matching attributes" do
|
582
|
+
document = @klass.parse("<link>no match</link>")
|
583
|
+
expect(document.link).to be_nil
|
584
|
+
end
|
585
|
+
|
586
|
+
it "saves the text of an element that has matching attributes when it is the second of that type" do
|
587
|
+
document = @klass.parse("<xml><link>no match</link><link foo=\"bar\">match</link></xml>")
|
588
|
+
expect(document.link).to eq("match")
|
589
|
+
end
|
590
|
+
|
591
|
+
it "saves the text of an element that has matching attributes plus a few more" do
|
592
|
+
document = @klass.parse("<xml><link>no match</link><link asdf='jkl' foo='bar'>match</link>")
|
593
|
+
expect(document.link).to eq("match")
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
describe "using the 'value' option" do
|
599
|
+
before do
|
600
|
+
@klass = Class.new do
|
601
|
+
include Saxophone
|
602
|
+
element :link, value: :foo
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
it "saves the attribute value" do
|
607
|
+
document = @klass.parse("<link foo='test'>hello</link>")
|
608
|
+
expect(document.link).to eq("test")
|
609
|
+
end
|
610
|
+
|
611
|
+
it "saves the attribute value when there is no text enclosed by the tag" do
|
612
|
+
document = @klass.parse("<link foo='test'></link>")
|
613
|
+
expect(document.link).to eq("test")
|
614
|
+
end
|
615
|
+
|
616
|
+
it "saves the attribute value when the tag close is in the open" do
|
617
|
+
document = @klass.parse("<link foo='test'/>")
|
618
|
+
expect(document.link).to eq("test")
|
619
|
+
end
|
620
|
+
|
621
|
+
it "saves two different attribute values on a single tag" do
|
622
|
+
@klass = Class.new do
|
623
|
+
include Saxophone
|
624
|
+
element :link, value: :foo, as: :first
|
625
|
+
element :link, value: :bar, as: :second
|
626
|
+
end
|
627
|
+
|
628
|
+
document = @klass.parse("<link foo='foo value' bar='bar value'></link>")
|
629
|
+
expect(document.first).to eq("foo value")
|
630
|
+
expect(document.second).to eq("bar value")
|
631
|
+
end
|
632
|
+
|
633
|
+
it "does not fail if one of the attribute hasn't been defined" do
|
634
|
+
@klass = Class.new do
|
635
|
+
include Saxophone
|
636
|
+
element :link, value: :foo, as: :first
|
637
|
+
element :link, value: :bar, as: :second
|
638
|
+
end
|
639
|
+
|
640
|
+
document = @klass.parse("<link foo='foo value'></link>")
|
641
|
+
expect(document.first).to eq("foo value")
|
642
|
+
expect(document.second).to be_nil
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
describe "when desiring both the content and attributes of an element" do
|
647
|
+
before do
|
648
|
+
@klass = Class.new do
|
649
|
+
include Saxophone
|
650
|
+
element :link
|
651
|
+
element :link, value: :foo, as: :link_foo
|
652
|
+
element :link, value: :bar, as: :link_bar
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
it "parses the element and attribute values" do
|
657
|
+
document = @klass.parse("<link foo='test1' bar='test2'>hello</link>")
|
658
|
+
expect(document.link).to eq("hello")
|
659
|
+
expect(document.link_foo).to eq("test1")
|
660
|
+
expect(document.link_bar).to eq("test2")
|
661
|
+
end
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
describe "elements" do
|
667
|
+
describe "when parsing multiple elements" do
|
668
|
+
before do
|
669
|
+
@klass = Class.new do
|
670
|
+
include Saxophone
|
671
|
+
elements :entry, as: :entries
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
it "provides a collection accessor" do
|
676
|
+
document = @klass.new
|
677
|
+
document.entries << :foo
|
678
|
+
expect(document.entries).to eq([:foo])
|
679
|
+
end
|
680
|
+
|
681
|
+
it "parses a single element" do
|
682
|
+
document = @klass.parse("<entry>hello</entry>")
|
683
|
+
expect(document.entries).to eq(["hello"])
|
684
|
+
end
|
685
|
+
|
686
|
+
it "parses multiple elements" do
|
687
|
+
document = @klass.parse("<xml><entry>hello</entry><entry>world</entry></xml>")
|
688
|
+
expect(document.entries).to eq(["hello", "world"])
|
689
|
+
end
|
690
|
+
|
691
|
+
it "parses multiple elements when taking an attribute value" do
|
692
|
+
attribute_klass = Class.new do
|
693
|
+
include Saxophone
|
694
|
+
elements :entry, as: :entries, value: :foo
|
695
|
+
end
|
696
|
+
|
697
|
+
doc = attribute_klass.parse("<xml><entry foo='asdf' /><entry foo='jkl' /></xml>")
|
698
|
+
expect(doc.entries).to eq(["asdf", "jkl"])
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
describe "when using the with and class options" do
|
703
|
+
before do
|
704
|
+
class Bar
|
705
|
+
include Saxophone
|
706
|
+
element :title
|
707
|
+
end
|
708
|
+
|
709
|
+
class Foo
|
710
|
+
include Saxophone
|
711
|
+
element :title
|
712
|
+
end
|
713
|
+
|
714
|
+
class Item
|
715
|
+
include Saxophone
|
716
|
+
end
|
717
|
+
|
718
|
+
@klass = Class.new do
|
719
|
+
include Saxophone
|
720
|
+
elements :item, as: :items, with: { type: "Bar" }, class: Bar
|
721
|
+
elements :item, as: :items, with: { type: /Foo/ }, class: Foo
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
725
|
+
it "casts into the correct class" do
|
726
|
+
document = @klass.parse("<items><item type=\"Bar\"><title>Bar title</title></item><item type=\"Foo\"><title>Foo title</title></item></items>")
|
727
|
+
expect(document.items.size).to eq(2)
|
728
|
+
expect(document.items.first).to be_a(Bar)
|
729
|
+
expect(document.items.first.title).to eq("Bar title")
|
730
|
+
expect(document.items.last).to be_a(Foo)
|
731
|
+
expect(document.items.last.title).to eq("Foo title")
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
describe "when using the class option" do
|
736
|
+
before do
|
737
|
+
class Foo
|
738
|
+
include Saxophone
|
739
|
+
element :title
|
740
|
+
end
|
741
|
+
|
742
|
+
@klass = Class.new do
|
743
|
+
include Saxophone
|
744
|
+
elements :entry, as: :entries, class: Foo
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
it "parses a single element with children" do
|
749
|
+
document = @klass.parse("<entry><title>a title</title></entry>")
|
750
|
+
expect(document.entries.size).to eq(1)
|
751
|
+
expect(document.entries.first.title).to eq("a title")
|
752
|
+
end
|
753
|
+
|
754
|
+
it "parses multiple elements with children" do
|
755
|
+
document = @klass.parse("<xml><entry><title>title 1</title></entry><entry><title>title 2</title></entry></xml>")
|
756
|
+
expect(document.entries.size).to eq(2)
|
757
|
+
expect(document.entries.first.title).to eq("title 1")
|
758
|
+
expect(document.entries.last.title).to eq("title 2")
|
759
|
+
end
|
760
|
+
|
761
|
+
it "does not parse a top level element that is specified only in a child" do
|
762
|
+
document = @klass.parse("<xml><title>no parse</title><entry><title>correct title</title></entry></xml>")
|
763
|
+
expect(document.entries.size).to eq(1)
|
764
|
+
expect(document.entries.first.title).to eq("correct title")
|
765
|
+
end
|
766
|
+
|
767
|
+
it "parses elements, and make attributes and inner text available" do
|
768
|
+
class Related
|
769
|
+
include Saxophone
|
770
|
+
element "related", as: :item
|
771
|
+
element "related", as: :attr, value: "attr"
|
772
|
+
end
|
773
|
+
|
774
|
+
class Foo
|
775
|
+
elements "related", as: "items", class: Related
|
776
|
+
end
|
777
|
+
|
778
|
+
doc = Foo.parse(%{<xml><collection><related attr='baz'>something</related><related>somethingelse</related></collection></xml>})
|
779
|
+
expect(doc.items.first).not_to be_nil
|
780
|
+
expect(doc.items.size).to eq(2)
|
781
|
+
expect(doc.items.first.item).to eq("something")
|
782
|
+
expect(doc.items.last.item).to eq("somethingelse")
|
783
|
+
end
|
784
|
+
|
785
|
+
it "parses out an attribute value from the tag that starts the collection" do
|
786
|
+
class Foo
|
787
|
+
element :entry, value: :href, as: :url
|
788
|
+
end
|
789
|
+
|
790
|
+
document = @klass.parse("<xml><entry href='http://pauldix.net'><title>paul</title></entry></xml>")
|
791
|
+
expect(document.entries.size).to eq(1)
|
792
|
+
expect(document.entries.first.title).to eq("paul")
|
793
|
+
expect(document.entries.first.url).to eq("http://pauldix.net")
|
794
|
+
end
|
795
|
+
end
|
796
|
+
end
|
797
|
+
|
798
|
+
describe "when dealing with element names containing dashes" do
|
799
|
+
it "converts dashes to underscores" do
|
800
|
+
class Dashes
|
801
|
+
include Saxophone
|
802
|
+
element :dashed_element
|
803
|
+
end
|
804
|
+
|
805
|
+
parsed = Dashes.parse("<dashed-element>Text</dashed-element>")
|
806
|
+
expect(parsed.dashed_element).to eq "Text"
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
describe "full example" do
|
811
|
+
before do
|
812
|
+
@xml = File.read("spec/fixtures/atom.xml")
|
813
|
+
|
814
|
+
class AtomEntry
|
815
|
+
include Saxophone
|
816
|
+
element :title
|
817
|
+
element :name, as: :author
|
818
|
+
element "feedburner:origLink", as: :url
|
819
|
+
element :link, as: :alternate, value: :href, with: { type: "text/html", rel: "alternate" }
|
820
|
+
element :summary
|
821
|
+
element :content
|
822
|
+
element :published
|
823
|
+
end
|
824
|
+
|
825
|
+
class Atom
|
826
|
+
include Saxophone
|
827
|
+
element :title
|
828
|
+
element :link, value: :href, as: :url, with: { type: "text/html" }
|
829
|
+
element :link, value: :href, as: :feed_url, with: { type: "application/atom+xml" }
|
830
|
+
elements :entry, as: :entries, class: AtomEntry
|
831
|
+
end
|
832
|
+
|
833
|
+
@feed = Atom.parse(@xml)
|
834
|
+
end
|
835
|
+
|
836
|
+
it "parses the url" do
|
837
|
+
expect(@feed.url).to eq("http://www.pauldix.net/")
|
838
|
+
end
|
839
|
+
|
840
|
+
it "parses entry url" do
|
841
|
+
expect(@feed.entries.first.url).to eq("http://www.pauldix.net/2008/09/marshal-data-to.html?param1=1¶m2=2")
|
842
|
+
expect(@feed.entries.first.alternate).to eq("http://feeds.feedburner.com/~r/PaulDixExplainsNothing/~3/383536354/marshal-data-to.html?param1=1¶m2=2")
|
843
|
+
end
|
844
|
+
|
845
|
+
it "parses content" do
|
846
|
+
expect(@feed.entries.first.content.strip).to eq(File.read("spec/fixtures/atom-content.html").strip)
|
847
|
+
end
|
848
|
+
end
|
849
|
+
|
850
|
+
describe "parsing a tree" do
|
851
|
+
before do
|
852
|
+
@xml = %[
|
853
|
+
<categories>
|
854
|
+
<category id="1">
|
855
|
+
<title>First</title>
|
856
|
+
<categories>
|
857
|
+
<category id="2">
|
858
|
+
<title>Second</title>
|
859
|
+
</category>
|
860
|
+
</categories>
|
861
|
+
</category>
|
862
|
+
</categories>
|
863
|
+
]
|
864
|
+
|
865
|
+
class CategoryCollection; end
|
866
|
+
|
867
|
+
class Category
|
868
|
+
include Saxophone
|
869
|
+
attr_accessor :id
|
870
|
+
element :category, value: :id, as: :id
|
871
|
+
element :title
|
872
|
+
element :categories, as: :collection, class: CategoryCollection
|
873
|
+
ancestor :ancestor
|
874
|
+
end
|
875
|
+
|
876
|
+
class CategoryCollection
|
877
|
+
include Saxophone
|
878
|
+
elements :category, as: :categories, class: Category
|
879
|
+
end
|
880
|
+
|
881
|
+
@collection = CategoryCollection.parse(@xml)
|
882
|
+
end
|
883
|
+
|
884
|
+
it "parses the first category" do
|
885
|
+
expect(@collection.categories.first.id).to eq("1")
|
886
|
+
expect(@collection.categories.first.title).to eq("First")
|
887
|
+
expect(@collection.categories.first.ancestor).to eq(@collection)
|
888
|
+
end
|
889
|
+
|
890
|
+
it "parses the nested category" do
|
891
|
+
expect(@collection.categories.first.collection.categories.first.id).to eq("2")
|
892
|
+
expect(@collection.categories.first.collection.categories.first.title).to eq("Second")
|
893
|
+
end
|
894
|
+
end
|
895
|
+
|
896
|
+
describe "parsing a tree without a collection class" do
|
897
|
+
before do
|
898
|
+
@xml = %[
|
899
|
+
<categories>
|
900
|
+
<category id="1">
|
901
|
+
<title>First</title>
|
902
|
+
<categories>
|
903
|
+
<category id="2">
|
904
|
+
<title>Second</title>
|
905
|
+
</category>
|
906
|
+
</categories>
|
907
|
+
</category>
|
908
|
+
</categories>
|
909
|
+
]
|
910
|
+
|
911
|
+
class CategoryTree
|
912
|
+
include Saxophone
|
913
|
+
attr_accessor :id
|
914
|
+
element :category, value: :id, as: :id
|
915
|
+
element :title
|
916
|
+
elements :category, as: :categories, class: CategoryTree
|
917
|
+
end
|
918
|
+
|
919
|
+
@collection = CategoryTree.parse(@xml)
|
920
|
+
end
|
921
|
+
|
922
|
+
it "parses the first category" do
|
923
|
+
expect(@collection.categories.first.id).to eq("1")
|
924
|
+
expect(@collection.categories.first.title).to eq("First")
|
925
|
+
end
|
926
|
+
|
927
|
+
it "parses the nested category" do
|
928
|
+
expect(@collection.categories.first.categories.first.id).to eq("2")
|
929
|
+
expect(@collection.categories.first.categories.first.title).to eq("Second")
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
describe "with element deeper inside the xml structure" do
|
934
|
+
before do
|
935
|
+
@xml = %[
|
936
|
+
<item id="1">
|
937
|
+
<texts>
|
938
|
+
<title>Hello</title>
|
939
|
+
</texts>
|
940
|
+
</item>
|
941
|
+
]
|
942
|
+
|
943
|
+
@klass = Class.new do
|
944
|
+
include Saxophone
|
945
|
+
attr_accessor :id
|
946
|
+
element :item, value: "id", as: :id
|
947
|
+
element :title
|
948
|
+
end
|
949
|
+
|
950
|
+
@item = @klass.parse(@xml)
|
951
|
+
end
|
952
|
+
|
953
|
+
it "has an id" do
|
954
|
+
expect(@item.id).to eq("1")
|
955
|
+
end
|
956
|
+
|
957
|
+
it "has a title" do
|
958
|
+
expect(@item.title).to eq("Hello")
|
959
|
+
end
|
960
|
+
end
|
961
|
+
|
962
|
+
describe "with config to pull multiple attributes" do
|
963
|
+
before do
|
964
|
+
@xml = %[
|
965
|
+
<item id="1">
|
966
|
+
<author name="John Doe" role="writer" />
|
967
|
+
</item>
|
968
|
+
]
|
969
|
+
|
970
|
+
class AuthorElement
|
971
|
+
include Saxophone
|
972
|
+
attribute :name
|
973
|
+
attribute :role
|
974
|
+
end
|
975
|
+
|
976
|
+
class ItemElement
|
977
|
+
include Saxophone
|
978
|
+
element :author, class: AuthorElement
|
979
|
+
end
|
980
|
+
|
981
|
+
@item = ItemElement.parse(@xml)
|
982
|
+
end
|
983
|
+
|
984
|
+
it "has the child element" do
|
985
|
+
expect(@item.author).not_to be_nil
|
986
|
+
end
|
987
|
+
|
988
|
+
it "has the author name" do
|
989
|
+
expect(@item.author.name).to eq("John Doe")
|
990
|
+
end
|
991
|
+
|
992
|
+
it "has the author role" do
|
993
|
+
expect(@item.author.role).to eq("writer")
|
994
|
+
end
|
995
|
+
end
|
996
|
+
|
997
|
+
describe "with multiple elements and multiple attributes" do
|
998
|
+
before do
|
999
|
+
@xml = %[
|
1000
|
+
<item id="1">
|
1001
|
+
<author name="John Doe" role="writer" />
|
1002
|
+
<author name="Jane Doe" role="artist" />
|
1003
|
+
</item>
|
1004
|
+
]
|
1005
|
+
|
1006
|
+
class AuthorElement2
|
1007
|
+
include Saxophone
|
1008
|
+
attribute :name
|
1009
|
+
attribute :role
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
class ItemElement2
|
1013
|
+
include Saxophone
|
1014
|
+
elements :author, as: :authors, class: AuthorElement2
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
@item = ItemElement2.parse(@xml)
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
it "has the child elements" do
|
1021
|
+
expect(@item.authors).not_to be_nil
|
1022
|
+
expect(@item.authors.count).to eq(2)
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
it "has the author names" do
|
1026
|
+
expect(@item.authors.first.name).to eq("John Doe")
|
1027
|
+
expect(@item.authors.last.name).to eq("Jane Doe")
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
it "has the author roles" do
|
1031
|
+
expect(@item.authors.first.role).to eq("writer")
|
1032
|
+
expect(@item.authors.last.role).to eq("artist")
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
describe "with mixed attributes and element values" do
|
1037
|
+
before do
|
1038
|
+
@xml = %[
|
1039
|
+
<item id="1">
|
1040
|
+
<author role="writer">John Doe</author>
|
1041
|
+
</item>
|
1042
|
+
]
|
1043
|
+
|
1044
|
+
class AuthorElement3
|
1045
|
+
include Saxophone
|
1046
|
+
value :name
|
1047
|
+
attribute :role
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
class ItemElement3
|
1051
|
+
include Saxophone
|
1052
|
+
element :author, class: AuthorElement3
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
@item = ItemElement3.parse(@xml)
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
it "has the child elements" do
|
1059
|
+
expect(@item.author).not_to be_nil
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
it "has the author names" do
|
1063
|
+
expect(@item.author.name).to eq("John Doe")
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
it "has the author roles" do
|
1067
|
+
expect(@item.author.role).to eq("writer")
|
1068
|
+
end
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
describe "with multiple mixed attributes and element values" do
|
1072
|
+
before do
|
1073
|
+
@xml = %[
|
1074
|
+
<item id="1">
|
1075
|
+
<title>sweet</title>
|
1076
|
+
<author role="writer">John Doe</author>
|
1077
|
+
<author role="artist">Jane Doe</author>
|
1078
|
+
</item>
|
1079
|
+
]
|
1080
|
+
|
1081
|
+
class AuthorElement4
|
1082
|
+
include Saxophone
|
1083
|
+
value :name
|
1084
|
+
attribute :role
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
class ItemElement4
|
1088
|
+
include Saxophone
|
1089
|
+
element :title
|
1090
|
+
elements :author, as: :authors, class: AuthorElement4
|
1091
|
+
|
1092
|
+
def title=(blah)
|
1093
|
+
@title = blah
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
@item = ItemElement4.parse(@xml)
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
it "has the title" do
|
1101
|
+
expect(@item.title).to eq("sweet")
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
it "has the child elements" do
|
1105
|
+
expect(@item.authors).not_to be_nil
|
1106
|
+
expect(@item.authors.count).to eq(2)
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
it "has the author names" do
|
1110
|
+
expect(@item.authors.first.name).to eq("John Doe")
|
1111
|
+
expect(@item.authors.last.name).to eq("Jane Doe")
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
it "has the author roles" do
|
1115
|
+
expect(@item.authors.first.role).to eq("writer")
|
1116
|
+
expect(@item.authors.last.role).to eq("artist")
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
describe "with multiple elements with the same alias" do
|
1121
|
+
let(:item) { ItemElement5.parse(xml) }
|
1122
|
+
|
1123
|
+
before do
|
1124
|
+
class ItemElement5
|
1125
|
+
include Saxophone
|
1126
|
+
element :pubDate, as: :published
|
1127
|
+
element :"dc:date", as: :published
|
1128
|
+
end
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
describe "only first defined" do
|
1132
|
+
let(:xml) { "<item xmlns:dc='http://www.example.com'><pubDate>first value</pubDate></item>" }
|
1133
|
+
|
1134
|
+
it "has first value" do
|
1135
|
+
expect(item.published).to eq("first value")
|
1136
|
+
end
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
describe "only last defined" do
|
1140
|
+
let(:xml) { "<item xmlns:dc='http://www.example.com'><dc:date>last value</dc:date></item>" }
|
1141
|
+
|
1142
|
+
it "has last value" do
|
1143
|
+
expect(item.published).to eq("last value")
|
1144
|
+
end
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
describe "both defined" do
|
1148
|
+
let(:xml) { "<item xmlns:dc='http://www.example.com'><pubDate>first value</pubDate><dc:date>last value</dc:date></item>" }
|
1149
|
+
|
1150
|
+
it "has last value" do
|
1151
|
+
expect(item.published).to eq("last value")
|
1152
|
+
end
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
describe "both defined but order is reversed" do
|
1156
|
+
let(:xml) { "<item xmlns:dc='http://www.example.com'><dc:date>last value</dc:date><pubDate>first value</pubDate></item>" }
|
1157
|
+
|
1158
|
+
it "has first value" do
|
1159
|
+
expect(item.published).to eq("first value")
|
1160
|
+
end
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
describe "both defined but last is empty" do
|
1164
|
+
let(:xml) { "<item xmlns:dc='http://www.example.com'><pubDate>first value</pubDate><dc:date></dc:date></item>" }
|
1165
|
+
|
1166
|
+
it "has first value" do
|
1167
|
+
expect(item.published).to eq("first value")
|
1168
|
+
end
|
1169
|
+
end
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
describe "with error handling" do
|
1173
|
+
before do
|
1174
|
+
@xml = %[
|
1175
|
+
<item id="1">
|
1176
|
+
<title>sweet</title>
|
1177
|
+
]
|
1178
|
+
|
1179
|
+
class ItemElement5
|
1180
|
+
include Saxophone
|
1181
|
+
element :title
|
1182
|
+
end
|
1183
|
+
|
1184
|
+
@errors = []
|
1185
|
+
@warnings = []
|
1186
|
+
@item = ItemElement5.parse(
|
1187
|
+
@xml,
|
1188
|
+
->(x) { @errors << x },
|
1189
|
+
->(x) { @warnings << x },
|
1190
|
+
)
|
1191
|
+
end
|
1192
|
+
|
1193
|
+
it "has error" do
|
1194
|
+
expect(@errors.uniq.size).to eq(1)
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
it "has no warning" do
|
1198
|
+
expect(@warnings.uniq.size).to eq(0)
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
describe "with io as a input" do
|
1203
|
+
before do
|
1204
|
+
@io = StringIO.new('<item id="1"><title>sweet</title></item>')
|
1205
|
+
|
1206
|
+
class IoParser
|
1207
|
+
include Saxophone
|
1208
|
+
element :title
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
@item = ItemElement5.parse(@io)
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
it "parses" do
|
1215
|
+
expect(@item.title).to eq("sweet")
|
1216
|
+
end
|
1217
|
+
end
|
1218
|
+
end
|