sax-machine 0.0.16 → 1.3.2

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