nori-ng-1.6 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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