nokogiri-happymapper 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +26 -1
- data/README.md +204 -117
- data/lib/happymapper.rb +318 -343
- data/lib/happymapper/anonymous_mapper.rb +27 -29
- data/lib/happymapper/attribute.rb +7 -5
- data/lib/happymapper/element.rb +19 -24
- data/lib/happymapper/item.rb +18 -20
- data/lib/happymapper/supported_types.rb +20 -19
- data/lib/happymapper/text_node.rb +4 -3
- data/lib/happymapper/version.rb +3 -1
- data/spec/attribute_default_value_spec.rb +14 -15
- data/spec/attributes_spec.rb +14 -15
- data/spec/happymapper/attribute_spec.rb +4 -4
- data/spec/happymapper/element_spec.rb +3 -1
- data/spec/happymapper/item_spec.rb +49 -41
- data/spec/happymapper/text_node_spec.rb +3 -1
- data/spec/happymapper_parse_spec.rb +62 -44
- data/spec/happymapper_spec.rb +270 -263
- data/spec/has_many_empty_array_spec.rb +8 -7
- data/spec/ignay_spec.rb +27 -31
- data/spec/inheritance_spec.rb +30 -24
- data/spec/mixed_namespaces_spec.rb +14 -15
- data/spec/parse_with_object_to_update_spec.rb +37 -38
- data/spec/spec_helper.rb +18 -0
- data/spec/to_xml_spec.rb +64 -63
- data/spec/to_xml_with_namespaces_spec.rb +66 -64
- data/spec/wilcard_tag_name_spec.rb +25 -21
- data/spec/wrap_spec.rb +11 -11
- data/spec/xpath_spec.rb +33 -32
- metadata +33 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 75fd5c49ca6f39f746d0570dcf4b5cbcc53c983aeeb1d5198a4bb8108701fc46
|
4
|
+
data.tar.gz: 5e9106990b90654f2e93977381b3cd4a3901f79cdda533c2f2d6b452e67e95c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5bceafdb34f883e0980e914ff30bea0774436e9e105c1d89a25128f0e4bb7a1ddb17278bc971ee94578a83a399af4061ddc73813fc0edf46b5d5b35c8e6e48a0
|
7
|
+
data.tar.gz: 956560556fa3dbf7f111f61b4997645cc168f05ac8659c4f68fca2e30db8ab7e2a400ea18c6e13a35aeed81f4626d37e467e8fab116490945dd77269cfc1b4b0
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,29 @@
|
|
1
|
-
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.7.0 / 2018-08-27
|
4
|
+
|
5
|
+
### Breaking Changes
|
6
|
+
|
7
|
+
* Remove constant HappyMapper::DEFAULT_NS
|
8
|
+
* Drop support for Ruby 2.2 and below
|
9
|
+
|
10
|
+
### Improvements
|
11
|
+
|
12
|
+
* Support Ruby 2.5
|
13
|
+
* Always sort namespaces. This adds support for JRuby.
|
14
|
+
|
15
|
+
### Bug fixes
|
16
|
+
|
17
|
+
* Ensure `#to_xml` generates UTF-8 content
|
18
|
+
([#88](https://github.com/mvz/happymapper/pull/88))
|
19
|
+
* Handle namespaces for nested value elements when parsing anonymously
|
20
|
+
([#87](https://github.com/mvz/happymapper/pull/87))
|
21
|
+
* Handle attributes with a namespace that is different from the element
|
22
|
+
namespace ([#87](https://github.com/mvz/happymapper/pull/87))
|
23
|
+
* Ensure camel-cased elements have content in anonymous parse
|
24
|
+
([#85](https://github.com/mvz/happymapper/pull/85))
|
25
|
+
|
26
|
+
## 0.6.0 / 2017-09-17
|
2
27
|
|
3
28
|
* Prevent parsing of empty string for Date, DateTime (wushugene)
|
4
29
|
* Rescue nil dates (sarsena)
|
data/README.md
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
-
HappyMapper
|
2
|
-
===========
|
1
|
+
# HappyMapper
|
3
2
|
|
4
|
-
Happymapper allows you to parse XML data and convert it quickly and easily into
|
3
|
+
Happymapper allows you to parse XML data and convert it quickly and easily into
|
4
|
+
ruby data structures.
|
5
5
|
|
6
6
|
This project is a fork of the great work done first by
|
7
7
|
[jnunemaker](https://github.com/jnunemaker/happymapper).
|
8
8
|
|
9
|
+
[![Gem Version](https://badge.fury.io/rb/nokogiri-happymapper.svg)](https://badge.fury.io/rb/nokogiri-happymapper)
|
10
|
+
[![Build Status](https://travis-ci.org/mvz/happymapper.svg?branch=master)](https://travis-ci.org/mvz/happymapper)
|
11
|
+
[![Coverage Status](https://coveralls.io/repos/github/mvz/happymapper/badge.svg?branch=master)](https://coveralls.io/github/mvz/happymapper?branch=master)
|
12
|
+
[![Depfu](https://badges.depfu.com/badges/1707c8c2322e2ed267cf88bd4fb12b66/overview.svg)](https://depfu.com/github/mvz/happymapper)
|
13
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/491015f82bd2a45fd9d3/maintainability)](https://codeclimate.com/github/mvz/happymapper/maintainability)
|
14
|
+
|
9
15
|
## Major Differences
|
10
16
|
|
11
17
|
* [Nokogiri](http://nokogiri.org/) support
|
@@ -13,26 +19,27 @@ This project is a fork of the great work done first by
|
|
13
19
|
* Raw XML content parsing
|
14
20
|
* `#to_xml` support utilizing the same HappyMapper tags
|
15
21
|
* Numerous fixes for namespaces when using composition of classes
|
16
|
-
* Fixes for instances of XML where a namespace is defined but no elements
|
22
|
+
* Fixes for instances of XML where a namespace is defined but no elements
|
23
|
+
with that namespace are found
|
17
24
|
|
18
25
|
## Installation
|
19
26
|
|
20
|
-
|
27
|
+
Install via rubygems:
|
21
28
|
|
22
29
|
$ gem install nokogiri-happymapper
|
23
30
|
|
24
|
-
|
25
|
-
Add the `nokogiri-happymapper` gem to your project's `Gemfile`.
|
31
|
+
Or add the `nokogiri-happymapper` gem to your project's `Gemfile`.
|
26
32
|
|
27
|
-
gem 'nokogiri-happymapper', :
|
33
|
+
gem 'nokogiri-happymapper', require: 'happymapper'
|
28
34
|
|
29
|
-
Run
|
35
|
+
Run Bundler to install the gem:
|
30
36
|
|
31
37
|
$ bundle install
|
32
38
|
|
33
|
-
|
39
|
+
## Examples
|
34
40
|
|
35
|
-
Let's start with a simple example to get our feet wet. Here we have a simple
|
41
|
+
Let's start with a simple example to get our feet wet. Here we have a simple
|
42
|
+
example of XML that defines some address information:
|
36
43
|
|
37
44
|
<address>
|
38
45
|
<street>Milchstrasse</street>
|
@@ -42,11 +49,13 @@ Let's start with a simple example to get our feet wet. Here we have a simple exa
|
|
42
49
|
<country code="de">Germany</country>
|
43
50
|
</address>
|
44
51
|
|
45
|
-
Happymapper provides support for simple, zero configuration parsing as well as
|
52
|
+
Happymapper provides support for simple, zero configuration parsing as well as
|
53
|
+
the ability to model the XML content in classes.
|
46
54
|
|
47
|
-
|
55
|
+
### HappyMapper.parse(XML)
|
48
56
|
|
49
|
-
With no classes or configuration you can parse the example XML with little
|
57
|
+
With no classes or configuration you can parse the example XML with little
|
58
|
+
effort:
|
50
59
|
|
51
60
|
```ruby
|
52
61
|
address = HappyMapper.parse(ADDRESS_XML_DATA)
|
@@ -60,11 +69,14 @@ address.country.content # => Germany
|
|
60
69
|
|
61
70
|
It is important to be aware that this no configuration parsing is limited in capacity:
|
62
71
|
|
63
|
-
* All element names are converted to accessor methods with
|
72
|
+
* All element names are converted to accessor methods with
|
73
|
+
[underscorized](http://rubydoc.info/gems/activesupport/ActiveSupport/Inflector:underscore)
|
74
|
+
names
|
64
75
|
* All value fields are left as String types
|
65
|
-
* Determining if there is just one or multiple child elements is hard, so it
|
76
|
+
* Determining if there is just one or multiple child elements is hard, so it
|
77
|
+
assumes it is one until it finds another with the same name.
|
66
78
|
|
67
|
-
|
79
|
+
### Address.parse(XML)
|
68
80
|
|
69
81
|
Happymapper will let you easily model this information as a class:
|
70
82
|
|
@@ -75,64 +87,86 @@ class Address
|
|
75
87
|
include HappyMapper
|
76
88
|
|
77
89
|
tag 'address'
|
78
|
-
element :street, String, :
|
79
|
-
element :postcode, String, :
|
80
|
-
element :housenumber, Integer, :
|
81
|
-
element :city, String, :
|
82
|
-
element :country, String, :
|
90
|
+
element :street, String, tag: 'street'
|
91
|
+
element :postcode, String, tag: 'postcode'
|
92
|
+
element :housenumber, Integer, tag: 'housenumber'
|
93
|
+
element :city, String, tag: 'city'
|
94
|
+
element :country, String, tag: 'country'
|
83
95
|
end
|
84
96
|
```
|
85
97
|
|
86
|
-
To make a class HappyMapper compatible you simply `include HappyMapper` within
|
98
|
+
To make a class HappyMapper compatible you simply `include HappyMapper` within
|
99
|
+
the class definition. This takes care of all the work of defining all the
|
100
|
+
speciality methods and magic you need to get running. As you can see we
|
101
|
+
immediately start using these methods.
|
87
102
|
|
88
103
|
* `tag` matches the name of the XML tag name 'address'.
|
89
104
|
|
90
|
-
* `element` defines accessor methods for the specified symbol
|
105
|
+
* `element` defines accessor methods for the specified symbol
|
106
|
+
(e.g. `:street`,`:housenumber`) that will return the class type
|
107
|
+
(e.g. `String`,`Integer`) of the XML tag specified
|
108
|
+
(e.g. `tag: 'street'`, `tag: 'housenumber'`).
|
91
109
|
|
92
|
-
When you define an element with an accessor with the same name as the tag, this
|
110
|
+
When you define an element with an accessor with the same name as the tag, this
|
111
|
+
is the case for all the examples above, you can omit the `:tag`. These two
|
112
|
+
element declaration are equivalent to each other:
|
93
113
|
|
94
114
|
```ruby
|
95
|
-
element :street, String, :
|
115
|
+
element :street, String, tag: 'street'
|
96
116
|
element :street, String
|
97
117
|
```
|
98
118
|
|
99
|
-
Including the additional tag element is not going to hurt anything and in some
|
119
|
+
Including the additional tag element is not going to hurt anything and in some
|
120
|
+
cases will make it absolutely clear how these elements map to the XML. However,
|
121
|
+
once you know this rule, it is hard not to want to save yourself the
|
122
|
+
keystrokes.
|
100
123
|
|
101
124
|
Instead of `element` you may also use `has_one`:
|
102
125
|
|
103
126
|
```ruby
|
104
|
-
element :street, String, :
|
127
|
+
element :street, String, tag: 'street'
|
105
128
|
element :street, String
|
106
129
|
has_one :street, String
|
107
130
|
```
|
108
131
|
|
109
132
|
These three statements are equivalent to each other.
|
110
133
|
|
111
|
-
|
134
|
+
### Parsing
|
112
135
|
|
113
|
-
With the mapping of the address XML articulated in our Address class it is time
|
136
|
+
With the mapping of the address XML articulated in our Address class it is time
|
137
|
+
to parse the data:
|
114
138
|
|
115
139
|
```ruby
|
116
|
-
address = Address.parse(ADDRESS_XML_DATA, :
|
140
|
+
address = Address.parse(ADDRESS_XML_DATA, single: true)
|
117
141
|
puts address.street
|
118
142
|
```
|
119
143
|
|
120
|
-
Assuming that the constant `ADDRESS_XML_DATA` contains a string representation
|
144
|
+
Assuming that the constant `ADDRESS_XML_DATA` contains a string representation
|
145
|
+
of the address XML data this is fairly straight-forward save for the `parse`
|
146
|
+
method.
|
121
147
|
|
122
|
-
The `parse` method, like `tag` and `element` are all added when you included
|
148
|
+
The `parse` method, like `tag` and `element` are all added when you included
|
149
|
+
HappyMapper in the class. Parse is a wonderful, magical place that converts all
|
150
|
+
these declarations that you have made into the data structure you are about to
|
151
|
+
know and love.
|
123
152
|
|
124
|
-
But what about the
|
153
|
+
But what about the `single: true`? Right, that is because by default when
|
154
|
+
your object is all done parsing it will be an array. In this case an array with
|
155
|
+
one element, but an array none the less. So the following are equivalent to
|
156
|
+
each other:
|
125
157
|
|
126
158
|
```ruby
|
127
159
|
address = Address.parse(ADDRESS_XML_DATA).first
|
128
|
-
address = Address.parse(ADDRESS_XML_DATA, :
|
160
|
+
address = Address.parse(ADDRESS_XML_DATA, single: true)
|
129
161
|
```
|
130
162
|
|
131
|
-
The first one returns an array and we return the first instance, the second
|
163
|
+
The first one returns an array and we return the first instance, the second
|
164
|
+
will do that work for us inside of parse.
|
132
165
|
|
133
|
-
|
166
|
+
### Multiple Elements Mapping
|
134
167
|
|
135
|
-
What if our address XML was a little different, perhaps we allowed multiple
|
168
|
+
What if our address XML was a little different, perhaps we allowed multiple
|
169
|
+
streets:
|
136
170
|
|
137
171
|
<address>
|
138
172
|
<street>Milchstrasse</street>
|
@@ -143,20 +177,23 @@ What if our address XML was a little different, perhaps we allowed multiple stre
|
|
143
177
|
<country code="de">Germany</country>
|
144
178
|
</address>
|
145
179
|
|
146
|
-
Similar to `element` or `has_one`, the declaration for when you have multiple
|
180
|
+
Similar to `element` or `has_one`, the declaration for when you have multiple
|
181
|
+
elements you simply use:
|
147
182
|
|
148
183
|
```ruby
|
149
|
-
has_many :streets, String, :
|
184
|
+
has_many :streets, String, tag: 'street'
|
150
185
|
```
|
151
186
|
|
152
187
|
Your resulting `streets` method will now return an array.
|
153
188
|
|
154
189
|
```ruby
|
155
|
-
address = Address.parse(ADDRESS_XML_DATA, :
|
190
|
+
address = Address.parse(ADDRESS_XML_DATA, single: true)
|
156
191
|
puts address.streets.join('\n')
|
157
192
|
```
|
158
193
|
|
159
|
-
Imagine that you have to write `streets.join('\n')` for the rest of eternity
|
194
|
+
Imagine that you have to write `streets.join('\n')` for the rest of eternity
|
195
|
+
throughout your code. It would be a nightmare and one that you could avoid by
|
196
|
+
creating your own convenience method.
|
160
197
|
|
161
198
|
```ruby
|
162
199
|
require 'happymapper'
|
@@ -172,17 +209,18 @@ class Address
|
|
172
209
|
@streets.join('\n')
|
173
210
|
end
|
174
211
|
|
175
|
-
element :postcode, String, :
|
176
|
-
element :housenumber, String, :
|
177
|
-
element :city, String, :
|
178
|
-
element :country, String, :
|
212
|
+
element :postcode, String, tag: 'postcode'
|
213
|
+
element :housenumber, String, tag: 'housenumber'
|
214
|
+
element :city, String, tag: 'city'
|
215
|
+
element :country, String, tag: 'country'
|
179
216
|
end
|
180
217
|
```
|
181
218
|
|
182
|
-
Now when we call the method `streets` we get a single value, but we still have
|
219
|
+
Now when we call the method `streets` we get a single value, but we still have
|
220
|
+
the instance variable `@streets` if we ever need to the values as an array.
|
183
221
|
|
184
222
|
|
185
|
-
|
223
|
+
### Attribute Mapping
|
186
224
|
|
187
225
|
<address location='home'>
|
188
226
|
<street>Milchstrasse</street>
|
@@ -196,13 +234,13 @@ Now when we call the method `streets` we get a single value, but we still have t
|
|
196
234
|
Attributes are absolutely the same as `element` or `has_many`
|
197
235
|
|
198
236
|
```ruby
|
199
|
-
attribute :location, String, :
|
237
|
+
attribute :location, String, tag: 'location
|
200
238
|
```
|
201
239
|
|
202
|
-
Again, you can omit the tag if the attribute accessor symbol matches the name
|
203
|
-
|
240
|
+
Again, you can omit the tag if the attribute accessor symbol matches the name
|
241
|
+
of the attribute.
|
204
242
|
|
205
|
-
|
243
|
+
#### Attributes On Empty Child Elements
|
206
244
|
|
207
245
|
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
|
208
246
|
<id>tag:all-the-episodes.heroku.com,2005:/tv_shows</id>
|
@@ -212,12 +250,14 @@ Again, you can omit the tag if the attribute accessor symbol matches the name of
|
|
212
250
|
<updated>2011-07-10T06:52:27Z</updated>
|
213
251
|
</feed>
|
214
252
|
|
215
|
-
In this case you would need to map an element to a new `Link` class just to
|
253
|
+
In this case you would need to map an element to a new `Link` class just to
|
254
|
+
access `<link>`s attributes, except that there is an alternate syntax. Instead
|
255
|
+
of
|
216
256
|
|
217
257
|
```ruby
|
218
258
|
class Feed
|
219
259
|
# ....
|
220
|
-
has_many :links, Link, :
|
260
|
+
has_many :links, Link, tag: 'link', xpath: '.'
|
221
261
|
end
|
222
262
|
|
223
263
|
class Link
|
@@ -232,17 +272,25 @@ end
|
|
232
272
|
You can drop the `Link` class and simply replace the `has_many` on `Feed` with
|
233
273
|
|
234
274
|
```ruby
|
235
|
-
element :link, String, :
|
275
|
+
element :link, String, single: false, attributes: { rel: String, type: String, href: String }
|
236
276
|
```
|
237
277
|
|
238
|
-
As there is no content, the type given for `:link` (`String` above) is
|
278
|
+
As there is no content, the type given for `:link` (`String` above) is
|
279
|
+
irrelevant, but `nil` won't work and other types may try to perform typecasting
|
280
|
+
and fail. You can omit the single: false for elements that only occur once
|
281
|
+
within their parent.
|
239
282
|
|
240
|
-
This syntax is most appropriate for elements that (a) have attributes but no
|
283
|
+
This syntax is most appropriate for elements that (a) have attributes but no
|
284
|
+
content and (b) only occur at only one level of the heirarchy. If `<feed>`
|
285
|
+
contained another element that also contained a `<link>` (as atom feeds
|
286
|
+
generally do) it would be DRY-er to use the first syntax, i.e. with a separate
|
287
|
+
`Link` class.
|
241
288
|
|
242
289
|
|
243
|
-
|
290
|
+
### Class composition (and Text Node)
|
244
291
|
|
245
|
-
Our address has a country and that country element has a code. Up until this
|
292
|
+
Our address has a country and that country element has a code. Up until this
|
293
|
+
point we neglected it as we declared a `country` as being a `String`.
|
246
294
|
|
247
295
|
<address location='home'>
|
248
296
|
<street>Milchstrasse</street>
|
@@ -278,33 +326,40 @@ class Address
|
|
278
326
|
|
279
327
|
tag 'address'
|
280
328
|
|
281
|
-
has_many :streets, String, :
|
329
|
+
has_many :streets, String, tag: 'street'
|
282
330
|
|
283
331
|
def streets
|
284
332
|
@streets.join('\n')
|
285
333
|
end
|
286
334
|
|
287
|
-
element :postcode, String, :
|
288
|
-
element :housenumber, String, :
|
289
|
-
element :city, String, :
|
290
|
-
element :country, Country, :
|
335
|
+
element :postcode, String, tag: 'postcode'
|
336
|
+
element :housenumber, String, tag: 'housenumber'
|
337
|
+
element :city, String, tag: 'city'
|
338
|
+
element :country, Country, tag: 'country'
|
291
339
|
end
|
292
340
|
```
|
293
341
|
|
294
|
-
Instead of `String`, `Boolean`, or `Integer` we say that it is a `Country` and
|
342
|
+
Instead of `String`, `Boolean`, or `Integer` we say that it is a `Country` and
|
343
|
+
HappyMapper takes care of the details of continuing the XML mapping through the
|
344
|
+
country element.
|
295
345
|
|
296
346
|
```ruby
|
297
|
-
address = Address.parse(ADDRESS_XML_DATA, :
|
347
|
+
address = Address.parse(ADDRESS_XML_DATA, single: true)
|
298
348
|
puts address.country.code
|
299
349
|
```
|
300
350
|
|
301
|
-
A quick note, in the above example we used the constant `Country`. We could
|
351
|
+
A quick note, in the above example we used the constant `Country`. We could
|
352
|
+
have used `'Country'`. The nice part of using the latter declaration, enclosed
|
353
|
+
in quotes, is that you do not have to define your class before this class. So
|
354
|
+
Country and Address can live in separate files and as long as both constants
|
355
|
+
are available when it comes time to parse you are golden.
|
302
356
|
|
303
|
-
|
357
|
+
### Custom XPATH
|
304
358
|
|
305
|
-
|
359
|
+
#### Has One, Has Many
|
306
360
|
|
307
|
-
Getting to elements deep down within your XML can be a little more work if you
|
361
|
+
Getting to elements deep down within your XML can be a little more work if you
|
362
|
+
did not have xpath support. Consider the following example:
|
308
363
|
|
309
364
|
<media>
|
310
365
|
<gallery>
|
@@ -320,22 +375,26 @@ Getting to elements deep down within your XML can be a little more work if you d
|
|
320
375
|
</picture>
|
321
376
|
</media>
|
322
377
|
|
323
|
-
You may want to map the sub-elements contained buried in the 'gallery' as top
|
378
|
+
You may want to map the sub-elements contained buried in the 'gallery' as top
|
379
|
+
level items in the media. Traditionally you could use class composition to
|
380
|
+
accomplish this task, however, using the xpath attribute you have the ability
|
381
|
+
to shortcut some of that work.
|
324
382
|
|
325
383
|
```ruby
|
326
384
|
class Media
|
327
385
|
include HappyMapper
|
328
386
|
|
329
|
-
has_one :title, String, :
|
330
|
-
has_one :link, String, :
|
387
|
+
has_one :title, String, xpath: 'gallery/title'
|
388
|
+
has_one :link, String, xpath: 'gallery/title/@href'
|
331
389
|
end
|
332
390
|
```
|
333
391
|
|
334
|
-
|
392
|
+
### Shared Functionality
|
335
393
|
|
336
|
-
|
394
|
+
#### Inheritance Approach
|
337
395
|
|
338
|
-
While mapping XML to objects you may arrive at a point where you have two or
|
396
|
+
While mapping XML to objects you may arrive at a point where you have two or
|
397
|
+
more very similar structures.
|
339
398
|
|
340
399
|
```ruby
|
341
400
|
class Article
|
@@ -361,7 +420,9 @@ class Gallery
|
|
361
420
|
end
|
362
421
|
```
|
363
422
|
|
364
|
-
In this example there are definitely two similarities between our two pieces of
|
423
|
+
In this example there are definitely two similarities between our two pieces of
|
424
|
+
content. So much so that you might be included to create an inheritance
|
425
|
+
structure to save yourself some keystrokes.
|
365
426
|
|
366
427
|
```ruby
|
367
428
|
class Content
|
@@ -385,7 +446,7 @@ class Gallery < Content
|
|
385
446
|
end
|
386
447
|
```
|
387
448
|
|
388
|
-
|
449
|
+
#### Module Mixins Approach
|
389
450
|
|
390
451
|
You can also solve the above problem through mixins.
|
391
452
|
|
@@ -417,12 +478,17 @@ class Gallery
|
|
417
478
|
end
|
418
479
|
```
|
419
480
|
|
420
|
-
Here, when we include `Content` in both of these classes the module method
|
481
|
+
Here, when we include `Content` in both of these classes the module method
|
482
|
+
`#included` is called and our class is given as a parameter. So we take that
|
483
|
+
opportunity to do some surgery and define our happymapper elements as well as
|
484
|
+
any other methods that may rely on those instance variables that come along in
|
485
|
+
the package.
|
421
486
|
|
422
487
|
|
423
|
-
|
488
|
+
### Filtering with XPATH (non-greedy)
|
424
489
|
|
425
|
-
I ran into a case where I wanted to capture all the pictures that were directly
|
490
|
+
I ran into a case where I wanted to capture all the pictures that were directly
|
491
|
+
under media, but not the ones contained within a gallery.
|
426
492
|
|
427
493
|
<media>
|
428
494
|
<gallery>
|
@@ -445,15 +511,15 @@ require 'happymapper'
|
|
445
511
|
class Media
|
446
512
|
include HappyMapper
|
447
513
|
|
448
|
-
has_many :galleries, Gallery, :
|
449
|
-
has_many :pictures, Picture, :
|
514
|
+
has_many :galleries, Gallery, tag: 'gallery'
|
515
|
+
has_many :pictures, Picture, tag: 'picture'
|
450
516
|
end
|
451
517
|
```
|
452
518
|
|
453
519
|
However when I parsed the media xml the number of pictures returned to me was 2, not 1.
|
454
520
|
|
455
521
|
```ruby
|
456
|
-
pictures = Media.parse(MEDIA_XML
|
522
|
+
pictures = Media.parse(MEDIA_XML,single: true).pictures
|
457
523
|
pictures.length.should == 1 # => Failed Expectation
|
458
524
|
```
|
459
525
|
|
@@ -466,16 +532,17 @@ To limit an element from being greedy and only finding elements at the
|
|
466
532
|
level of the current node you can specify an XPATH.
|
467
533
|
|
468
534
|
```ruby
|
469
|
-
has_many :pictures, Picture, :
|
535
|
+
has_many :pictures, Picture, tag: 'picture', xpath: '.'
|
470
536
|
```
|
471
537
|
|
472
538
|
`.` states that we are only interested in pictures that can be found directly
|
473
539
|
under the current node. So when we parse again we will have only our one element.
|
474
540
|
|
475
541
|
|
476
|
-
|
542
|
+
### Namespaces
|
477
543
|
|
478
|
-
Obviously your XML and these trivial examples are easy to map and parse because
|
544
|
+
Obviously your XML and these trivial examples are easy to map and parse because
|
545
|
+
they lack the treacherous namespaces that befall most XML files.
|
479
546
|
|
480
547
|
Perhaps our `address` XML is really swarming with namespaces:
|
481
548
|
|
@@ -488,7 +555,10 @@ Perhaps our `address` XML is really swarming with namespaces:
|
|
488
555
|
<prefix:country code="de">Germany</prefix:country>
|
489
556
|
</prefix:address>
|
490
557
|
|
491
|
-
Here again is our address example with a made up namespace called `prefix` that
|
558
|
+
Here again is our address example with a made up namespace called `prefix` that
|
559
|
+
comes direct to use from unicornland, a very magical place indeed. Well we are
|
560
|
+
going to have to do some work on our class definition and that simply adding
|
561
|
+
this one liner to the `Address` class:
|
492
562
|
|
493
563
|
```ruby
|
494
564
|
class Address
|
@@ -500,17 +570,21 @@ class Address
|
|
500
570
|
end
|
501
571
|
```
|
502
572
|
|
503
|
-
Of course, if that is too easy for you, you can append a
|
573
|
+
Of course, if that is too easy for you, you can append a `namespace: 'prefix`
|
574
|
+
to every one of the elements that you defined.
|
504
575
|
|
505
576
|
```ruby
|
506
|
-
has_many :street, String, :
|
507
|
-
element :postcode, String, :
|
508
|
-
element :housenumber, String, :
|
509
|
-
element :city, String, :
|
510
|
-
element :country, Country, :
|
577
|
+
has_many :street, String, tag: 'street', namespace: 'prefix'
|
578
|
+
element :postcode, String, tag: 'postcode', namespace: 'prefix'
|
579
|
+
element :housenumber, String, tag: 'housenumber', namespace: 'prefix'
|
580
|
+
element :city, String, tag: 'city', namespace: 'prefix'
|
581
|
+
element :country, Country, tag: 'country', namespace: 'prefix'
|
511
582
|
```
|
512
583
|
|
513
|
-
I definitely recommend the former, as it saves you a whole hell of lot of
|
584
|
+
I definitely recommend the former, as it saves you a whole hell of lot of
|
585
|
+
typing. However, there are times when appending a namespace to an element
|
586
|
+
declaration is important and that is when it has a different namespace than
|
587
|
+
`namespace 'prefix'`.
|
514
588
|
|
515
589
|
Imagine that our `country` actually belonged to a completely different namespace.
|
516
590
|
|
@@ -527,63 +601,76 @@ Imagine that our `country` actually belonged to a completely different namespace
|
|
527
601
|
Well we would need to specify that namespace:
|
528
602
|
|
529
603
|
```ruby
|
530
|
-
element :country, Country, :
|
604
|
+
element :country, Country, tag: 'country', namespace: 'different'
|
531
605
|
```
|
532
606
|
|
533
607
|
With that we should be able to parse as we once did.
|
534
608
|
|
535
|
-
|
609
|
+
### Large Datasets (in_groups_of)
|
536
610
|
|
537
|
-
When dealing with large sets of XML that simply cannot or should not be placed
|
611
|
+
When dealing with large sets of XML that simply cannot or should not be placed
|
612
|
+
into memory the objects can be handled in groups through the `:in_groups_of`
|
613
|
+
parameter.
|
538
614
|
|
539
615
|
```ruby
|
540
|
-
Address.parse(LARGE_ADDRESS_XML_DATA
|
616
|
+
Address.parse(LARGE_ADDRESS_XML_DATA,in_groups_of: 5) do |group|
|
541
617
|
puts address.streets
|
542
618
|
end
|
543
619
|
```
|
544
620
|
|
545
|
-
This trivial block will parse the large set of XML data and in groups of 5
|
621
|
+
This trivial block will parse the large set of XML data and in groups of 5
|
622
|
+
addresses at a time display the streets.
|
546
623
|
|
547
|
-
|
624
|
+
### Saving to XML
|
548
625
|
|
549
|
-
Saving a class to XML is as easy as calling `#to_xml`. The end result will be
|
626
|
+
Saving a class to XML is as easy as calling `#to_xml`. The end result will be
|
627
|
+
the current state of your object represented as xml. Let's cover some details
|
628
|
+
that are sometimes necessary and features present to make your life easier.
|
550
629
|
|
551
630
|
|
552
|
-
|
631
|
+
#### :on_save
|
553
632
|
|
554
|
-
When you are saving data to xml it is often important to change or manipulate
|
633
|
+
When you are saving data to xml it is often important to change or manipulate
|
634
|
+
data to a particular format. For example, a time object:
|
555
635
|
|
556
636
|
```ruby
|
557
|
-
has_one :published_time, Time, :
|
637
|
+
has_one :published_time, Time, on_save: lambda {|time| time.strftime("%H:%M:%S") if time }
|
558
638
|
```
|
559
639
|
|
560
|
-
Here we add the options `:on_save` and specify a lambda which will be executed
|
640
|
+
Here we add the options `:on_save` and specify a lambda which will be executed
|
641
|
+
on the method call to `:published_time`.
|
561
642
|
|
562
|
-
|
643
|
+
#### :state_when_nil
|
563
644
|
|
564
|
-
When an element contains a nil value, or perhaps the result of the :on_save
|
645
|
+
When an element contains a nil value, or perhaps the result of the :on_save
|
646
|
+
lambda correctly results in a nil value you will be happy that the element will
|
647
|
+
not appear in the resulting XML. However, there are time when you will want to
|
648
|
+
see that element and that's when `:state_when_nil` is there for you.
|
565
649
|
|
566
650
|
```ruby
|
567
|
-
has_one :favorite_color, String, :
|
651
|
+
has_one :favorite_color, String, state_when_nil: true
|
568
652
|
```
|
569
653
|
|
570
|
-
The resulting XML will include the 'favorite_color' element even if the
|
654
|
+
The resulting XML will include the 'favorite_color' element even if the
|
655
|
+
favorite color has not been specified.
|
571
656
|
|
572
|
-
|
657
|
+
#### :read_only
|
573
658
|
|
574
659
|
When an element, attribute, or text node is a value that you have no interest in
|
575
660
|
saving to XML, you can ensure that takes place by stating that it is `read only`.
|
576
661
|
|
577
662
|
```ruby
|
578
|
-
has_one :modified, Boolean, :
|
579
|
-
attribute :temporary, Boolean, :
|
663
|
+
has_one :modified, Boolean, read_only: true
|
664
|
+
attribute :temporary, Boolean, read_only: true
|
580
665
|
```
|
581
666
|
|
582
667
|
This is useful if perhaps the incoming XML is different than the out-going XML.
|
583
668
|
|
584
|
-
|
669
|
+
#### namespaces
|
585
670
|
|
586
|
-
Parsing the XML to objects only required you to simply specify the prefix of
|
671
|
+
Parsing the XML to objects only required you to simply specify the prefix of
|
672
|
+
the namespace you wanted to parse, when you persist to xml you will need to
|
673
|
+
define your namespaces so that they are correctly captured.
|
587
674
|
|
588
675
|
```ruby
|
589
676
|
class Address
|
@@ -599,7 +686,7 @@ class Address
|
|
599
686
|
element :postcode, String
|
600
687
|
element :housenumber, String
|
601
688
|
element :city, String
|
602
|
-
element :country, Country, :
|
689
|
+
element :country, Country, tag: 'country', namespace: 'different'
|
603
690
|
|
604
691
|
end
|
605
|
-
```
|
692
|
+
```
|