nokogiri-happymapper 0.5.7 → 0.5.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- OTI1ZDRjNDk4NTE1ZjAxZTA0ZjE0YzhhM2JkYTk3YmZjOWZkYjE3NA==
5
- data.tar.gz: !binary |-
6
- NjQyMjEyMDBmYTU5NGZmNzM2NGY4NzJkM2Q1ZDA5YmU0NzY5NDU2OA==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- NjY1MTkxOTI0NjM1NDI2YTdjOGUwNzFhNjkzMWNiMzg5OGQ4ZTcyZGQ3NzZm
10
- YmJlNzg0MjNlYjk4MDE5N2E0M2IyNjM5MGE5MmE3M2I3MzE2MTM4MmEwZTNi
11
- OTMxMzkxMTViMGI3Mzc5MmY2ZGJjOGY5YTYyNjY1OTk3YTIwZDU=
12
- data.tar.gz: !binary |-
13
- ZjBmZjEwMzQ4NWRmYWJmYzFiMDRkZDYwYzU5N2VjY2U5ZTEwODU1ZmRiYjVm
14
- Njc5OTIyNjA3NmJhMDNmOWNhM2RjOTI5NDZhNmEzODQ5ZDc0MzMxYTM0MWE0
15
- MTQzMjYzMDY0Njg4ZGE3NWFjNTU1YmY3NDAwY2ExNDhmMDdhMDc=
2
+ SHA1:
3
+ metadata.gz: cdf3f127daf46afa0d3d5e20bed4bf5551c29f46
4
+ data.tar.gz: 391a93426ddbd64054b5e5237796691c02d6ad1b
5
+ SHA512:
6
+ metadata.gz: 1f0eaadc20671544e2cdb7738cb05359101de87dbeea066492abe2a65aa8016458d15cec88cce6bbba034b167605c5a3e5abd34d7e033df7f0d4101327c736ec
7
+ data.tar.gz: b690eb50747ebdffb6224e5715124de18c25cba9387c58db51b71d612ac0303be2ed38c4379afd78805a7d5043c9d4c0cf12a60e29edf7b01c66a636ce3d51f0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.5.8 / 2013-10-12
2
+
3
+ * Allow child elements to remove their parent's namespacing (dcarneiro)
4
+ * has_many elements were returning nil because the tag name was being ignored (haarts)
5
+ * Subclassed happymapper classes are allowed to override elements (benoist)
6
+ * Attributes on elements with dashes will properly created methods (alex-klepa)
7
+ * 'Embedded' attributes break parsing when parent element is not present (geoffwa)
8
+
9
+ ## 0.5.7 / 2012-10-29
10
+
1
11
  ## 0.5.6 / 2012-10-29
2
12
 
3
13
  * Add possibility to give a configuration block to Nokogiri when parsing (DieboldInc).
data/README.md CHANGED
@@ -11,13 +11,13 @@ This project is a fork of the great work done first by
11
11
  * [Nokogiri](http://nokogiri.org/) support
12
12
  * Text nodes parsing
13
13
  * Raw XML content parsing
14
- * [burtlo](http://github.com/burtlo/happymapper)'s `#to_xml` support utilizing the same HappyMapper tags
15
- * Fixes for [namespaces when using composition of classes](https://github.com/burtlo/happymapper/commit/fd1e898c70f7289d2d2618d629b56f2f6623785c)
16
- * Fixes for instances of XML where a [namespace is defined but no elements with that namespace are found](https://github.com/burtlo/happymapper/commit/9614221a80ff3bda18ff859aa751dff29cf52fd3).
14
+ * `#to_xml` support utilizing the same HappyMapper tags
15
+ * Numerous fixes for namespaces when using composition of classes
16
+ * Fixes for instances of XML where a namespace is defined but no elements with that namespace are found
17
17
 
18
18
  ## Installation
19
19
 
20
- ### [Rubygems](https://rubygems.org/gems/nokogiri-happymapper)
20
+ ### [Rubygems](https://rubyygems.org/gems/nokogiri-happymapper)
21
21
 
22
22
  $ gem install nokogiri-happymapper
23
23
 
@@ -68,18 +68,20 @@ It is important to be aware that this no configuration parsing is limited in cap
68
68
 
69
69
  Happymapper will let you easily model this information as a class:
70
70
 
71
- require 'happymapper'
72
-
73
- class Address
74
- include HappyMapper
75
-
76
- tag 'address'
77
- element :street, String, :tag => 'street'
78
- element :postcode, String, :tag => 'postcode'
79
- element :housenumber, Integer, :tag => 'housenumber'
80
- element :city, String, :tag => 'city'
81
- element :country, String, :tag => 'country'
82
- end
71
+ ```ruby
72
+ require 'happymapper'
73
+
74
+ class Address
75
+ include HappyMapper
76
+
77
+ tag 'address'
78
+ element :street, String, :tag => 'street'
79
+ element :postcode, String, :tag => 'postcode'
80
+ element :housenumber, Integer, :tag => 'housenumber'
81
+ element :city, String, :tag => 'city'
82
+ element :country, String, :tag => 'country'
83
+ end
84
+ ```
83
85
 
84
86
  To make a class HappyMapper compatible you simply `include HappyMapper` within the class definition. This takes care of all the work of defining all the speciality methods and magic you need to get running. As you can see we immediately start using these methods.
85
87
 
@@ -89,16 +91,20 @@ To make a class HappyMapper compatible you simply `include HappyMapper` within t
89
91
 
90
92
  When you define an element with an accessor with the same name as the tag, this is the case for all the examples above, you can omit the `:tag`. These two element declaration are equivalent to each other:
91
93
 
92
- element :street, String, :tag => 'street'
93
- element :street, String
94
+ ```ruby
95
+ element :street, String, :tag => 'street'
96
+ element :street, String
97
+ ```
94
98
 
95
99
  Including the additional tag element is not going to hurt anything and in some cases will make it absolutely clear how these elements map to the XML. However, once you know this rule, it is hard not to want to save yourself the keystrokes.
96
100
 
97
101
  Instead of `element` you may also use `has_one`:
98
102
 
99
- element :street, String, :tag => 'street'
100
- element :street, String
101
- has_one :street, String
103
+ ```ruby
104
+ element :street, String, :tag => 'street'
105
+ element :street, String
106
+ has_one :street, String
107
+ ```
102
108
 
103
109
  These three statements are equivalent to each other.
104
110
 
@@ -106,8 +112,10 @@ These three statements are equivalent to each other.
106
112
 
107
113
  With the mapping of the address XML articulated in our Address class it is time to parse the data:
108
114
 
109
- address = Address.parse(ADDRESS_XML_DATA, :single => true)
110
- puts address.street
115
+ ```ruby
116
+ address = Address.parse(ADDRESS_XML_DATA, :single => true)
117
+ puts address.street
118
+ ```
111
119
 
112
120
  Assuming that the constant `ADDRESS_XML_DATA` contains a string representation of the address XML data this is fairly straight-forward save for the `parse` method.
113
121
 
@@ -115,8 +123,10 @@ The `parse` method, like `tag` and `element` are all added when you included Hap
115
123
 
116
124
  But what about the `:single => true`? Right, that is because by default when your object is all done parsing it will be an array. In this case an array with one element, but an array none the less. So the following are equivalent to each other:
117
125
 
118
- address = Address.parse(ADDRESS_XML_DATA).first
119
- address = Address.parse(ADDRESS_XML_DATA, :single => true)
126
+ ```ruby
127
+ address = Address.parse(ADDRESS_XML_DATA).first
128
+ address = Address.parse(ADDRESS_XML_DATA, :single => true)
129
+ ```
120
130
 
121
131
  The first one returns an array and we return the first instance, the second will do that work for us inside of parse.
122
132
 
@@ -135,33 +145,39 @@ What if our address XML was a little different, perhaps we allowed multiple stre
135
145
 
136
146
  Similar to `element` or `has_one`, the declaration for when you have multiple elements you simply use:
137
147
 
138
- has_many :streets, String, :tag => 'street'
148
+ ```ruby
149
+ has_many :streets, String, :tag => 'street'
150
+ ```
139
151
 
140
152
  Your resulting `streets` method will now return an array.
141
153
 
142
- address = Address.parse(ADDRESS_XML_DATA, :single => true)
143
- puts address.streets.join('\n')
154
+ ```ruby
155
+ address = Address.parse(ADDRESS_XML_DATA, :single => true)
156
+ puts address.streets.join('\n')
157
+ ```
144
158
 
145
159
  Imagine that you have to write `streets.join('\n')` for the rest of eternity throughout your code. It would be a nightmare and one that you could avoid by creating your own convenience method.
146
160
 
147
- require 'happymapper'
161
+ ```ruby
162
+ require 'happymapper'
148
163
 
149
- class Address
150
- include HappyMapper
164
+ class Address
165
+ include HappyMapper
151
166
 
152
- tag 'address'
167
+ tag 'address'
153
168
 
154
- has_many :streets, String
169
+ has_many :streets, String
155
170
 
156
- def streets
157
- @streets.join('\n')
158
- end
171
+ def streets
172
+ @streets.join('\n')
173
+ end
159
174
 
160
- element :postcode, String, :tag => 'postcode'
161
- element :housenumber, String, :tag => 'housenumber'
162
- element :city, String, :tag => 'city'
163
- element :country, String, :tag => 'country'
164
- end
175
+ element :postcode, String, :tag => 'postcode'
176
+ element :housenumber, String, :tag => 'housenumber'
177
+ element :city, String, :tag => 'city'
178
+ element :country, String, :tag => 'country'
179
+ end
180
+ ```
165
181
 
166
182
  Now when we call the method `streets` we get a single value, but we still have the instance variable `@streets` if we ever need to the values as an array.
167
183
 
@@ -179,7 +195,9 @@ Now when we call the method `streets` we get a single value, but we still have t
179
195
 
180
196
  Attributes are absolutely the same as `element` or `has_many`
181
197
 
182
- attribute :location, String, :tag => 'location
198
+ ```ruby
199
+ attribute :location, String, :tag => 'location
200
+ ```
183
201
 
184
202
  Again, you can omit the tag if the attribute accessor symbol matches the name of the attribute.
185
203
 
@@ -196,22 +214,26 @@ Again, you can omit the tag if the attribute accessor symbol matches the name of
196
214
 
197
215
  In this case you would need to map an element to a new `Link` class just to access `<link>`s attributes, except that there is an alternate syntax. Instead of
198
216
 
199
- class Feed
200
- # ....
201
- has_many :links, Link, :tag => 'link', :xpath => '.'
202
- end
203
-
204
- class Link
205
- include HappyMapper
206
-
207
- attribute :rel, String
208
- attribute :type, String
209
- attribute :href, String
210
- end
217
+ ```ruby
218
+ class Feed
219
+ # ....
220
+ has_many :links, Link, :tag => 'link', :xpath => '.'
221
+ end
222
+
223
+ class Link
224
+ include HappyMapper
225
+
226
+ attribute :rel, String
227
+ attribute :type, String
228
+ attribute :href, String
229
+ end
230
+ ```
211
231
 
212
232
  You can drop the `Link` class and simply replace the `has_many` on `Feed` with
213
233
 
214
- element :link, String, :single => false, :attributes => { :rel => String, :type => String, :href => String }
234
+ ```ruby
235
+ element :link, String, :single => false, :attributes => { :rel => String, :type => String, :href => String }
236
+ ```
215
237
 
216
238
  As there is no content, the type given for `:link` (`String` above) is irrelevant, but `nil` won't work and other types may try to perform typecasting and fail. You can omit the :single => false for elements that only occur once within their parent.
217
239
 
@@ -233,14 +255,16 @@ Our address has a country and that country element has a code. Up until this poi
233
255
 
234
256
  Well if we only going to parse country, on it's own, we would likely create a class mapping for it.
235
257
 
236
- class Country
237
- include HappyMapper
258
+ ```ruby
259
+ class Country
260
+ include HappyMapper
238
261
 
239
- tag 'country'
262
+ tag 'country'
240
263
 
241
- attribute :code, String
242
- content :name, String
243
- end
264
+ attribute :code, String
265
+ content :name, String
266
+ end
267
+ ```
244
268
 
245
269
  We are utilizing an `attribute` declaration and a new declaration called `content`.
246
270
 
@@ -248,27 +272,31 @@ We are utilizing an `attribute` declaration and a new declaration called `conten
248
272
 
249
273
  Awesome, now if we were to redeclare our `Address` class we would use our new `Country` class.
250
274
 
251
- class Address
252
- include HappyMapper
275
+ ```ruby
276
+ class Address
277
+ include HappyMapper
253
278
 
254
- tag 'address'
279
+ tag 'address'
255
280
 
256
- has_many :streets, String, :tag => 'street'
281
+ has_many :streets, String, :tag => 'street'
257
282
 
258
- def streets
259
- @streets.join('\n')
260
- end
283
+ def streets
284
+ @streets.join('\n')
285
+ end
261
286
 
262
- element :postcode, String, :tag => 'postcode'
263
- element :housenumber, String, :tag => 'housenumber'
264
- element :city, String, :tag => 'city'
265
- element :country, Country, :tag => 'country'
266
- end
287
+ element :postcode, String, :tag => 'postcode'
288
+ element :housenumber, String, :tag => 'housenumber'
289
+ element :city, String, :tag => 'city'
290
+ element :country, Country, :tag => 'country'
291
+ end
292
+ ```
267
293
 
268
294
  Instead of `String`, `Boolean`, or `Integer` we say that it is a `Country` and HappyMapper takes care of the details of continuing the XML mapping through the country element.
269
295
 
270
- address = Address.parse(ADDRESS_XML_DATA, :single => true)
271
- puts address.country.code
296
+ ```ruby
297
+ address = Address.parse(ADDRESS_XML_DATA, :single => true)
298
+ puts address.country.code
299
+ ```
272
300
 
273
301
  A quick note, in the above example we used the constant `Country`. We could have used `'Country'`. The nice part of using the latter declaration, enclosed in quotes, is that you do not have to define your class before this class. So Country and Address can live in separate files and as long as both constants are available when it comes time to parse you are golden.
274
302
 
@@ -294,13 +322,14 @@ Getting to elements deep down within your XML can be a little more work if you d
294
322
 
295
323
  You may want to map the sub-elements contained buried in the 'gallery' as top level items in the media. Traditionally you could use class composition to accomplish this task, however, using the xpath attribute you have the ability to shortcut some of that work.
296
324
 
297
- class Media
298
- include HappyMapper
299
-
300
- has_one :title, String, :xpath => 'gallery/title'
301
- has_one :link, String, :xpath => 'gallery/title/@href'
302
- end
325
+ ```ruby
326
+ class Media
327
+ include HappyMapper
303
328
 
329
+ has_one :title, String, :xpath => 'gallery/title'
330
+ has_one :link, String, :xpath => 'gallery/title/@href'
331
+ end
332
+ ```
304
333
 
305
334
  ## Shared Functionality
306
335
 
@@ -308,85 +337,90 @@ You may want to map the sub-elements contained buried in the 'gallery' as top le
308
337
 
309
338
  While mapping XML to objects you may arrive at a point where you have two or more very similar structures.
310
339
 
311
- class Article
312
- include HappyMapper
340
+ ```ruby
341
+ class Article
342
+ include HappyMapper
313
343
 
314
- has_one :title, String
315
- has_one :author, String
316
- has_one :published, Time
344
+ has_one :title, String
345
+ has_one :author, String
346
+ has_one :published, Time
317
347
 
318
- has_one :entry, String
348
+ has_one :entry, String
319
349
 
320
- end
350
+ end
321
351
 
322
- class Gallery
323
- include HappyMapper
352
+ class Gallery
353
+ include HappyMapper
324
354
 
325
- has_one :title, String
326
- has_one :author, String
327
- has_one :published, Time
355
+ has_one :title, String
356
+ has_one :author, String
357
+ has_one :published, Time
328
358
 
329
- has_many :photos, String
359
+ has_many :photos, String
330
360
 
331
- end
361
+ end
362
+ ```
332
363
 
333
364
  In this example there are definitely two similarities between our two pieces of content. So much so that you might be included to create an inheritance structure to save yourself some keystrokes.
334
365
 
335
- class Content
336
- include HappyMapper
366
+ ```ruby
367
+ class Content
368
+ include HappyMapper
337
369
 
338
- has_one :title, String
339
- has_one :author, String
340
- has_one :published, Time
341
- end
370
+ has_one :title, String
371
+ has_one :author, String
372
+ has_one :published, Time
373
+ end
342
374
 
343
- class Article < Content
344
- include HappyMapper
375
+ class Article < Content
376
+ include HappyMapper
345
377
 
346
- has_one :entry, String
347
- end
378
+ has_one :entry, String
379
+ end
348
380
 
349
- class Gallery < Content
350
- include HappyMapper
381
+ class Gallery < Content
382
+ include HappyMapper
351
383
 
352
- has_many :photos, String
353
- end
384
+ has_many :photos, String
385
+ end
386
+ ```
354
387
 
355
- ### Module Mixins Approache
388
+ ### Module Mixins Approach
356
389
 
357
390
  You can also solve the above problem through mixins.
358
391
 
359
- module Content
360
- def self.included(content)
361
- content.has_one :title, String
362
- content.has_one :author, String
363
- content.has_one :published, Time
364
- end
365
-
366
- def published_time
367
- @published.strftime("%H:%M:%S")
368
- end
369
- end
370
-
371
- class Article
372
- include HappyMapper
373
-
374
- include Content
375
- has_one :entry, String
376
- end
377
-
378
- class Gallery
379
- include HappyMapper
380
-
381
- include Content
382
- has_many :photos, String
383
- end
384
-
392
+ ```ruby
393
+ module Content
394
+ def self.included(content)
395
+ content.has_one :title, String
396
+ content.has_one :author, String
397
+ content.has_one :published, Time
398
+ end
399
+
400
+ def published_time
401
+ @published.strftime("%H:%M:%S")
402
+ end
403
+ end
404
+
405
+ class Article
406
+ include HappyMapper
407
+
408
+ include Content
409
+ has_one :entry, String
410
+ end
411
+
412
+ class Gallery
413
+ include HappyMapper
414
+
415
+ include Content
416
+ has_many :photos, String
417
+ end
418
+ ```
385
419
 
386
420
  Here, when we include `Content` in both of these classes the module method `#included` is called and our class is given as a parameter. So we take that opportunity to do some surgery and define our happymapper elements as well as any other methods that may rely on those instance variables that come along in the package.
387
421
 
388
422
 
389
- ## Filtering with XPATH
423
+ ## Filtering with XPATH (non-greedy)
390
424
 
391
425
  I ran into a case where I wanted to capture all the pictures that were directly under media, but not the ones contained within a gallery.
392
426
 
@@ -405,25 +439,38 @@ I ran into a case where I wanted to capture all the pictures that were directly
405
439
 
406
440
  The following `Media` class is where I started:
407
441
 
408
- require 'happymapper'
442
+ ```ruby
443
+ require 'happymapper'
409
444
 
410
- class Media
411
- include HappyMapper
445
+ class Media
446
+ include HappyMapper
412
447
 
413
- has_many :galleries, Gallery, :tag => 'gallery'
414
- has_many :pictures, Picture, :tag => 'picture'
415
- end
448
+ has_many :galleries, Gallery, :tag => 'gallery'
449
+ has_many :pictures, Picture, :tag => 'picture'
450
+ end
451
+ ```
416
452
 
417
453
  However when I parsed the media xml the number of pictures returned to me was 2, not 1.
418
454
 
419
- pictures = Media.parse(MEDIA_XML,:single => true).pictures
420
- pictures.length.should == 1 # => Failed Expectation
455
+ ```ruby
456
+ pictures = Media.parse(MEDIA_XML,:single => true).pictures
457
+ pictures.length.should == 1 # => Failed Expectation
458
+ ```
459
+
460
+ The reason that 2 elements are returned and not 1 is because the default
461
+ mappings are assigned XPATH './/' which makes them greedy. Essentially by
462
+ default it will find all elements with the tag 'pictures' at the current
463
+ level of the document and anywhere else within the document.
421
464
 
422
- I was mistaken and that is because, by default the mappings are assigned XPATH './/' which is requiring all the elements no matter where they can be found. To override this you can specify an XPATH value for your defined elements.
465
+ To limit an element from being greedy and only finding elements at the
466
+ level of the current node you can specify an XPATH.
423
467
 
424
- has_many :pictures, Picture, :tag => 'picture', :xpath => '/media'
468
+ ```ruby
469
+ has_many :pictures, Picture, :tag => 'picture', :xpath => '.'
470
+ ```
425
471
 
426
- `/media` states that we are only interested in pictures that can be found directly under the media element. So when we parse again we will have only our one element.
472
+ `.` states that we are only interested in pictures that can be found directly
473
+ under the current node. So when we parse again we will have only our one element.
427
474
 
428
475
 
429
476
  ## Namespaces
@@ -443,28 +490,32 @@ Perhaps our `address` XML is really swarming with namespaces:
443
490
 
444
491
  Here again is our address example with a made up namespace called `prefix` that comes direct to use from unicornland, a very magical place indeed. Well we are going to have to do some work on our class definition and that simply adding this one liner to the `Address` class:
445
492
 
446
- class Address
447
- include HappyMapper
493
+ ```ruby
494
+ class Address
495
+ include HappyMapper
448
496
 
449
- tag 'address'
450
- namespace 'prefix'
451
- # ... rest of the code ...
452
- end
497
+ tag 'address'
498
+ namespace 'prefix'
499
+ # ... rest of the code ...
500
+ end
501
+ ```
453
502
 
454
503
  Of course, if that is too easy for you, you can append a `:namespace => 'prefix` to every one of the elements that you defined.
455
504
 
456
- has_many :street, String, :tag => 'street', :namespace => 'prefix'
457
- element :postcode, String, :tag => 'postcode', :namespace => 'prefix'
458
- element :housenumber, String, :tag => 'housenumber', :namespace => 'prefix'
459
- element :city, String, :tag => 'city', :namespace => 'prefix'
460
- element :country, Country, :tag => 'country', :namespace => 'prefix'
505
+ ```ruby
506
+ has_many :street, String, :tag => 'street', :namespace => 'prefix'
507
+ element :postcode, String, :tag => 'postcode', :namespace => 'prefix'
508
+ element :housenumber, String, :tag => 'housenumber', :namespace => 'prefix'
509
+ element :city, String, :tag => 'city', :namespace => 'prefix'
510
+ element :country, Country, :tag => 'country', :namespace => 'prefix'
511
+ ```
461
512
 
462
513
  I definitely recommend the former, as it saves you a whole hell of lot of typing. However, there are times when appending a namespace to an element declaration is important and that is when it has a different namespace then `namespsace 'prefix'`.
463
514
 
464
515
  Imagine that our `country` actually belonged to a completely different namespace.
465
516
 
466
517
  <prefix:address location='home' xmlns:prefix="http://www.unicornland.com/prefix"
467
- xmlns:prefix="http://www.trollcountry.com/different">
518
+ xmlns:different="http://www.trollcountry.com/different">
468
519
  <prefix:street>Milchstrasse</prefix:street>
469
520
  <prefix:street>Another Street</prefix:street>
470
521
  <prefix:housenumber>23</prefix:housenumber>
@@ -475,7 +526,9 @@ Imagine that our `country` actually belonged to a completely different namespace
475
526
 
476
527
  Well we would need to specify that namespace:
477
528
 
478
- element :country, Country, :tag => 'country', :namespace => 'different'
529
+ ```ruby
530
+ element :country, Country, :tag => 'country', :namespace => 'different'
531
+ ```
479
532
 
480
533
  With that we should be able to parse as we once did.
481
534
 
@@ -483,9 +536,11 @@ With that we should be able to parse as we once did.
483
536
 
484
537
  When dealing with large sets of XML that simply cannot or should not be placed into memory the objects can be handled in groups through the `:in_groups_of` parameter.
485
538
 
486
- Address.parse(LARGE_ADDRESS_XML_DATA,:in_groups_of => 5) do |group|
487
- puts address.streets
488
- end
539
+ ```ruby
540
+ Address.parse(LARGE_ADDRESS_XML_DATA,:in_groups_of => 5) do |group|
541
+ puts address.streets
542
+ end
543
+ ```
489
544
 
490
545
  This trivial block will parse the large set of XML data and in groups of 5 addresses at a time display the streets.
491
546
 
@@ -498,7 +553,9 @@ Saving a class to XML is as easy as calling `#to_xml`. The end result will be t
498
553
 
499
554
  When you are saving data to xml it is often important to change or manipulate data to a particular format. For example, a time object:
500
555
 
501
- has_one :published_time, Time, :on_save => lambda {|time| time.strftime("%H:%M:%S") if time }
556
+ ```ruby
557
+ has_one :published_time, Time, :on_save => lambda {|time| time.strftime("%H:%M:%S") if time }
558
+ ```
502
559
 
503
560
  Here we add the options `:on_save` and specify a lambda which will be executed on the method call to `:published_time`.
504
561
 
@@ -506,7 +563,9 @@ Here we add the options `:on_save` and specify a lambda which will be executed o
506
563
 
507
564
  When an element contains a nil value, or perhaps the result of the :on_save lambda correctly results in a nil value you will be happy that the element will not appear in the resulting XML. However, there are time when you will want to see that element and that's when `:state_when_nil` is there for you.
508
565
 
509
- has_one :favorite_color, String, :state_when_nil => true
566
+ ```ruby
567
+ has_one :favorite_color, String, :state_when_nil => true
568
+ ```
510
569
 
511
570
  The resulting XML will include the 'favorite_color' element even if the favorite color has not been specified.
512
571
 
@@ -515,28 +574,32 @@ The resulting XML will include the 'favorite_color' element even if the favorite
515
574
  When an element, attribute, or text node is a value that you have no interest in
516
575
  saving to XML, you can ensure that takes place by stating that it is `read only`.
517
576
 
518
- has_one :modified, Boolean, :read_only => true
519
- attribute :temporary, Boolean, :read_only => true
577
+ ```ruby
578
+ has_one :modified, Boolean, :read_only => true
579
+ attribute :temporary, Boolean, :read_only => true
580
+ ```
520
581
 
521
582
  This is useful if perhaps the incoming XML is different than the out-going XML.
522
583
 
523
584
  ### namespaces
524
585
 
525
- While parsing the XML only required you to simply specify the prefix of the namespace you wanted to parse, when you persist to xml you will need to define your namespaces so that they are correctly captured.
586
+ Parsing the XML to objects only required you to simply specify the prefix of the namespace you wanted to parse, when you persist to xml you will need to define your namespaces so that they are correctly captured.
526
587
 
527
- class Address
528
- include HappyMapper
588
+ ```ruby
589
+ class Address
590
+ include HappyMapper
529
591
 
530
- register_namespace 'prefix', 'http://www.unicornland.com/prefix'
531
- register_namespace 'different', 'http://www.trollcountry.com/different'
592
+ register_namespace 'prefix', 'http://www.unicornland.com/prefix'
593
+ register_namespace 'different', 'http://www.trollcountry.com/different'
532
594
 
533
- tag 'address'
534
- namespace 'prefix'
595
+ tag 'address'
596
+ namespace 'prefix'
535
597
 
536
- has_many :street, String
537
- element :postcode, String
538
- element :housenumber, String
539
- element :city, String
540
- element :country, Country, :tag => 'country', :namespace => 'different'
598
+ has_many :street, String
599
+ element :postcode, String
600
+ element :housenumber, String
601
+ element :city, String
602
+ element :country, Country, :tag => 'country', :namespace => 'different'
541
603
 
542
- end
604
+ end
605
+ ```
data/lib/happymapper.rb CHANGED
@@ -14,15 +14,17 @@ module HappyMapper
14
14
  def self.included(base)
15
15
  if !(base.superclass <= HappyMapper)
16
16
  base.instance_eval do
17
- @attributes = []
18
- @elements = []
17
+ @attributes = {}
18
+ @elements = {}
19
19
  @registered_namespaces = {}
20
20
  @wrapper_anonymous_classes = {}
21
21
  end
22
22
  else
23
23
  base.instance_eval do
24
- @attributes = superclass.attributes.dup
25
- @elements = superclass.elements.dup
24
+ @attributes =
25
+ superclass.instance_variable_get(:@attributes).dup
26
+ @elements =
27
+ superclass.instance_variable_get(:@elements).dup
26
28
  @registered_namespaces =
27
29
  superclass.instance_variable_get(:@registered_namespaces).dup
28
30
  @wrapper_anonymous_classes =
@@ -52,7 +54,7 @@ module HappyMapper
52
54
  #
53
55
  def attribute(name, type, options={})
54
56
  attribute = Attribute.new(name, type, options)
55
- @attributes << attribute
57
+ @attributes[name] = attribute
56
58
  attr_accessor attribute.method_name.intern
57
59
  end
58
60
 
@@ -63,7 +65,7 @@ module HappyMapper
63
65
  # an empty array is returned when there have been no attributes defined.
64
66
  #
65
67
  def attributes
66
- @attributes
68
+ @attributes.values
67
69
  end
68
70
 
69
71
  #
@@ -107,7 +109,7 @@ module HappyMapper
107
109
  #
108
110
  def element(name, type, options={})
109
111
  element = Element.new(name, type, options)
110
- @elements << element
112
+ @elements[name] = element
111
113
  attr_accessor element.method_name.intern
112
114
  end
113
115
 
@@ -119,7 +121,7 @@ module HappyMapper
119
121
  # defined.
120
122
  #
121
123
  def elements
122
- @elements
124
+ @elements.values
123
125
  end
124
126
 
125
127
  #
@@ -338,18 +340,36 @@ module HappyMapper
338
340
  nodes = []
339
341
 
340
342
  # when finding nodes, do it in this order:
341
- # 1. specified tag
343
+ # 1. specified tag if one has been provided
342
344
  # 2. name of element
343
345
  # 3. tag_name (derived from class name by default)
344
346
 
347
+ # If a tag has been provided we need to search for it.
345
348
 
346
- [options[:tag], options[:name], tag_name].compact.each do |xpath_ext|
349
+ if options.key?(:tag)
347
350
  begin
348
- nodes = node.xpath(xpath + xpath_ext.to_s, namespaces)
351
+ nodes = node.xpath(xpath + options[:tag].to_s, namespaces)
349
352
  rescue
350
- break
353
+ # This exception takes place when the namespace is often not found
354
+ # and we should continue on with the empty array of nodes.
351
355
  end
352
- break if nodes && !nodes.empty?
356
+ else
357
+
358
+ # This is the default case when no tag value is provided.
359
+ # First we use the name of the element `items` in `has_many items`
360
+ # Second we use the tag name which is the name of the class cleaned up
361
+
362
+ [options[:name], tag_name].compact.each do |xpath_ext|
363
+ begin
364
+ nodes = node.xpath(xpath + xpath_ext.to_s, namespaces)
365
+ rescue
366
+ break
367
+ # This exception takes place when the namespace is often not found
368
+ # and we should continue with the empty array of nodes or keep looking
369
+ end
370
+ break if nodes && !nodes.empty?
371
+ end
372
+
353
373
  end
354
374
 
355
375
  nodes
@@ -35,12 +35,14 @@ module HappyMapper
35
35
  if options[:attributes].is_a?(Hash)
36
36
  result = result.first unless result.respond_to?(:attribute_nodes)
37
37
 
38
+ return unless result.respond_to?(:attribute_nodes)
39
+
38
40
  result.attribute_nodes.each do |xml_attribute|
39
41
  if attribute_options = options[:attributes][xml_attribute.name.to_sym]
40
42
  attribute_value = Attribute.new(xml_attribute.name.to_sym, *attribute_options).from_xml_node(result, namespace, xpath_options)
41
43
 
42
44
  result.instance_eval <<-EOV
43
- def value.#{xml_attribute.name}
45
+ def value.#{xml_attribute.name.gsub(/\-/, '_')}
44
46
  #{attribute_value.inspect}
45
47
  end
46
48
  EOV
@@ -32,6 +32,8 @@ module HappyMapper
32
32
  #
33
33
  def from_xml_node(node, namespace, xpath_options)
34
34
 
35
+ namespace = options[:namespace] if options.key?(:namespace)
36
+
35
37
  if suported_type_registered?
36
38
  find(node, namespace, xpath_options) { |n| process_node_as_supported_type(n) }
37
39
  elsif constant == XmlContent
@@ -1,3 +1,3 @@
1
1
  module HappyMapper
2
- VERSION = "0.5.7"
2
+ VERSION = "0.5.8"
3
3
  end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Attribute Method Conversion" do
4
+
5
+ let(:xml_document) do
6
+ %{<document>
7
+ <link data-src='http://cooking.com/roastbeef' type='recipe'>Roast Beef</link>
8
+ </document>}
9
+ end
10
+
11
+ module AttributeMethodConversion
12
+ class Document
13
+ include HappyMapper
14
+
15
+ has_many :link, String, :attributes => { :'data-src' => String, :type => String, :href => String }
16
+
17
+ end
18
+ end
19
+
20
+ let(:document) do
21
+ AttributeMethodConversion::Document.parse(xml_document,single: true)
22
+ end
23
+
24
+ it "link" do
25
+ expect(document.link).to eq ["Roast Beef"]
26
+ end
27
+
28
+ it "link.data_src" do
29
+ expect(document.link.first.data_src).to eq "http://cooking.com/roastbeef"
30
+ end
31
+
32
+ it "link.type" do
33
+ expect(document.link.first.type).to eq "recipe"
34
+ end
35
+
36
+ end
@@ -0,0 +1,18 @@
1
+ <aws:weather xmlns:aws="http://www.aws.com/aws">
2
+ <aws:api version="2.0"/>
3
+ <aws:WebURL>http://weather.weatherbug.com/IN/Carmel-weather.html?ZCode=Z5546&amp;Units=0&amp;stat=MOCAR</aws:WebURL>
4
+ <aws:ob>
5
+ <aws:ob-date>
6
+ <aws:year number="2008"/>
7
+ <aws:month number="12" text="December" abbrv="Dec"/>
8
+ <aws:day number="30" text="Tuesday" abbrv="Tue"/>
9
+ <aws:hour number="4" hour-24="16"/>
10
+ <aws:minute number="18"/>
11
+ <aws:second number="01"/>
12
+ <aws:am-pm abbrv="PM"/>
13
+ <aws:time-zone offset="-5" text="Eastern Standard Time" abbrv="EST"/>
14
+ </aws:ob-date>
15
+ <aws:feels-like units="&amp;deg;F">51</aws:feels-like>
16
+ <aws:temp units="&amp;deg;F">51.8</aws:temp>
17
+ </aws:ob>
18
+ </aws:weather>
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
1
+ require 'spec_helper'
2
2
 
3
3
  describe HappyMapper do
4
4
 
@@ -45,7 +45,7 @@ describe HappyMapper do
45
45
 
46
46
  subject { described_class.parse fixture_file('subclass_namespace.xml') }
47
47
 
48
- it "should parse the elements an values correctly" do
48
+ it "should parse the elements and values correctly" do
49
49
  subject.title.should == "article title"
50
50
  subject.photo.publish_options.author.should == "Stephanie"
51
51
  subject.gallery.photo.title.should == "photo title"
@@ -70,7 +70,7 @@ describe HappyMapper do
70
70
  subject.image.should == [ "image1", "image2" ]
71
71
 
72
72
  end
73
-
73
+
74
74
  end
75
75
 
76
76
  context "xml with multiple namespaces" do
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'uri'
2
3
 
3
4
  module Analytics
4
5
  class Property
@@ -780,6 +781,10 @@ describe HappyMapper do
780
781
  feed.link.last.href.should == 'http://www.example.com/tv_shows.atom'
781
782
  end
782
783
 
784
+ it "parses xml with optional elements with embedded attributes" do
785
+ expect { CurrentWeather.parse(fixture_file('current_weather_missing_elements.xml')) }.to_not raise_error()
786
+ end
787
+
783
788
  it "returns nil rather than empty array for absent values when :single => true" do
784
789
  address = Address.parse('<?xml version="1.0" encoding="UTF-8"?><foo/>', :single => true)
785
790
  address.should be_nil
@@ -1099,7 +1104,7 @@ describe HappyMapper do
1099
1104
  end
1100
1105
 
1101
1106
  it 'parses according to @nokogiri_config_callback' do
1102
- expect { custom.parse(fixture_file('set_config_options.xml')) }.to_not raise_error(Nokogiri::XML::SyntaxError)
1107
+ expect { custom.parse(fixture_file('set_config_options.xml')) }.to_not raise_error
1103
1108
  end
1104
1109
 
1105
1110
  it 'can clear @nokogiri_config_callback' do
@@ -0,0 +1,43 @@
1
+ require "spec_helper"
2
+
3
+ module Sheep
4
+ class Item
5
+ include HappyMapper
6
+ end
7
+
8
+ class Navigator
9
+ include HappyMapper
10
+ tag 'navigator'
11
+
12
+ # This is purposefully set to have the name 'items' with the tag 'item'.
13
+ # The idea is that it should not find the empty items contained within the
14
+ # xml and return an empty array. This exercises the order of how nodes
15
+ # are searched for within an XML document.
16
+ has_many :items, Item, tag: 'item'
17
+
18
+ has_many :items_with_a_different_name, Item, tag: 'item'
19
+
20
+ end
21
+ end
22
+
23
+ describe "emptyness" do
24
+ let(:xml) do
25
+ <<-EOF
26
+ <navigator>
27
+ <items/>
28
+ </navigator>
29
+ EOF
30
+ end
31
+
32
+ let(:navigator) do
33
+ Sheep::Navigator.parse(xml)
34
+ end
35
+
36
+ it "returns an empty array" do
37
+ navigator.items_with_a_different_name.should be_empty
38
+ end
39
+
40
+ it "returns an empty array" do
41
+ navigator.items.should be_empty
42
+ end
43
+ end
@@ -19,6 +19,52 @@ describe "Using inheritance to share elements and attributes" do
19
19
  has_many :immunities, String
20
20
  end
21
21
 
22
+ class Overwrite < Parent
23
+ include HappyMapper
24
+
25
+ attribute :love, String
26
+ element :genetics, Integer
27
+ end
28
+
29
+ describe "Overwrite" do
30
+ let(:subject) do
31
+ xml = '<overwrite love="love" naivety="trusting"><genetics>1001</genetics><immunities>Chicken Pox</immunities></overwrite>'
32
+ Overwrite.parse(xml, single: true)
33
+ end
34
+
35
+ it 'overrides the parent elements and attributes' do
36
+ expect(Overwrite.attributes.count).to be == Parent.attributes.count
37
+ expect(Overwrite.elements.count).to be == Parent.elements.count
38
+ end
39
+
40
+ context "when parsing xml" do
41
+ it 'parses the new overwritten attribut' do
42
+ expect(subject.love).to be == "love"
43
+ end
44
+
45
+ it 'parses the new overwritten element' do
46
+ expect(subject.genetics).to be == 1001
47
+ end
48
+ end
49
+
50
+ context "when saving to xml" do
51
+ subject do
52
+ overwrite = Overwrite.new
53
+ overwrite.genetics = 1
54
+ overwrite.love = "love"
55
+ Nokogiri::XML(overwrite.to_xml).root
56
+ end
57
+
58
+ it 'has only 1 genetics element' do
59
+ expect(subject.xpath('//genetics').count).to be == 1
60
+ end
61
+
62
+ it 'has only 1 love attribute' do
63
+ expect(subject.xpath('@love').text).to be == "love"
64
+ end
65
+ end
66
+ end
67
+
22
68
  describe "Child", "a subclass of the Parent" do
23
69
  let(:subject) do
24
70
  xml = '<child love="99" naivety="trusting"><genetics>ABBA</genetics><immunities>Chicken Pox</immunities></child>'
@@ -58,4 +104,4 @@ describe "Using inheritance to share elements and attributes" do
58
104
  end
59
105
 
60
106
  end
61
- end
107
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe "A document with mixed namespaces" do
4
+
5
+ #
6
+ # Note that the parent element of the xml has the namespacing. The elements
7
+ # contained within the xml do not share the parent element namespace so a
8
+ # user of the library would likely need to clear the namespace on each of
9
+ # these child elements.
10
+ #
11
+ let(:xml_document) do
12
+ %{<prefix:address location='home' xmlns:prefix="http://www.unicornland.com/prefix"
13
+ xmlns:different="http://www.trollcountry.com/different">
14
+ <street>Milchstrasse</street>
15
+ <street>Another Street</street>
16
+ <housenumber>23</housenumber>
17
+ <different:postcode>26131</different:postcode>
18
+ <city>Oldenburg</city>
19
+ </prefix:address>}
20
+ end
21
+
22
+ module MixedNamespaces
23
+ class Address
24
+ include HappyMapper
25
+
26
+ namespace :prefix
27
+ tag :address
28
+
29
+ # Here each of the elements have their namespace set to nil to reset their
30
+ # namespace so that it is not the same as the prefix namespace
31
+
32
+ has_many :streets, String, tag: 'street', namespace: nil
33
+
34
+ has_one :house_number, String, tag: 'housenumber', namespace: nil
35
+ has_one :postcode, String, namespace: 'different'
36
+ has_one :city, String, namespace: nil
37
+ end
38
+ end
39
+
40
+ let(:address) do
41
+ MixedNamespaces::Address.parse(xml_document, single: true)
42
+ end
43
+
44
+
45
+ it "has the correct streets" do
46
+ expect(address.streets).to eq [ "Milchstrasse", "Another Street" ]
47
+ end
48
+
49
+ it "house number" do
50
+ expect(address.house_number).to eq "23"
51
+ end
52
+
53
+ it "postcode" do
54
+ expect(address.postcode).to eq "26131"
55
+ end
56
+
57
+ it "city" do
58
+ expect(address.city).to eq "Oldenburg"
59
+ end
60
+
61
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nokogiri-happymapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.7
4
+ version: 0.5.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damien Le Berrigaud
@@ -63,6 +63,7 @@ files:
63
63
  - README.md
64
64
  - CHANGELOG.md
65
65
  - spec/attribute_default_value_spec.rb
66
+ - spec/attributes_spec.rb
66
67
  - spec/fixtures/address.xml
67
68
  - spec/fixtures/ambigous_items.xml
68
69
  - spec/fixtures/analytics.xml
@@ -70,6 +71,7 @@ files:
70
71
  - spec/fixtures/atom.xml
71
72
  - spec/fixtures/commit.xml
72
73
  - spec/fixtures/current_weather.xml
74
+ - spec/fixtures/current_weather_missing_elements.xml
73
75
  - spec/fixtures/default_namespace_combi.xml
74
76
  - spec/fixtures/dictionary.xml
75
77
  - spec/fixtures/family_tree.xml
@@ -95,8 +97,10 @@ files:
95
97
  - spec/happymapper/text_node_spec.rb
96
98
  - spec/happymapper_parse_spec.rb
97
99
  - spec/happymapper_spec.rb
100
+ - spec/has_many_empty_array_spec.rb
98
101
  - spec/ignay_spec.rb
99
102
  - spec/inheritance_spec.rb
103
+ - spec/mixed_namespaces_spec.rb
100
104
  - spec/parse_with_object_to_update_spec.rb
101
105
  - spec/spec_helper.rb
102
106
  - spec/to_xml_spec.rb
@@ -114,22 +118,23 @@ require_paths:
114
118
  - lib
115
119
  required_ruby_version: !ruby/object:Gem::Requirement
116
120
  requirements:
117
- - - ! '>='
121
+ - - '>='
118
122
  - !ruby/object:Gem::Version
119
123
  version: '0'
120
124
  required_rubygems_version: !ruby/object:Gem::Requirement
121
125
  requirements:
122
- - - ! '>='
126
+ - - '>='
123
127
  - !ruby/object:Gem::Version
124
128
  version: '0'
125
129
  requirements: []
126
130
  rubyforge_project:
127
- rubygems_version: 2.0.3
131
+ rubygems_version: 2.0.6
128
132
  signing_key:
129
133
  specification_version: 3
130
134
  summary: Provides a simple way to map XML to Ruby Objects and back again.
131
135
  test_files:
132
136
  - spec/attribute_default_value_spec.rb
137
+ - spec/attributes_spec.rb
133
138
  - spec/fixtures/address.xml
134
139
  - spec/fixtures/ambigous_items.xml
135
140
  - spec/fixtures/analytics.xml
@@ -137,6 +142,7 @@ test_files:
137
142
  - spec/fixtures/atom.xml
138
143
  - spec/fixtures/commit.xml
139
144
  - spec/fixtures/current_weather.xml
145
+ - spec/fixtures/current_weather_missing_elements.xml
140
146
  - spec/fixtures/default_namespace_combi.xml
141
147
  - spec/fixtures/dictionary.xml
142
148
  - spec/fixtures/family_tree.xml
@@ -162,8 +168,10 @@ test_files:
162
168
  - spec/happymapper/text_node_spec.rb
163
169
  - spec/happymapper_parse_spec.rb
164
170
  - spec/happymapper_spec.rb
171
+ - spec/has_many_empty_array_spec.rb
165
172
  - spec/ignay_spec.rb
166
173
  - spec/inheritance_spec.rb
174
+ - spec/mixed_namespaces_spec.rb
167
175
  - spec/parse_with_object_to_update_spec.rb
168
176
  - spec/spec_helper.rb
169
177
  - spec/to_xml_spec.rb
@@ -171,3 +179,4 @@ test_files:
171
179
  - spec/wilcard_tag_name_spec.rb
172
180
  - spec/wrap_spec.rb
173
181
  - spec/xpath_spec.rb
182
+ has_rdoc: