nori 0.2.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.

Potentially problematic release.


This version of nori might be problematic. Click here for more details.

@@ -0,0 +1,15 @@
1
+ require "spec_helper"
2
+
3
+ describe Object do
4
+
5
+ describe "#blank?" do
6
+ it "should return true for blank objects" do
7
+ [nil, false, [], {}].each { |object| object.should be_blank }
8
+ end
9
+
10
+ it "should return false for non-blank objects" do
11
+ [true, [nil], 1, "string", { :key => "value" }].each { |object| object.should_not be_blank }
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe String do
4
+
5
+ describe "#snake_case" do
6
+ it "lowercases one word CamelCase" do
7
+ "Merb".snake_case.should == "merb"
8
+ end
9
+
10
+ it "makes one underscore snake_case two word CamelCase" do
11
+ "MerbCore".snake_case.should == "merb_core"
12
+ end
13
+
14
+ it "handles CamelCase with more than 2 words" do
15
+ "SoYouWantContributeToMerbCore".snake_case.should == "so_you_want_contribute_to_merb_core"
16
+ end
17
+
18
+ it "handles CamelCase with more than 2 capital letter in a row" do
19
+ "CNN".snake_case.should == "cnn"
20
+ "CNNNews".snake_case.should == "cnn_news"
21
+ "HeadlineCNNNews".snake_case.should == "headline_cnn_news"
22
+ end
23
+
24
+ it "does NOT change one word lowercase" do
25
+ "merb".snake_case.should == "merb"
26
+ end
27
+
28
+ it "leaves snake_case as is" do
29
+ "merb_core".snake_case.should == "merb_core"
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,506 @@
1
+ require "spec_helper"
2
+
3
+ describe Nori do
4
+
5
+ Nori::Parser::PARSERS.each do |parser, class_name|
6
+ context "using the :#{parser} parser" do
7
+
8
+ let(:parser) { parser }
9
+
10
+ it "should transform a simple tag with content" do
11
+ xml = "<tag>This is the contents</tag>"
12
+ parse(xml).should == { 'tag' => 'This is the contents' }
13
+ end
14
+
15
+ it "should work with cdata tags" do
16
+ xml = <<-END
17
+ <tag>
18
+ <![CDATA[
19
+ text inside cdata
20
+ ]]>
21
+ </tag>
22
+ END
23
+ parse(xml)["tag"].strip.should == "text inside cdata"
24
+ end
25
+
26
+ it "should transform a simple tag with attributes" do
27
+ xml = "<tag attr1='1' attr2='2'></tag>"
28
+ hash = { 'tag' => { 'attr1' => '1', 'attr2' => '2' } }
29
+ parse(xml).should == hash
30
+ end
31
+
32
+ it "should transform repeating siblings into an array" do
33
+ xml =<<-XML
34
+ <opt>
35
+ <user login="grep" fullname="Gary R Epstein" />
36
+ <user login="stty" fullname="Simon T Tyson" />
37
+ </opt>
38
+ XML
39
+
40
+ parse(xml)['opt']['user'].class.should == Array
41
+
42
+ hash = {
43
+ 'opt' => {
44
+ 'user' => [{
45
+ 'login' => 'grep',
46
+ 'fullname' => 'Gary R Epstein'
47
+ },{
48
+ 'login' => 'stty',
49
+ 'fullname' => 'Simon T Tyson'
50
+ }]
51
+ }
52
+ }
53
+
54
+ parse(xml).should == hash
55
+ end
56
+
57
+ it "should not transform non-repeating siblings into an array" do
58
+ xml =<<-XML
59
+ <opt>
60
+ <user login="grep" fullname="Gary R Epstein" />
61
+ </opt>
62
+ XML
63
+
64
+ parse(xml)['opt']['user'].class.should == Hash
65
+
66
+ hash = {
67
+ 'opt' => {
68
+ 'user' => {
69
+ 'login' => 'grep',
70
+ 'fullname' => 'Gary R Epstein'
71
+ }
72
+ }
73
+ }
74
+
75
+ parse(xml).should == hash
76
+ end
77
+
78
+ context "Parsing xml with text and attributes" do
79
+ before do
80
+ xml =<<-XML
81
+ <opt>
82
+ <user login="grep">Gary R Epstein</user>
83
+ <user>Simon T Tyson</user>
84
+ </opt>
85
+ XML
86
+ @data = parse(xml)
87
+ end
88
+
89
+ it "correctly parse text nodes" do
90
+ @data.should == {
91
+ 'opt' => {
92
+ 'user' => [
93
+ 'Gary R Epstein',
94
+ 'Simon T Tyson'
95
+ ]
96
+ }
97
+ }
98
+ end
99
+
100
+ it "be parse attributes for text node if present" do
101
+ @data['opt']['user'][0].attributes.should == {'login' => 'grep'}
102
+ end
103
+
104
+ it "default attributes to empty hash if not present" do
105
+ @data['opt']['user'][1].attributes.should == {}
106
+ end
107
+ end
108
+
109
+ it "should typecast an integer" do
110
+ xml = "<tag type='integer'>10</tag>"
111
+ parse(xml)['tag'].should == 10
112
+ end
113
+
114
+ it "should typecast a true boolean" do
115
+ xml = "<tag type='boolean'>true</tag>"
116
+ parse(xml)['tag'].should be(true)
117
+ end
118
+
119
+ it "should typecast a false boolean" do
120
+ ["false"].each do |w|
121
+ parse("<tag type='boolean'>#{w}</tag>")['tag'].should be(false)
122
+ end
123
+ end
124
+
125
+ it "should typecast a datetime" do
126
+ xml = "<tag type='datetime'>2007-12-31 10:32</tag>"
127
+ parse(xml)['tag'].should == Time.parse( '2007-12-31 10:32' ).utc
128
+ end
129
+
130
+ it "should typecast a date" do
131
+ xml = "<tag type='date'>2007-12-31</tag>"
132
+ parse(xml)['tag'].should == Date.parse('2007-12-31')
133
+ end
134
+
135
+ xml_entities = {
136
+ "<" => "&lt;",
137
+ ">" => "&gt;",
138
+ '"' => "&quot;",
139
+ "'" => "&apos;",
140
+ "&" => "&amp;"
141
+ }
142
+
143
+ it "should unescape html entities" do
144
+ xml_entities.each do |k,v|
145
+ xml = "<tag>Some content #{v}</tag>"
146
+ parse(xml)['tag'].should =~ Regexp.new(k)
147
+ end
148
+ end
149
+
150
+ it "should unescape XML entities in attributes" do
151
+ xml_entities.each do |k,v|
152
+ xml = "<tag attr='Some content #{v}'></tag>"
153
+ parse(xml)['tag']['attr'].should =~ Regexp.new(k)
154
+ end
155
+ end
156
+
157
+ it "should undasherize keys as tags" do
158
+ xml = "<tag-1>Stuff</tag-1>"
159
+ parse(xml).keys.should include( 'tag_1' )
160
+ end
161
+
162
+ it "should undasherize keys as attributes" do
163
+ xml = "<tag1 attr-1='1'></tag1>"
164
+ parse(xml)['tag1'].keys.should include( 'attr_1')
165
+ end
166
+
167
+ it "should undasherize keys as tags and attributes" do
168
+ xml = "<tag-1 attr-1='1'></tag-1>"
169
+ parse(xml).keys.should include( 'tag_1' )
170
+ parse(xml)['tag_1'].keys.should include( 'attr_1')
171
+ end
172
+
173
+ it "should render nested content correctly" do
174
+ xml = "<root><tag1>Tag1 Content <em><strong>This is strong</strong></em></tag1></root>"
175
+ parse(xml)['root']['tag1'].should == "Tag1 Content <em><strong>This is strong</strong></em>"
176
+ end
177
+
178
+ it "should render nested content with splshould text nodes correctly" do
179
+ xml = "<root>Tag1 Content<em>Stuff</em> Hi There</root>"
180
+ parse(xml)['root'].should == "Tag1 Content<em>Stuff</em> Hi There"
181
+ end
182
+
183
+ it "should ignore attributes when a child is a text node" do
184
+ xml = "<root attr1='1'>Stuff</root>"
185
+ parse(xml).should == { "root" => "Stuff" }
186
+ end
187
+
188
+ it "should ignore attributes when any child is a text node" do
189
+ xml = "<root attr1='1'>Stuff <em>in italics</em></root>"
190
+ parse(xml).should == { "root" => "Stuff <em>in italics</em>" }
191
+ end
192
+
193
+ it "should correctly transform multiple children" do
194
+ xml = <<-XML
195
+ <user gender='m'>
196
+ <age type='integer'>35</age>
197
+ <name>Home Simpson</name>
198
+ <dob type='date'>1988-01-01</dob>
199
+ <joined-at type='datetime'>2000-04-28 23:01</joined-at>
200
+ <is-cool type='boolean'>true</is-cool>
201
+ </user>
202
+ XML
203
+
204
+ hash = {
205
+ "user" => {
206
+ "gender" => "m",
207
+ "age" => 35,
208
+ "name" => "Home Simpson",
209
+ "dob" => Date.parse('1988-01-01'),
210
+ "joined_at" => Time.parse("2000-04-28 23:01"),
211
+ "is_cool" => true
212
+ }
213
+ }
214
+
215
+ parse(xml).should == hash
216
+ end
217
+
218
+ it "should properly handle nil values (ActiveSupport Compatible)" do
219
+ topic_xml = <<-EOT
220
+ <topic>
221
+ <title></title>
222
+ <id type="integer"></id>
223
+ <approved type="boolean"></approved>
224
+ <written-on type="date"></written-on>
225
+ <viewed-at type="datetime"></viewed-at>
226
+ <content type="yaml"></content>
227
+ <parent-id></parent-id>
228
+ </topic>
229
+ EOT
230
+
231
+ expected_topic_hash = {
232
+ 'title' => nil,
233
+ 'id' => nil,
234
+ 'approved' => nil,
235
+ 'written_on' => nil,
236
+ 'viewed_at' => nil,
237
+ 'content' => nil,
238
+ 'parent_id' => nil
239
+ }
240
+ parse(topic_xml)["topic"].should == expected_topic_hash
241
+ end
242
+
243
+ it "should handle a single record from xml (ActiveSupport Compatible)" do
244
+ topic_xml = <<-EOT
245
+ <topic>
246
+ <title>The First Topic</title>
247
+ <author-name>David</author-name>
248
+ <id type="integer">1</id>
249
+ <approved type="boolean"> true </approved>
250
+ <replies-count type="integer">0</replies-count>
251
+ <replies-close-in type="integer">2592000000</replies-close-in>
252
+ <written-on type="date">2003-07-16</written-on>
253
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
254
+ <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\n</content>
255
+ <author-email-address>david@loudthinking.com</author-email-address>
256
+ <parent-id></parent-id>
257
+ <ad-revenue type="decimal">1.5</ad-revenue>
258
+ <optimum-viewing-angle type="float">135</optimum-viewing-angle>
259
+ <resident type="symbol">yes</resident>
260
+ </topic>
261
+ EOT
262
+
263
+ expected_topic_hash = {
264
+ 'title' => "The First Topic",
265
+ 'author_name' => "David",
266
+ 'id' => 1,
267
+ 'approved' => true,
268
+ 'replies_count' => 0,
269
+ 'replies_close_in' => 2592000000,
270
+ 'written_on' => Date.new(2003, 7, 16),
271
+ 'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
272
+ # Changed this line where the key is :message. The yaml specifies this as a symbol, and who am I to change what you specify
273
+ # The line in ActiveSupport is
274
+ # 'content' => { 'message' => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
275
+ 'content' => { :message => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
276
+ 'author_email_address' => "david@loudthinking.com",
277
+ 'parent_id' => nil,
278
+ 'ad_revenue' => BigDecimal("1.50"),
279
+ 'optimum_viewing_angle' => 135.0,
280
+ 'resident' => :yes
281
+ }
282
+
283
+ parse(topic_xml)["topic"].each do |k,v|
284
+ v.should == expected_topic_hash[k]
285
+ end
286
+ end
287
+
288
+ it "should handle multiple records (ActiveSupport Compatible)" do
289
+ topics_xml = <<-EOT
290
+ <topics type="array">
291
+ <topic>
292
+ <title>The First Topic</title>
293
+ <author-name>David</author-name>
294
+ <id type="integer">1</id>
295
+ <approved type="boolean">false</approved>
296
+ <replies-count type="integer">0</replies-count>
297
+ <replies-close-in type="integer">2592000000</replies-close-in>
298
+ <written-on type="date">2003-07-16</written-on>
299
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
300
+ <content>Have a nice day</content>
301
+ <author-email-address>david@loudthinking.com</author-email-address>
302
+ <parent-id nil="true"></parent-id>
303
+ </topic>
304
+ <topic>
305
+ <title>The Second Topic</title>
306
+ <author-name>Jason</author-name>
307
+ <id type="integer">1</id>
308
+ <approved type="boolean">false</approved>
309
+ <replies-count type="integer">0</replies-count>
310
+ <replies-close-in type="integer">2592000000</replies-close-in>
311
+ <written-on type="date">2003-07-16</written-on>
312
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
313
+ <content>Have a nice day</content>
314
+ <author-email-address>david@loudthinking.com</author-email-address>
315
+ <parent-id></parent-id>
316
+ </topic>
317
+ </topics>
318
+ EOT
319
+
320
+ expected_topic_hash = {
321
+ 'title' => "The First Topic",
322
+ 'author_name' => "David",
323
+ 'id' => 1,
324
+ 'approved' => false,
325
+ 'replies_count' => 0,
326
+ 'replies_close_in' => 2592000000,
327
+ 'written_on' => Date.new(2003, 7, 16),
328
+ 'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
329
+ 'content' => "Have a nice day",
330
+ 'author_email_address' => "david@loudthinking.com",
331
+ 'parent_id' => nil
332
+ }
333
+
334
+ # puts Nori.parse(topics_xml)['topics'].first.inspect
335
+ parse(topics_xml)["topics"].first.each do |k,v|
336
+ v.should == expected_topic_hash[k]
337
+ end
338
+ end
339
+
340
+ it "should handle a single record from_xml with attributes other than type (ActiveSupport Compatible)" do
341
+ topic_xml = <<-EOT
342
+ <rsp stat="ok">
343
+ <photos page="1" pages="1" perpage="100" total="16">
344
+ <photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
345
+ </photos>
346
+ </rsp>
347
+ EOT
348
+
349
+ expected_topic_hash = {
350
+ 'id' => "175756086",
351
+ 'owner' => "55569174@N00",
352
+ 'secret' => "0279bf37a1",
353
+ 'server' => "76",
354
+ 'title' => "Colored Pencil PhotoBooth Fun",
355
+ 'ispublic' => "1",
356
+ 'isfriend' => "0",
357
+ 'isfamily' => "0",
358
+ }
359
+
360
+ parse(topic_xml)["rsp"]["photos"]["photo"].each do |k, v|
361
+ v.should == expected_topic_hash[k]
362
+ end
363
+ end
364
+
365
+ it "should handle an emtpy array (ActiveSupport Compatible)" do
366
+ blog_xml = <<-XML
367
+ <blog>
368
+ <posts type="array"></posts>
369
+ </blog>
370
+ XML
371
+ expected_blog_hash = {"blog" => {"posts" => []}}
372
+ parse(blog_xml).should == expected_blog_hash
373
+ end
374
+
375
+ it "should handle empty array with whitespace from xml (ActiveSupport Compatible)" do
376
+ blog_xml = <<-XML
377
+ <blog>
378
+ <posts type="array">
379
+ </posts>
380
+ </blog>
381
+ XML
382
+ expected_blog_hash = {"blog" => {"posts" => []}}
383
+ parse(blog_xml).should == expected_blog_hash
384
+ end
385
+
386
+ it "should handle array with one entry from_xml (ActiveSupport Compatible)" do
387
+ blog_xml = <<-XML
388
+ <blog>
389
+ <posts type="array">
390
+ <post>a post</post>
391
+ </posts>
392
+ </blog>
393
+ XML
394
+ expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
395
+ parse(blog_xml).should == expected_blog_hash
396
+ end
397
+
398
+ it "should handle array with multiple entries from xml (ActiveSupport Compatible)" do
399
+ blog_xml = <<-XML
400
+ <blog>
401
+ <posts type="array">
402
+ <post>a post</post>
403
+ <post>another post</post>
404
+ </posts>
405
+ </blog>
406
+ XML
407
+ expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
408
+ parse(blog_xml).should == expected_blog_hash
409
+ end
410
+
411
+ it "should handle file types (ActiveSupport Compatible)" do
412
+ blog_xml = <<-XML
413
+ <blog>
414
+ <logo type="file" name="logo.png" content_type="image/png">
415
+ </logo>
416
+ </blog>
417
+ XML
418
+ hash = parse(blog_xml)
419
+ hash.keys.should include('blog')
420
+ hash['blog'].keys.should include('logo')
421
+
422
+ file = hash['blog']['logo']
423
+ file.original_filename.should == 'logo.png'
424
+ file.content_type.should == 'image/png'
425
+ end
426
+
427
+ it "should handle file from xml with defaults (ActiveSupport Compatible)" do
428
+ blog_xml = <<-XML
429
+ <blog>
430
+ <logo type="file">
431
+ </logo>
432
+ </blog>
433
+ XML
434
+ file = parse(blog_xml)['blog']['logo']
435
+ file.original_filename.should == 'untitled'
436
+ file.content_type.should == 'application/octet-stream'
437
+ end
438
+
439
+ it "should handle xsd like types from xml (ActiveSupport Compatible)" do
440
+ bacon_xml = <<-EOT
441
+ <bacon>
442
+ <weight type="double">0.5</weight>
443
+ <price type="decimal">12.50</price>
444
+ <chunky type="boolean"> 1 </chunky>
445
+ <expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
446
+ <notes type="string"></notes>
447
+ <illustration type="base64Binary">YmFiZS5wbmc=</illustration>
448
+ </bacon>
449
+ EOT
450
+
451
+ expected_bacon_hash = {
452
+ 'weight' => 0.5,
453
+ 'chunky' => true,
454
+ 'price' => BigDecimal("12.50"),
455
+ 'expires_at' => Time.utc(2007,12,25,12,34,56),
456
+ 'notes' => "",
457
+ 'illustration' => "babe.png"
458
+ }
459
+
460
+ parse(bacon_xml)["bacon"].should == expected_bacon_hash
461
+ end
462
+
463
+ it "should let type trickle through when unknown (ActiveSupport Compatible)" do
464
+ product_xml = <<-EOT
465
+ <product>
466
+ <weight type="double">0.5</weight>
467
+ <image type="ProductImage"><filename>image.gif</filename></image>
468
+
469
+ </product>
470
+ EOT
471
+
472
+ expected_product_hash = {
473
+ 'weight' => 0.5,
474
+ 'image' => {'type' => 'ProductImage', 'filename' => 'image.gif' },
475
+ }
476
+
477
+ parse(product_xml)["product"].should == expected_product_hash
478
+ end
479
+
480
+ it "should handle unescaping from xml (ActiveResource Compatible)" #do
481
+ # xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
482
+ # expected_hash = {
483
+ # 'bare_string' => 'First & Last Name',
484
+ # 'pre_escaped_string' => 'First &amp; Last Name'
485
+ # }
486
+ #
487
+ # parse(xml_string)['person'].should == expected_hash
488
+ # end
489
+
490
+ it "handle an empty xml string" do
491
+ parse('').should == {}
492
+ end
493
+
494
+ # As returned in the response body by the unfuddle XML API when creating objects
495
+ it "handle an xml string containing a single space" do
496
+ parse(' ').should == {}
497
+ end
498
+
499
+ end
500
+ end
501
+
502
+ def parse(xml)
503
+ Nori.parse xml, parser
504
+ end
505
+
506
+ end