nori-ng-1.6 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,630 @@
1
+ require "spec_helper"
2
+
3
+ describe Nori do
4
+
5
+ Nori::PARSERS.each do |parser, class_name|
6
+ context "using the :#{parser} parser" do
7
+
8
+ let(:parser) { parser }
9
+
10
+ it "should work with unnormalized characters" do
11
+ xml = '<root>&amp;</root>'
12
+ parse(xml).should == { 'root' => "&" }
13
+ end
14
+
15
+ it "should transform a simple tag with content" do
16
+ xml = "<tag>This is the contents</tag>"
17
+ parse(xml).should == { 'tag' => 'This is the contents' }
18
+ end
19
+
20
+ it "should work with cdata tags" do
21
+ xml = <<-END
22
+ <tag>
23
+ <![CDATA[
24
+ text inside cdata
25
+ ]]>
26
+ </tag>
27
+ END
28
+ parse(xml)["tag"].strip.should == "text inside cdata"
29
+ end
30
+
31
+ it "should transform a simple tag with attributes" do
32
+ xml = "<tag attr1='1' attr2='2'></tag>"
33
+ hash = { 'tag' => { '@attr1' => '1', '@attr2' => '2' } }
34
+ parse(xml).should == hash
35
+ end
36
+
37
+ it "should transform repeating siblings into an array" do
38
+ xml =<<-XML
39
+ <opt>
40
+ <user login="grep" fullname="Gary R Epstein" />
41
+ <user login="stty" fullname="Simon T Tyson" />
42
+ </opt>
43
+ XML
44
+
45
+ parse(xml)['opt']['user'].class.should == Array
46
+
47
+ hash = {
48
+ 'opt' => {
49
+ 'user' => [{
50
+ '@login' => 'grep',
51
+ '@fullname' => 'Gary R Epstein'
52
+ },{
53
+ '@login' => 'stty',
54
+ '@fullname' => 'Simon T Tyson'
55
+ }]
56
+ }
57
+ }
58
+
59
+ parse(xml).should == hash
60
+ end
61
+
62
+ it "should not transform non-repeating siblings into an array" do
63
+ xml =<<-XML
64
+ <opt>
65
+ <user login="grep" fullname="Gary R Epstein" />
66
+ </opt>
67
+ XML
68
+
69
+ parse(xml)['opt']['user'].class.should == Hash
70
+
71
+ hash = {
72
+ 'opt' => {
73
+ 'user' => {
74
+ '@login' => 'grep',
75
+ '@fullname' => 'Gary R Epstein'
76
+ }
77
+ }
78
+ }
79
+
80
+ parse(xml).should == hash
81
+ end
82
+
83
+ it "should prefix attributes with an @-sign to avoid problems with overwritten values" do
84
+ xml =<<-XML
85
+ <multiRef id="id1">
86
+ <login>grep</login>
87
+ <id>76737</id>
88
+ </multiRef>
89
+ XML
90
+
91
+ parse(xml)["multiRef"].should == { "login" => "grep", "@id" => "id1", "id" => "76737" }
92
+ end
93
+
94
+ context "without advanced typecasting" do
95
+ it "should not transform 'true'" do
96
+ hash = parse("<value>true</value>", :advanced_typecasting => false)
97
+ hash["value"].should == "true"
98
+ end
99
+
100
+ it "should not transform 'false'" do
101
+ hash = parse("<value>false</value>", :advanced_typecasting => false)
102
+ hash["value"].should == "false"
103
+ end
104
+
105
+ it "should not transform Strings matching the xs:time format" do
106
+ hash = parse("<value>09:33:55Z</value>", :advanced_typecasting => false)
107
+ hash["value"].should == "09:33:55Z"
108
+ end
109
+
110
+ it "should not transform Strings matching the xs:date format" do
111
+ hash = parse("<value>1955-04-18-05:00</value>", :advanced_typecasting => false)
112
+ hash["value"].should == "1955-04-18-05:00"
113
+ end
114
+
115
+ it "should not transform Strings matching the xs:dateTime format" do
116
+ hash = parse("<value>1955-04-18T11:22:33-05:00</value>", :advanced_typecasting => false)
117
+ hash["value"].should == "1955-04-18T11:22:33-05:00"
118
+ end
119
+ end
120
+
121
+ context "with advanced typecasting" do
122
+ it "should transform 'true' to TrueClass" do
123
+ parse("<value>true</value>")["value"].should == true
124
+ end
125
+
126
+ it "should transform 'false' to FalseClass" do
127
+ parse("<value>false</value>")["value"].should == false
128
+ end
129
+
130
+ it "should transform Strings matching the xs:time format to Time objects" do
131
+ parse("<value>09:33:55Z</value>")["value"].should == Time.parse("09:33:55Z")
132
+ end
133
+
134
+ it "should transform Strings matching the xs:time format ahead of utc to Time objects" do
135
+ parse("<value>09:33:55+02:00</value>")["value"].should == Time.parse("09:33:55+02:00")
136
+ end
137
+
138
+ it "should transform Strings matching the xs:date format to Date objects" do
139
+ parse("<value>1955-04-18-05:00</value>")["value"].should == Date.parse("1955-04-18-05:00")
140
+ end
141
+
142
+ it "should transform Strings matching the xs:dateTime format ahead of utc to Date objects" do
143
+ parse("<value>1955-04-18+02:00</value>")["value"].should == Date.parse("1955-04-18+02:00")
144
+ end
145
+
146
+ it "should transform Strings matching the xs:dateTime format to DateTime objects" do
147
+ parse("<value>1955-04-18T11:22:33-05:00</value>")["value"].should ==
148
+ DateTime.parse("1955-04-18T11:22:33-05:00")
149
+ end
150
+
151
+ it "should transform Strings matching the xs:dateTime format ahead of utc to DateTime objects" do
152
+ parse("<value>1955-04-18T11:22:33+02:00</value>")["value"].should ==
153
+ DateTime.parse("1955-04-18T11:22:33+02:00")
154
+ end
155
+
156
+ it "should transform Strings matching the xs:dateTime format with seconds and an offset to DateTime objects" do
157
+ parse("<value>2004-04-12T13:20:15.5-05:00</value>")["value"].should ==
158
+ DateTime.parse("2004-04-12T13:20:15.5-05:00")
159
+ end
160
+
161
+ it "should not transform Strings containing an xs:time String and more" do
162
+ parse("<value>09:33:55Z is a time</value>")["value"].should == "09:33:55Z is a time"
163
+ parse("<value>09:33:55Z_is_a_file_name</value>")["value"].should == "09:33:55Z_is_a_file_name"
164
+ end
165
+
166
+ it "should not transform Strings containing an xs:date String and more" do
167
+ parse("<value>1955-04-18-05:00 is a date</value>")["value"].should == "1955-04-18-05:00 is a date"
168
+ parse("<value>1955-04-18-05:00_is_a_file_name</value>")["value"].should == "1955-04-18-05:00_is_a_file_name"
169
+ end
170
+
171
+ it "should not transform Strings containing an xs:dateTime String and more" do
172
+ parse("<value>1955-04-18T11:22:33-05:00 is a dateTime</value>")["value"].should ==
173
+ "1955-04-18T11:22:33-05:00 is a dateTime"
174
+ parse("<value>1955-04-18T11:22:33-05:00_is_a_file_name</value>")["value"].should ==
175
+ "1955-04-18T11:22:33-05:00_is_a_file_name"
176
+ end
177
+
178
+ ["00-00-00", "0000-00-00", "0000-00-00T00:00:00", "0569-23-0141", "DS2001-19-1312654773", "e6:53:01:00:ce:b4:06"].each do |date_string|
179
+ it "should not transform a String like '#{date_string}' to date or time" do
180
+ parse("<value>#{date_string}</value>")["value"].should == date_string
181
+ end
182
+ end
183
+ end
184
+
185
+ context "Parsing xml with text and attributes" do
186
+ before do
187
+ xml =<<-XML
188
+ <opt>
189
+ <user login="grep">Gary R Epstein</user>
190
+ <user>Simon T Tyson</user>
191
+ </opt>
192
+ XML
193
+ @data = parse(xml)
194
+ end
195
+
196
+ it "correctly parse text nodes" do
197
+ @data.should == {
198
+ 'opt' => {
199
+ 'user' => [
200
+ 'Gary R Epstein',
201
+ 'Simon T Tyson'
202
+ ]
203
+ }
204
+ }
205
+ end
206
+
207
+ it "be parse attributes for text node if present" do
208
+ @data['opt']['user'][0].attributes.should == {'login' => 'grep'}
209
+ end
210
+
211
+ it "default attributes to empty hash if not present" do
212
+ @data['opt']['user'][1].attributes.should == {}
213
+ end
214
+
215
+ it "add 'attributes' accessor methods to parsed instances of String" do
216
+ @data['opt']['user'][0].should respond_to(:attributes)
217
+ @data['opt']['user'][0].should respond_to(:attributes=)
218
+ end
219
+
220
+ it "not add 'attributes' accessor methods to all instances of String" do
221
+ "some-string".should_not respond_to(:attributes)
222
+ "some-string".should_not respond_to(:attributes=)
223
+ end
224
+ end
225
+
226
+ it "should typecast an integer" do
227
+ xml = "<tag type='integer'>10</tag>"
228
+ parse(xml)['tag'].should == 10
229
+ end
230
+
231
+ it "should typecast a true boolean" do
232
+ xml = "<tag type='boolean'>true</tag>"
233
+ parse(xml)['tag'].should be(true)
234
+ end
235
+
236
+ it "should typecast a false boolean" do
237
+ ["false"].each do |w|
238
+ parse("<tag type='boolean'>#{w}</tag>")['tag'].should be(false)
239
+ end
240
+ end
241
+
242
+ it "should typecast a datetime" do
243
+ xml = "<tag type='datetime'>2007-12-31 10:32</tag>"
244
+ parse(xml)['tag'].should == Time.parse( '2007-12-31 10:32' ).utc
245
+ end
246
+
247
+ it "should typecast a date" do
248
+ xml = "<tag type='date'>2007-12-31</tag>"
249
+ parse(xml)['tag'].should == Date.parse('2007-12-31')
250
+ end
251
+
252
+ xml_entities = {
253
+ "<" => "&lt;",
254
+ ">" => "&gt;",
255
+ '"' => "&quot;",
256
+ "'" => "&apos;",
257
+ "&" => "&amp;"
258
+ }
259
+
260
+ it "should unescape html entities" do
261
+ xml_entities.each do |k,v|
262
+ xml = "<tag>Some content #{v}</tag>"
263
+ parse(xml)['tag'].should =~ Regexp.new(k)
264
+ end
265
+ end
266
+
267
+ it "should unescape XML entities in attributes" do
268
+ xml_entities.each do |key, value|
269
+ xml = "<tag attr='Some content #{value}'></tag>"
270
+ parse(xml)['tag']['@attr'].should =~ Regexp.new(key)
271
+ end
272
+ end
273
+
274
+ it "should undasherize keys as tags" do
275
+ xml = "<tag-1>Stuff</tag-1>"
276
+ parse(xml).keys.should include('tag_1')
277
+ end
278
+
279
+ it "should undasherize keys as attributes" do
280
+ xml = "<tag1 attr-1='1'></tag1>"
281
+ parse(xml)['tag1'].keys.should include('@attr_1')
282
+ end
283
+
284
+ it "should undasherize keys as tags and attributes" do
285
+ xml = "<tag-1 attr-1='1'></tag-1>"
286
+ parse(xml).keys.should include('tag_1')
287
+ parse(xml)['tag_1'].keys.should include('@attr_1')
288
+ end
289
+
290
+ it "should render nested content correctly" do
291
+ xml = "<root><tag1>Tag1 Content <em><strong>This is strong</strong></em></tag1></root>"
292
+ parse(xml)['root']['tag1'].should == "Tag1 Content <em><strong>This is strong</strong></em>"
293
+ end
294
+
295
+ it "should render nested content with splshould text nodes correctly" do
296
+ xml = "<root>Tag1 Content<em>Stuff</em> Hi There</root>"
297
+ parse(xml)['root'].should == "Tag1 Content<em>Stuff</em> Hi There"
298
+ end
299
+
300
+ it "should ignore attributes when a child is a text node" do
301
+ xml = "<root attr1='1'>Stuff</root>"
302
+ parse(xml).should == { "root" => "Stuff" }
303
+ end
304
+
305
+ it "should ignore attributes when any child is a text node" do
306
+ xml = "<root attr1='1'>Stuff <em>in italics</em></root>"
307
+ parse(xml).should == { "root" => "Stuff <em>in italics</em>" }
308
+ end
309
+
310
+ it "should correctly transform multiple children" do
311
+ xml = <<-XML
312
+ <user gender='m'>
313
+ <age type='integer'>35</age>
314
+ <name>Home Simpson</name>
315
+ <dob type='date'>1988-01-01</dob>
316
+ <joined-at type='datetime'>2000-04-28 23:01</joined-at>
317
+ <is-cool type='boolean'>true</is-cool>
318
+ </user>
319
+ XML
320
+
321
+ hash = {
322
+ "user" => {
323
+ "@gender" => "m",
324
+ "age" => 35,
325
+ "name" => "Home Simpson",
326
+ "dob" => Date.parse('1988-01-01'),
327
+ "joined_at" => Time.parse("2000-04-28 23:01"),
328
+ "is_cool" => true
329
+ }
330
+ }
331
+
332
+ parse(xml).should == hash
333
+ end
334
+
335
+ it "should properly handle nil values (ActiveSupport Compatible)" do
336
+ topic_xml = <<-EOT
337
+ <topic>
338
+ <title></title>
339
+ <id type="integer"></id>
340
+ <approved type="boolean"></approved>
341
+ <written-on type="date"></written-on>
342
+ <viewed-at type="datetime"></viewed-at>
343
+ <content type="yaml"></content>
344
+ <parent-id></parent-id>
345
+ <nil_true nil="true"/>
346
+ <namespaced xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
347
+ </topic>
348
+ EOT
349
+
350
+ expected_topic_hash = {
351
+ 'title' => nil,
352
+ 'id' => nil,
353
+ 'approved' => nil,
354
+ 'written_on' => nil,
355
+ 'viewed_at' => nil,
356
+ # don't execute arbitary YAML code
357
+ 'content' => { "@type" => "yaml" },
358
+ 'parent_id' => nil,
359
+ 'nil_true' => nil,
360
+ 'namespaced' => nil
361
+ }
362
+ parse(topic_xml)["topic"].should == expected_topic_hash
363
+ end
364
+
365
+ it "should handle a single record from xml (ActiveSupport Compatible)" do
366
+ topic_xml = <<-EOT
367
+ <topic>
368
+ <title>The First Topic</title>
369
+ <author-name>David</author-name>
370
+ <id type="integer">1</id>
371
+ <approved type="boolean"> true </approved>
372
+ <replies-count type="integer">0</replies-count>
373
+ <replies-close-in type="integer">2592000000</replies-close-in>
374
+ <written-on type="date">2003-07-16</written-on>
375
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
376
+ <content type="yaml">--- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n should_have_underscores: true</content>
377
+ <author-email-address>david@loudthinking.com</author-email-address>
378
+ <parent-id></parent-id>
379
+ <ad-revenue type="decimal">1.5</ad-revenue>
380
+ <optimum-viewing-angle type="float">135</optimum-viewing-angle>
381
+ <resident type="symbol">yes</resident>
382
+ </topic>
383
+ EOT
384
+
385
+ expected_topic_hash = {
386
+ 'title' => "The First Topic",
387
+ 'author_name' => "David",
388
+ 'id' => 1,
389
+ 'approved' => true,
390
+ 'replies_count' => 0,
391
+ 'replies_close_in' => 2592000000,
392
+ 'written_on' => Date.new(2003, 7, 16),
393
+ 'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
394
+ # Changed this line where the key is :message. The yaml specifies this as a symbol, and who am I to change what you specify
395
+ # The line in ActiveSupport is
396
+ # 'content' => { 'message' => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
397
+ 'content' => "--- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n should_have_underscores: true",
398
+ 'author_email_address' => "david@loudthinking.com",
399
+ 'parent_id' => nil,
400
+ 'ad_revenue' => BigDecimal("1.50"),
401
+ 'optimum_viewing_angle' => 135.0,
402
+ # don't create symbols from arbitary remote code
403
+ 'resident' => "yes"
404
+ }
405
+
406
+ parse(topic_xml)["topic"].each do |k,v|
407
+ v.should == expected_topic_hash[k]
408
+ end
409
+ end
410
+
411
+ it "should handle multiple records (ActiveSupport Compatible)" do
412
+ topics_xml = <<-EOT
413
+ <topics type="array">
414
+ <topic>
415
+ <title>The First Topic</title>
416
+ <author-name>David</author-name>
417
+ <id type="integer">1</id>
418
+ <approved type="boolean">false</approved>
419
+ <replies-count type="integer">0</replies-count>
420
+ <replies-close-in type="integer">2592000000</replies-close-in>
421
+ <written-on type="date">2003-07-16</written-on>
422
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
423
+ <content>Have a nice day</content>
424
+ <author-email-address>david@loudthinking.com</author-email-address>
425
+ <parent-id nil="true"></parent-id>
426
+ </topic>
427
+ <topic>
428
+ <title>The Second Topic</title>
429
+ <author-name>Jason</author-name>
430
+ <id type="integer">1</id>
431
+ <approved type="boolean">false</approved>
432
+ <replies-count type="integer">0</replies-count>
433
+ <replies-close-in type="integer">2592000000</replies-close-in>
434
+ <written-on type="date">2003-07-16</written-on>
435
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
436
+ <content>Have a nice day</content>
437
+ <author-email-address>david@loudthinking.com</author-email-address>
438
+ <parent-id></parent-id>
439
+ </topic>
440
+ </topics>
441
+ EOT
442
+
443
+ expected_topic_hash = {
444
+ 'title' => "The First Topic",
445
+ 'author_name' => "David",
446
+ 'id' => 1,
447
+ 'approved' => false,
448
+ 'replies_count' => 0,
449
+ 'replies_close_in' => 2592000000,
450
+ 'written_on' => Date.new(2003, 7, 16),
451
+ 'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
452
+ 'content' => "Have a nice day",
453
+ 'author_email_address' => "david@loudthinking.com",
454
+ 'parent_id' => nil
455
+ }
456
+
457
+ # puts Nori.parse(topics_xml)['topics'].first.inspect
458
+ parse(topics_xml)["topics"].first.each do |k,v|
459
+ v.should == expected_topic_hash[k]
460
+ end
461
+ end
462
+
463
+ it "should handle a single record from_xml with attributes other than type (ActiveSupport Compatible)" do
464
+ topic_xml = <<-EOT
465
+ <rsp stat="ok">
466
+ <photos page="1" pages="1" perpage="100" total="16">
467
+ <photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
468
+ </photos>
469
+ </rsp>
470
+ EOT
471
+
472
+ expected_topic_hash = {
473
+ '@id' => "175756086",
474
+ '@owner' => "55569174@N00",
475
+ '@secret' => "0279bf37a1",
476
+ '@server' => "76",
477
+ '@title' => "Colored Pencil PhotoBooth Fun",
478
+ '@ispublic' => "1",
479
+ '@isfriend' => "0",
480
+ '@isfamily' => "0",
481
+ }
482
+
483
+ parse(topic_xml)["rsp"]["photos"]["photo"].each do |k, v|
484
+ v.should == expected_topic_hash[k]
485
+ end
486
+ end
487
+
488
+ it "should handle an emtpy array (ActiveSupport Compatible)" do
489
+ blog_xml = <<-XML
490
+ <blog>
491
+ <posts type="array"></posts>
492
+ </blog>
493
+ XML
494
+ expected_blog_hash = {"blog" => {"posts" => []}}
495
+ parse(blog_xml).should == expected_blog_hash
496
+ end
497
+
498
+ it "should handle empty array with whitespace from xml (ActiveSupport Compatible)" do
499
+ blog_xml = <<-XML
500
+ <blog>
501
+ <posts type="array">
502
+ </posts>
503
+ </blog>
504
+ XML
505
+ expected_blog_hash = {"blog" => {"posts" => []}}
506
+ parse(blog_xml).should == expected_blog_hash
507
+ end
508
+
509
+ it "should handle array with one entry from_xml (ActiveSupport Compatible)" do
510
+ blog_xml = <<-XML
511
+ <blog>
512
+ <posts type="array">
513
+ <post>a post</post>
514
+ </posts>
515
+ </blog>
516
+ XML
517
+ expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
518
+ parse(blog_xml).should == expected_blog_hash
519
+ end
520
+
521
+ it "should handle array with multiple entries from xml (ActiveSupport Compatible)" do
522
+ blog_xml = <<-XML
523
+ <blog>
524
+ <posts type="array">
525
+ <post>a post</post>
526
+ <post>another post</post>
527
+ </posts>
528
+ </blog>
529
+ XML
530
+ expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
531
+ parse(blog_xml).should == expected_blog_hash
532
+ end
533
+
534
+ it "should handle file types (ActiveSupport Compatible)" do
535
+ blog_xml = <<-XML
536
+ <blog>
537
+ <logo type="file" name="logo.png" content_type="image/png">
538
+ </logo>
539
+ </blog>
540
+ XML
541
+ hash = parse(blog_xml)
542
+ hash.keys.should include('blog')
543
+ hash['blog'].keys.should include('logo')
544
+
545
+ file = hash['blog']['logo']
546
+ file.original_filename.should == 'logo.png'
547
+ file.content_type.should == 'image/png'
548
+ end
549
+
550
+ it "should handle file from xml with defaults (ActiveSupport Compatible)" do
551
+ blog_xml = <<-XML
552
+ <blog>
553
+ <logo type="file">
554
+ </logo>
555
+ </blog>
556
+ XML
557
+ file = parse(blog_xml)['blog']['logo']
558
+ file.original_filename.should == 'untitled'
559
+ file.content_type.should == 'application/octet-stream'
560
+ end
561
+
562
+ it "should handle xsd like types from xml (ActiveSupport Compatible)" do
563
+ bacon_xml = <<-EOT
564
+ <bacon>
565
+ <weight type="double">0.5</weight>
566
+ <price type="decimal">12.50</price>
567
+ <chunky type="boolean"> 1 </chunky>
568
+ <expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
569
+ <notes type="string"></notes>
570
+ <illustration type="base64Binary">YmFiZS5wbmc=</illustration>
571
+ </bacon>
572
+ EOT
573
+
574
+ expected_bacon_hash = {
575
+ 'weight' => 0.5,
576
+ 'chunky' => true,
577
+ 'price' => BigDecimal("12.50"),
578
+ 'expires_at' => Time.utc(2007,12,25,12,34,56),
579
+ 'notes' => "",
580
+ 'illustration' => "babe.png"
581
+ }
582
+
583
+ parse(bacon_xml)["bacon"].should == expected_bacon_hash
584
+ end
585
+
586
+ it "should let type trickle through when unknown (ActiveSupport Compatible)" do
587
+ product_xml = <<-EOT
588
+ <product>
589
+ <weight type="double">0.5</weight>
590
+ <image type="ProductImage"><filename>image.gif</filename></image>
591
+
592
+ </product>
593
+ EOT
594
+
595
+ expected_product_hash = {
596
+ 'weight' => 0.5,
597
+ 'image' => {'@type' => 'ProductImage', 'filename' => 'image.gif' },
598
+ }
599
+
600
+ parse(product_xml)["product"].should == expected_product_hash
601
+ end
602
+
603
+ it "should handle unescaping from xml (ActiveResource Compatible)" #do
604
+ # xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
605
+ # expected_hash = {
606
+ # 'bare_string' => 'First & Last Name',
607
+ # 'pre_escaped_string' => 'First &amp; Last Name'
608
+ # }
609
+ #
610
+ # parse(xml_string)['person'].should == expected_hash
611
+ # end
612
+
613
+ it "handle an empty xml string" do
614
+ parse('').should == {}
615
+ end
616
+
617
+ # As returned in the response body by the unfuddle XML API when creating objects
618
+ it "handle an xml string containing a single space" do
619
+ parse(' ').should == {}
620
+ end
621
+
622
+ end
623
+ end
624
+
625
+ def parse(xml, options = {})
626
+ defaults = { :parser => parser }
627
+ Nori.new(defaults.merge(options)).parse(xml)
628
+ end
629
+
630
+ end
@@ -0,0 +1,2 @@
1
+ require "bundler"
2
+ Bundler.require :default, :development