awesome_xml 1.0.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.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +498 -0
- data/Rakefile +6 -0
- data/lib/awesome_xml/class_methods.rb +82 -0
- data/lib/awesome_xml/duration/chunk_parser.rb +52 -0
- data/lib/awesome_xml/duration/format/dynamic_chunk.rb +76 -0
- data/lib/awesome_xml/duration/format/static_chunk.rb +33 -0
- data/lib/awesome_xml/duration/format.rb +48 -0
- data/lib/awesome_xml/native_type.rb +35 -0
- data/lib/awesome_xml/node_evaluator.rb +44 -0
- data/lib/awesome_xml/node_xpath.rb +46 -0
- data/lib/awesome_xml/type.rb +39 -0
- data/lib/awesome_xml/types/date_time.rb +21 -0
- data/lib/awesome_xml/types/duration.rb +54 -0
- data/lib/awesome_xml/types/float.rb +14 -0
- data/lib/awesome_xml/types/integer.rb +14 -0
- data/lib/awesome_xml/types/text.rb +18 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6e0fdacdca25fa723e63830f68a8b2c250c4b1df
|
4
|
+
data.tar.gz: f49a9d5503da9076d8608e98168267ebf572a304
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1acd280ece6c36b3aa553e5b12901a8df5bf566363a7c4b0854bf285d23c0b817b74800c57255b885c4742b68b473fa3c53afa6ca78d815f9aef57ee734ac162
|
7
|
+
data.tar.gz: bf932106ae5e58ff57922688405c50074fd6e7640b3fa1f8ccc1066ad9abd2cc1e0fac1b17aa93affdabd483763ade2b0dd3248d76ace5d7308d8b7b05379bc7
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 fromAtoB
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,498 @@
|
|
1
|
+
# AwesomeXML
|
2
|
+
|
3
|
+
AwesomeXML is an XML mapping library that lets your Ruby classes parse arbitrary data from XML documents into a hash.
|
4
|
+
The hash can be structured completely freely. The parsing itself is based on [Nokogiri](https://github.com/sparklemotion/nokogiri).
|
5
|
+
The concept is very similar to that of [happymapper](https://github.com/dam5s/happymapper).
|
6
|
+
|
7
|
+
## Include it
|
8
|
+
|
9
|
+
Include `AwesomeXML` in any class you want to have all the capabilities this gem provides to you.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class MyDocument
|
13
|
+
include AwesomeXML
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
## Feed it
|
18
|
+
|
19
|
+
Your class will now have a `.parse` class method which takes in a single argument containing a string
|
20
|
+
representing an XML document. It returns an instance of your class. Like this:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
my_document = MyDocument.parse('<document><title>This is a document.</title></document>')
|
24
|
+
=> #<MyDocument:0x007fc57d239520 @xml=#<Nokogiri::XML::Document:0x3fe2be91ca54 name="document" children=[#<Nokogiri::XML::Element:0x3fe2be91c70c name="document" children=[#<Nokogiri::XML::Element:0x3fe2be91c52c name="title" children=[#<Nokogiri::XML::Text:0x3fe2be91c34c "This is a document.">]>]>]>, @parent_node=nil>
|
25
|
+
```
|
26
|
+
|
27
|
+
## Create your first awesome node
|
28
|
+
|
29
|
+
Let's say you have this XML document and you want to parse the content of the `<title></title>` tag.
|
30
|
+
|
31
|
+
```xml
|
32
|
+
<document>
|
33
|
+
<title>This is a document.</title>
|
34
|
+
</document>
|
35
|
+
```
|
36
|
+
|
37
|
+
The `AwesomeXML` module defines several class methods on your class that that help you with that.
|
38
|
+
The most basic one is the `.node` method.
|
39
|
+
Its arguments are
|
40
|
+
- a symbol, which will be the name of your node.
|
41
|
+
- the type which the parser will assume the parsed value has
|
42
|
+
- an options hash (optional)
|
43
|
+
|
44
|
+
The type can either be a native type given in the form of a symbol (currently supported are `:text`,
|
45
|
+
`:integer`, `:float`, `:duration` and `:date_time`), or a custom class. You can also pass in a string containing
|
46
|
+
a class name in case the class constant is not yet defined at the time you run the `.node` method.
|
47
|
+
More about that later.
|
48
|
+
|
49
|
+
Let's try it!
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class MyDocument
|
53
|
+
include AwesomeXML
|
54
|
+
|
55
|
+
set_context 'document'
|
56
|
+
node :title, :text
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
Notice we needed to set a context node `'document'` so the `title` node could be found. `.set_context` takes an XPath
|
61
|
+
and sets the current node for the whole class. There's a few other ways you can achievement the same thing as above.
|
62
|
+
For example by passing in an explicit XPath.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class MyDocument
|
66
|
+
include AwesomeXML
|
67
|
+
|
68
|
+
node :title, :text, xpath: 'document/title'
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
If you don't pass an XPath (like in the very first example), the default is assumed, which is `"./#{name_of_you_node}"`.
|
73
|
+
Or, if you don't want to set the context node for the whole class, you can use `.with_context`, which takes a block:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class MyDocument
|
77
|
+
include AwesomeXML
|
78
|
+
|
79
|
+
with_context 'document' do
|
80
|
+
node :title, :text
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
All of these make a few things possible. Firstly, after calling `MyDocument.parse(xml_string)`, you can access
|
86
|
+
an attribute reader method with the name of your node (`title`). It contains the value parsed from your XML document.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
my_document.title
|
90
|
+
=> "This is a document."
|
91
|
+
```
|
92
|
+
|
93
|
+
Secondly, it changes the result of the `#to_hash` method of your class. More about that later.
|
94
|
+
|
95
|
+
## Attributes, elements and `self`
|
96
|
+
|
97
|
+
Let's say your XML document has important data hidden in the attributes of tags:
|
98
|
+
|
99
|
+
```xml
|
100
|
+
<document title='This is a document.'/>
|
101
|
+
```
|
102
|
+
|
103
|
+
One way to do it is to pass the option `attribute: true` to your node:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class MyDocument
|
107
|
+
include AwesomeXML
|
108
|
+
|
109
|
+
set_context 'document'
|
110
|
+
node :title, :text, attribute: true
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
This is the same as passing an explicit XPath `"./@#{name_of_you_node}"`.
|
115
|
+
Instead of just `true`, you can pass in a symbol (or string) to the `:attribute` option that will then be used to
|
116
|
+
build the XPath to your node, instead of using the node name. Use this whenever you want your nodes
|
117
|
+
to be named differently than in the XML document.
|
118
|
+
|
119
|
+
This is also true for the other two types of nodes: elements and `self`. By default, `AwesomeXML` will look for
|
120
|
+
elements, so passing the option `element: true` will do nothing. But you can use the option like `:attribute`, in
|
121
|
+
that you can pass something else than `true` to tell the parser to look for an element with a different name.
|
122
|
+
|
123
|
+
The last type of node is `self`. Pass in `self: true` if you want to access the content of the current context
|
124
|
+
node itself. This is equivalent to passing in `xpath: '.'`. Changing the option value will do nothing.
|
125
|
+
|
126
|
+
## Method nodes
|
127
|
+
|
128
|
+
If you want, you can define your node in a method. Like this:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class MyDocument
|
132
|
+
include AwesomeXML
|
133
|
+
|
134
|
+
set_context 'document'
|
135
|
+
node :title, :text
|
136
|
+
method_node :reversed_title
|
137
|
+
|
138
|
+
def reversed_title
|
139
|
+
title.reverse
|
140
|
+
end
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
You might say, ok, that's useless. I don't need to have the node define my `#reversed_title` method for me,
|
145
|
+
I'm doing that myself already! And you would be correct. There's one side effect, though, related to
|
146
|
+
the following awesome method that is provided to you:
|
147
|
+
|
148
|
+
## `#to_hash`
|
149
|
+
|
150
|
+
Including `AwesomeXML` will define the method `#to_hash` on your class. It traverses all the nodes
|
151
|
+
you defined in your class (including the ones declared with `.method_node`) and returns values in a hash
|
152
|
+
that follows the structure you defined. Let's take the example from the section above. Then, `#to_hash`
|
153
|
+
would do the following:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
my_document.to_hash
|
157
|
+
=> {:title=>"This is a document.", :reversed_title=>".tnemucod a si sihT"}
|
158
|
+
```
|
159
|
+
|
160
|
+
Let's step it up a little.
|
161
|
+
|
162
|
+
## Child nodes
|
163
|
+
|
164
|
+
Let's say you have a slightly more complicated XML document. Baby steps.
|
165
|
+
|
166
|
+
```xml
|
167
|
+
<document>
|
168
|
+
<title>This is a document.</title>
|
169
|
+
<item ref='123'>
|
170
|
+
<owner>John Doe</owner>
|
171
|
+
</item>
|
172
|
+
</document>
|
173
|
+
```
|
174
|
+
|
175
|
+
If you want your parsed hash to look like this:
|
176
|
+
```ruby
|
177
|
+
{ title: "This is a document.", item: { reference: 123, owner: 'John Doe' } }
|
178
|
+
```
|
179
|
+
You can do that by creating a node of the type of another class that also includes `AwesomeXML`.
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
class MyDocument
|
183
|
+
include AwesomeXML
|
184
|
+
|
185
|
+
set_context 'document'
|
186
|
+
node :title, :text
|
187
|
+
node :item, 'Item'
|
188
|
+
|
189
|
+
class Item
|
190
|
+
include AwesomeXML
|
191
|
+
|
192
|
+
node :reference, :integer, attribute: :ref
|
193
|
+
node :owner, :text
|
194
|
+
end
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
198
|
+
Easy! You might have noticed that the context node for the `Item` class is automatically set. No need
|
199
|
+
to call `.set_context` except you want to override the default, of course.
|
200
|
+
|
201
|
+
If you want, you can also pass in the class itself instead of a string with the class name.
|
202
|
+
Just make sure that it is defined before you use it in your `.node` method! Like this:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
class MyDocument
|
206
|
+
include AwesomeXML
|
207
|
+
|
208
|
+
class Item
|
209
|
+
include AwesomeXML
|
210
|
+
|
211
|
+
node :reference, :integer, attribute: :ref
|
212
|
+
node :owner, :text
|
213
|
+
end
|
214
|
+
|
215
|
+
set_context 'document'
|
216
|
+
node :title, :text
|
217
|
+
node :item, Item
|
218
|
+
end
|
219
|
+
```
|
220
|
+
|
221
|
+
## Array nodes
|
222
|
+
|
223
|
+
What if you have more than one `<item/>`? Say your XML document looks like this:
|
224
|
+
|
225
|
+
```xml
|
226
|
+
<document>
|
227
|
+
<item ref='123'/>
|
228
|
+
<item ref='456'/>
|
229
|
+
<item ref='789'/>
|
230
|
+
</document>
|
231
|
+
```
|
232
|
+
|
233
|
+
And you want your parsed hash to look like this:
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
{ refs: [123, 456, 789] }
|
237
|
+
```
|
238
|
+
|
239
|
+
Fret no more, just use the option `array: true`:
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
class MyDocument
|
243
|
+
include AwesomeXML
|
244
|
+
|
245
|
+
set_context 'document/item'
|
246
|
+
node :refs, :integer, attribute: true, array: true
|
247
|
+
end
|
248
|
+
```
|
249
|
+
|
250
|
+
Pretty self-explanatory, right? `AwesomeXML` even singularizes your node name automatically!
|
251
|
+
|
252
|
+
Okay, you say, that's a very simple array, indeed. What if I want an array of hashes? Like so:
|
253
|
+
```ruby
|
254
|
+
{ items: [{ ref: 123 }, { ref: 456 }, { ref: 789 }] }
|
255
|
+
```
|
256
|
+
|
257
|
+
Just combine the two things we last learned:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
class MyDocument
|
261
|
+
include AwesomeXML
|
262
|
+
|
263
|
+
set_context 'document'
|
264
|
+
node :items, 'Item', array: true
|
265
|
+
|
266
|
+
class Item
|
267
|
+
include AwesomeXML
|
268
|
+
|
269
|
+
node :ref, :integer, attribute: true
|
270
|
+
end
|
271
|
+
end
|
272
|
+
```
|
273
|
+
|
274
|
+
Awesome, right? You've got a few more notches you can kick it up, though.
|
275
|
+
|
276
|
+
## Passing blocks
|
277
|
+
|
278
|
+
That's right, you can pass blocks. It's actually very simple. All `*_node` methods (except `.method_node`
|
279
|
+
and `.constant_node`) define instance methods that yield their result to the block you specify. This lets you
|
280
|
+
do pretty much anything you want. Let's say you don't like the way the items are numbered in your XML document:
|
281
|
+
|
282
|
+
```xml
|
283
|
+
<document>
|
284
|
+
<item index='1'/>
|
285
|
+
<item index='2'/>
|
286
|
+
<item index='3'/>
|
287
|
+
</document>
|
288
|
+
```
|
289
|
+
|
290
|
+
Yuck. Let's fix that:
|
291
|
+
|
292
|
+
```ruby
|
293
|
+
class MyDocument
|
294
|
+
include AwesomeXML
|
295
|
+
|
296
|
+
set_context 'document'
|
297
|
+
node(:items, :integer, array: true, xpath: './item/@index') do |values|
|
298
|
+
values.map { |value| value - 1 }
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
my_document.to_hash
|
303
|
+
=> {:items=>[0, 1, 2]}
|
304
|
+
|
305
|
+
```
|
306
|
+
|
307
|
+
That's better. Note that array nodes yield the whole array to the block and not an `Enumerator`.
|
308
|
+
|
309
|
+
There's another twist to this block passing, though. AwesomeXML also yields the instance of your class
|
310
|
+
to the block so you can actually access other nodes inside the block! Let's see it in action.
|
311
|
+
|
312
|
+
Your XML data:
|
313
|
+
```xml
|
314
|
+
<document>
|
315
|
+
<items multiplicator='100'>
|
316
|
+
<item value='1'/>
|
317
|
+
<item value='2'/>
|
318
|
+
<item value='3'/>
|
319
|
+
</items>
|
320
|
+
</document>
|
321
|
+
```
|
322
|
+
|
323
|
+
Your `AwesomeXML` class:
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
class MyDocument
|
327
|
+
include AwesomeXML
|
328
|
+
|
329
|
+
set_context 'document/items'
|
330
|
+
node :multiplicator, :integer, attribute: true
|
331
|
+
node(:item_values, :integer, array: :true, xpath: './item/@value') do |values, instance|
|
332
|
+
values.map { |value| value * instance.multiplicator }
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
my_document.to_hash
|
337
|
+
=> {:multiplicator=>100, :item_values=>[100, 200, 300]}
|
338
|
+
```
|
339
|
+
|
340
|
+
## Overwriting attribute readers
|
341
|
+
|
342
|
+
You can achieve the same effect as passing blocks by redefining the attribute accessors that `AwesomeXML`
|
343
|
+
usually defines for you. Arguably, this is the more elegant method, although you might prefer the block
|
344
|
+
syntax's brevity for more simple operations.
|
345
|
+
|
346
|
+
Let's see how the example from above would look in this style:
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
class MyDocument
|
350
|
+
include AwesomeXML
|
351
|
+
|
352
|
+
set_context 'document/items'
|
353
|
+
node :multiplicator, :integer, attribute: true
|
354
|
+
node :item_values, :integer, array: :true, xpath: './item/@value'
|
355
|
+
|
356
|
+
def item_values
|
357
|
+
@item_values.map { |value| value * multiplicator }
|
358
|
+
end
|
359
|
+
end
|
360
|
+
```
|
361
|
+
|
362
|
+
## `#parent_node`
|
363
|
+
|
364
|
+
This method is available on all class instances including the `AwesomeXML` module. It returns the
|
365
|
+
instance of the class it was initialized from. Let's see how that can be useful. Let's again use
|
366
|
+
the XML document from the above two examples.
|
367
|
+
|
368
|
+
```xml
|
369
|
+
<document>
|
370
|
+
<items multiplicator='100'>
|
371
|
+
<item value='1'/>
|
372
|
+
<item value='2'/>
|
373
|
+
<item value='3'/>
|
374
|
+
</items>
|
375
|
+
</document>
|
376
|
+
```
|
377
|
+
|
378
|
+
This time, you want each `<item/>` to be represented by its own hash. Like this:
|
379
|
+
```ruby
|
380
|
+
my_document.to_hash
|
381
|
+
=> {:items=>[{:value=>100}, {:value=>200}, {:value=>300}]}
|
382
|
+
```
|
383
|
+
|
384
|
+
There's (at least) two ways to do this. You can either define the `multiplicator` node on your child class:
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
class MyDocument
|
388
|
+
include AwesomeXML
|
389
|
+
|
390
|
+
set_context 'document/items'
|
391
|
+
node :items, 'Item', array: true
|
392
|
+
|
393
|
+
class Item
|
394
|
+
include AwesomeXML
|
395
|
+
|
396
|
+
node :multiplicator, :integer, xpath: '../@multiplicator', private: true
|
397
|
+
node :value, :integer, attribute: true
|
398
|
+
|
399
|
+
def value
|
400
|
+
@value * multiplicator
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
```
|
405
|
+
|
406
|
+
Or, alternatively, you can use `#parent_node`:
|
407
|
+
|
408
|
+
```ruby
|
409
|
+
class MyDocument
|
410
|
+
include AwesomeXML
|
411
|
+
|
412
|
+
set_context 'document/items'
|
413
|
+
node :multiplicator, :integer, attribute: true, private: true
|
414
|
+
node :items, 'Item', array: true
|
415
|
+
|
416
|
+
class Item
|
417
|
+
include AwesomeXML
|
418
|
+
|
419
|
+
node :value, :integer, attribute: true
|
420
|
+
|
421
|
+
def value
|
422
|
+
@value * parent_node.multiplicator
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
```
|
427
|
+
|
428
|
+
Both are perfectly acceptable. The latter is slightly more efficient because the `multiplicator` node
|
429
|
+
will only be parsed once instead of once per `item`. You may have noticed that we used a new option:
|
430
|
+
`:private`. I'll explain it in the next section.
|
431
|
+
|
432
|
+
## More options
|
433
|
+
|
434
|
+
### `:private`
|
435
|
+
|
436
|
+
The `:private` option removes your node from the ones being evaluated in `#to_hash`. This is
|
437
|
+
helpful if you want to parse something that is not meant to end up in the parsed schema. Let's revisit the example
|
438
|
+
from above.
|
439
|
+
|
440
|
+
```xml
|
441
|
+
<document>
|
442
|
+
<items multiplicator='100'>
|
443
|
+
<item value='1'/>
|
444
|
+
<item value='2'/>
|
445
|
+
<item value='3'/>
|
446
|
+
</items>
|
447
|
+
</document>
|
448
|
+
```
|
449
|
+
|
450
|
+
Now let's try and remove the `multiplicator` from your parsed hash. Like so:
|
451
|
+
|
452
|
+
```ruby
|
453
|
+
class MyDocument
|
454
|
+
include AwesomeXML
|
455
|
+
|
456
|
+
set_context 'document/items'
|
457
|
+
node :multiplicator, :integer, attribute: true, private: true
|
458
|
+
node :item_values, :integer, array: :true, xpath: './item/@value'
|
459
|
+
|
460
|
+
def item_values
|
461
|
+
@item_values.map { |value| value * multiplicator }
|
462
|
+
end
|
463
|
+
end
|
464
|
+
```
|
465
|
+
|
466
|
+
```ruby
|
467
|
+
my_document.to_hash
|
468
|
+
=> {:item_values=>[100, 200, 300]}
|
469
|
+
```
|
470
|
+
|
471
|
+
Awesome.
|
472
|
+
|
473
|
+
### `:default` and `:default_empty`
|
474
|
+
|
475
|
+
Using these options, you can control what happens in case the tag or attribute you wanted to parse is empty
|
476
|
+
or doesn't even exist. For the former, use `:default_empty`, for the latter, use `:default`.
|
477
|
+
|
478
|
+
## More node types
|
479
|
+
|
480
|
+
Let's talk about duration nodes. As you may remember, `:duration` is of the native types for `.node`.
|
481
|
+
They return `ActiveSupport::Duration` objects, which interact freely with each other and with `Time` and
|
482
|
+
`DateTime` objects.
|
483
|
+
The special thing about them is that they take a *mandatory* `:format` option. There, you can specify the
|
484
|
+
format in which the duration you want to parse is available. The format is given in the form of a duration
|
485
|
+
format string with an easy syntax. Basically, you emulate the format of the given duration string and
|
486
|
+
replace the numbers with instructions how to treat them. The syntax is `"{#{unit}#{parse_length}}"`.
|
487
|
+
The `unit` can be one of `D`, `H`, `M`, or `S` (or their lowercase variants), representing days, hours, minutes, and seconds.
|
488
|
+
The `parse_length` tells the parser how many digits to look for, and can be any integer.
|
489
|
+
|
490
|
+
For example, let's say you want to parse a duration string that looks like `'1234'`, where the first two
|
491
|
+
digits stand for minutes and the last two for seconds. To parse this correctly, use the format string
|
492
|
+
`'{M2}{S2}'`. Easy enough.
|
493
|
+
|
494
|
+
What, though, if the number of digits vary? Maybe your duration string sometimes looks like `'12m34'`,
|
495
|
+
but when the numbers are single digit, it looks like `'2m1'`. In this case, just don't specify a
|
496
|
+
`parse_length`. Everything up to the following character (or the end of the duration string) will be
|
497
|
+
treated as going into the parsed value. The format string that would parse you the correct duration
|
498
|
+
would be `'{M}m{S}'`.
|
data/Rakefile
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A collection of class methods that will be available on classes that include `AwesomeXML`.
|
4
|
+
module AwesomeXML
|
5
|
+
module ClassMethods
|
6
|
+
attr_reader :context, :nodes, :public_nodes
|
7
|
+
private :nodes, :public_nodes
|
8
|
+
|
9
|
+
# Takes in a string representing an XML document. Initializes an instance of the class
|
10
|
+
# the module was included in and calls `#parse` on it. See there for more info.
|
11
|
+
def parse(xml)
|
12
|
+
new(xml).parse
|
13
|
+
end
|
14
|
+
|
15
|
+
# Takes in a string representing an XPath and assigns it to the class variable `@@context`.
|
16
|
+
# This sets the current context node for all nodes defined below it in the class this
|
17
|
+
# module is included in.
|
18
|
+
def set_context(xpath)
|
19
|
+
@context ||= xpath
|
20
|
+
end
|
21
|
+
|
22
|
+
# Works just like `set_context`, but sets the current context node only for nodes defined
|
23
|
+
# inside the block passed to this method.
|
24
|
+
def with_context(xpath, &block)
|
25
|
+
@local_context = xpath
|
26
|
+
yield
|
27
|
+
@local_context = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Defines a method on your class returning a constant.
|
31
|
+
def constant_node(name, value, options = {})
|
32
|
+
attr_reader name.to_sym
|
33
|
+
define_method("parse_#{name}".to_sym) do
|
34
|
+
instance_variable_set("@#{name}", value)
|
35
|
+
end
|
36
|
+
register(name, options[:private])
|
37
|
+
end
|
38
|
+
|
39
|
+
# Does not actually define a method, but registers the node name
|
40
|
+
# in the `@nodes` attribute.
|
41
|
+
def method_node(name)
|
42
|
+
define_method("parse_#{name}".to_sym) {}
|
43
|
+
register(name, false)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Defines a method on your class returning a parsed value
|
47
|
+
def node(name, type, options = {}, &block)
|
48
|
+
attr_reader name.to_sym
|
49
|
+
options[:local_context] = @local_context
|
50
|
+
xpath = NodeXPath.new(name, options).xpath
|
51
|
+
define_method("parse_#{name}".to_sym) do
|
52
|
+
evaluate_args = [xpath, AwesomeXML::Type.for(type, self.class.name), options]
|
53
|
+
instance_variable_set(
|
54
|
+
"@#{name}",
|
55
|
+
evaluate_nodes(*evaluate_args, &block)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
register(name, options[:private])
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns an array of symbols containing all method names defined by node builder methods
|
62
|
+
# in your class.
|
63
|
+
def nodes
|
64
|
+
@nodes ||= []
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns an array of symbols containing all method names defined by node builder methods
|
68
|
+
# in your class. Does not list nodes built with option `:private`.
|
69
|
+
def public_nodes
|
70
|
+
@public_nodes ||= []
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def register(node_name, privateness)
|
76
|
+
@nodes ||= []
|
77
|
+
@nodes << node_name.to_sym
|
78
|
+
@public_nodes ||= []
|
79
|
+
@public_nodes << node_name.to_sym unless privateness
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A class that lets you parse a string according to the rules of a
|
4
|
+
# specified duration format chunk.
|
5
|
+
module AwesomeXML
|
6
|
+
class Duration
|
7
|
+
class ChunkParser
|
8
|
+
attr_reader :duration_string_chunk, :format_chunk, :duration
|
9
|
+
private :duration_string_chunk, :format_chunk
|
10
|
+
|
11
|
+
# Parses a string given as `duration_string_chunk` according to the rules of the passed in
|
12
|
+
# `format_chunk`. The latter being either a `AwesomeXML::Duration::Format::StaticChunk`
|
13
|
+
# or a `AwesomeXML::Duration::Format::DynamicChunk`. Saves the resulting duration
|
14
|
+
# in the attribute `duration`.
|
15
|
+
def initialize(duration_string_chunk, format_chunk)
|
16
|
+
@duration_string_chunk = duration_string_chunk
|
17
|
+
@format_chunk = format_chunk
|
18
|
+
parse
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def parse
|
24
|
+
if format_chunk.dynamic?
|
25
|
+
@duration = number.public_send(format_chunk.unit)
|
26
|
+
else
|
27
|
+
fail format_mismatch unless duration_string_chunk == format_chunk.to_s
|
28
|
+
@duration = 0.seconds
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def number
|
33
|
+
fail format_mismatch unless valid_number?
|
34
|
+
duration_string_chunk.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid_number?
|
38
|
+
duration_string_chunk =~ /^[0-9]*$/
|
39
|
+
end
|
40
|
+
|
41
|
+
def format_mismatch
|
42
|
+
FormatMismatch.new(duration_string_chunk, format_chunk.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
class FormatMismatch < StandardError
|
46
|
+
def initialize(timestamp, format_string)
|
47
|
+
super("Duration string '#{timestamp}' does not conform to given format '#{format_string}'.")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A class that holds an array of characters and represents a dynamic
|
4
|
+
# component of a format string. Has a `unit`, a `parse_length` or a `delimiter`.
|
5
|
+
# See `AwesomeXML::Duration::Format` for more info.
|
6
|
+
module AwesomeXML
|
7
|
+
class Duration
|
8
|
+
class Format
|
9
|
+
class DynamicChunk
|
10
|
+
UNITS = { 'D' => :days, 'H' => :hours, 'M' => :minutes, 'S' => :seconds}
|
11
|
+
|
12
|
+
attr_accessor :format_chars, :delimiter
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@format_chars = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the defining characters joint into a string.
|
19
|
+
def to_s
|
20
|
+
[format_chars, delimiter].join
|
21
|
+
end
|
22
|
+
|
23
|
+
# Counterpart of the same method of `AwesomeXML::Duration::Format::DynamicChunk`.
|
24
|
+
# Used to differentiate between instances of these two classes.
|
25
|
+
def dynamic?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Takes the first character of `format_chars` and interprets as a duration unit.
|
30
|
+
def unit
|
31
|
+
fail InvalidDurationUnit.new(parsed_unit) unless valid_unit?
|
32
|
+
@unit ||= UNITS[parsed_unit]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Takes the characters following the first character of `format_chars` and interprets
|
36
|
+
# them as an integer representing the number of characters to parse when given to the
|
37
|
+
# `AweseomXML::Duration::ChunkParser` together with a piece of duration string.
|
38
|
+
# When the `format_chars` only contain a single character, this will be 0.
|
39
|
+
def parse_length
|
40
|
+
fail InvalidParseLength.new(parsed_parse_length) unless valid_parse_length?
|
41
|
+
@parse_length ||= parsed_parse_length.to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def valid_unit?
|
47
|
+
%w(D H M S).include?(parsed_unit)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parsed_unit
|
51
|
+
format_chars[0].upcase
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid_parse_length?
|
55
|
+
parsed_parse_length =~ /^[0-9]*$/ || parsed_parse_length.nil?
|
56
|
+
end
|
57
|
+
|
58
|
+
def parsed_parse_length
|
59
|
+
@parsed_parse_length ||= format_chars.drop(1).join
|
60
|
+
end
|
61
|
+
|
62
|
+
class InvalidDurationUnit < StandardError
|
63
|
+
def initialize(parsed_unit)
|
64
|
+
super("Parsed unknown duration unit: '#{parsed_unit}'. Please choose from [D, H, M, S].")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class InvalidParseLength < StandardError
|
69
|
+
def initialize(parsed_parse_length)
|
70
|
+
super("Couldn't parse '#{parsed_parse_length}' into an integer.")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A class that holds an array of characters and represents a static
|
4
|
+
# component of a format string. See `AwesomeXML::Duration::Format` for more info.
|
5
|
+
module AwesomeXML
|
6
|
+
class Duration
|
7
|
+
class Format
|
8
|
+
class StaticChunk
|
9
|
+
attr_accessor :format_chars
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@format_chars = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the defining characters joint into a string.
|
16
|
+
def to_s
|
17
|
+
format_chars.join
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the number of the defining characters.
|
21
|
+
def parse_length
|
22
|
+
format_chars.length
|
23
|
+
end
|
24
|
+
|
25
|
+
# Counterpart of the same method of `AwesomeXML::Duration::Format::DynamicChunk`.
|
26
|
+
# Used to differentiate between instances of these two classes.
|
27
|
+
def dynamic?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A representation of a user defined duration format.
|
4
|
+
module AwesomeXML
|
5
|
+
class Duration
|
6
|
+
class Format
|
7
|
+
attr_reader :format_string, :chunks
|
8
|
+
private :format_string
|
9
|
+
|
10
|
+
# Returns an `AwesomeXML::Duration::Format` instance representing a user defined
|
11
|
+
# duration format specified by the passed in `format_string`. Splits the format
|
12
|
+
# string into chunks that are each one of either `AwesomeXML::Duration::Format::StaticChunk`
|
13
|
+
# or `AwesomeXML::Duration::Format::DynamicChunk` and saves them in the attribute `chunks`.
|
14
|
+
# The latter class mentioned is used for a section of the format string inside curly
|
15
|
+
# brackets. For more information about the syntax of the format string, read the README.
|
16
|
+
def initialize(format_string)
|
17
|
+
@format_string = format_string
|
18
|
+
@chunks = []
|
19
|
+
compile
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def compile
|
25
|
+
format_string.chars.each(&method(:process))
|
26
|
+
@chunks = chunks&.compact
|
27
|
+
end
|
28
|
+
|
29
|
+
def process(character)
|
30
|
+
case character
|
31
|
+
when '{'
|
32
|
+
chunks.append(DynamicChunk.new)
|
33
|
+
when '}'
|
34
|
+
chunks.append(nil)
|
35
|
+
else
|
36
|
+
if chunks.last.nil?
|
37
|
+
if chunks[-2].is_a?(DynamicChunk)
|
38
|
+
chunks[-2].delimiter = character
|
39
|
+
end
|
40
|
+
chunks.tap(&:pop).append(StaticChunk.new)
|
41
|
+
end
|
42
|
+
|
43
|
+
chunks.last.format_chars.append(character)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module is shared by all native type classes of `AwesomeXML`. It defines the
|
4
|
+
# interface for types that the `AwesomeXML::NodeEvaluator` expects.
|
5
|
+
module AwesomeXML
|
6
|
+
module NativeType
|
7
|
+
attr_reader :string, :options
|
8
|
+
private :string, :options
|
9
|
+
|
10
|
+
# Native type instances are initialized with a `Nokogiri::XML` object and an options hash.
|
11
|
+
def initialize(node, options = {})
|
12
|
+
@string = node&.text
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
# This method returns the parsed value of the given node (obtained by calling `#text` on it) according
|
17
|
+
# to the implementation of the private method `#parse_value` defined in every native type class.
|
18
|
+
def evaluate
|
19
|
+
@value ||= with_defaults { parse_value }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def with_defaults(&block)
|
25
|
+
return options[:default] if string.nil?
|
26
|
+
return options[:default_empty] if options.has_key?(:default_empty) && string.empty?
|
27
|
+
return default_empty if string.empty?
|
28
|
+
yield
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_empty
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class is responsible for parsing a specific value from an XML document given all the
|
4
|
+
# necessary information.
|
5
|
+
module AwesomeXML
|
6
|
+
class NodeEvaluator
|
7
|
+
attr_reader :xml, :xpath, :type_class, :options
|
8
|
+
private :xml, :xpath, :type_class, :options
|
9
|
+
|
10
|
+
# Initialize an instance of this class with a Nokogiri::XML object, a string representing
|
11
|
+
# an XPath to the value(s) you want to parse, a type class (see `AwesomeXML::Type` for more
|
12
|
+
# info), and an options hash.
|
13
|
+
def initialize(xml, xpath, type_class, options)
|
14
|
+
@xml = xml
|
15
|
+
@xpath = xpath
|
16
|
+
@type_class = type_class
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
# Parses one or several nodes, depending on the `options[:array]` setting, according to the
|
21
|
+
# type passed in in the form of a class that handles the conversion.
|
22
|
+
def call
|
23
|
+
if options[:array]
|
24
|
+
all_nodes.map { |node| type_class.new(node, options).evaluate }
|
25
|
+
else
|
26
|
+
type_class.new(first_node, options).evaluate
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def all_nodes
|
33
|
+
xml_in_context&.xpath(xpath)
|
34
|
+
end
|
35
|
+
|
36
|
+
def first_node
|
37
|
+
xml_in_context&.at_xpath(xpath)
|
38
|
+
end
|
39
|
+
|
40
|
+
def xml_in_context
|
41
|
+
options[:local_context] ? xml&.xpath(options[:local_context]) : xml
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class's responsibility is to build an XPath from specified options.
|
4
|
+
module AwesomeXML
|
5
|
+
class NodeXPath
|
6
|
+
attr_reader :node_name, :specific_xpath, :element_option, :attribute_option, :self_option, :array
|
7
|
+
private :node_name, :specific_xpath, :element_option, :attribute_option, :self_option, :array
|
8
|
+
|
9
|
+
# Initialize this class by providing the name of the `AwesomeXML` node and an options hash.
|
10
|
+
# For more information on how the options work, please refer to the README.
|
11
|
+
def initialize(node_name, options)
|
12
|
+
@node_name = node_name
|
13
|
+
@specific_xpath = options[:xpath]
|
14
|
+
@element_option = options[:element]
|
15
|
+
@attribute_option = options[:attribute]
|
16
|
+
@self_option = options[:self]
|
17
|
+
@look_for = options[:look_for]
|
18
|
+
@array = options[:array]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns a String representing an XPath built from the options passed in at initialization time.
|
22
|
+
def xpath
|
23
|
+
specific_xpath || xpath_by_tag_type
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def xpath_by_tag_type
|
29
|
+
if attribute_option
|
30
|
+
"./@#{tag_name(attribute_option)}"
|
31
|
+
elsif self_option
|
32
|
+
"."
|
33
|
+
else
|
34
|
+
"./#{tag_name(element_option)}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def node_name_singular
|
39
|
+
array ? node_name.to_s.singularize.to_sym : node_name
|
40
|
+
end
|
41
|
+
|
42
|
+
def tag_name(option)
|
43
|
+
(option if option != true) || node_name_singular
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class converts types passed in as arguments to `.node` method calls to a
|
4
|
+
# type class. Either native or user-defined.
|
5
|
+
module AwesomeXML
|
6
|
+
module Type
|
7
|
+
NATIVE_TYPE_CLASSES = {
|
8
|
+
text: AwesomeXML::Text,
|
9
|
+
integer: AwesomeXML::Integer,
|
10
|
+
float: AwesomeXML::Float,
|
11
|
+
duration: AwesomeXML::Duration,
|
12
|
+
date_time: AwesomeXML::DateTime
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
# Takes a type (Symbol, String or Class) passed in from a `.node` method call and the
|
16
|
+
# name of the class it was called in. The latter is needed to correctly assign the namespace
|
17
|
+
# if the type is given in String form. Returns a class, either one of the native `AwesomeXML`
|
18
|
+
# types or a user-defined class. Raises an exception if `type` is given as a Symbol, but
|
19
|
+
# does not represent one of the native types.
|
20
|
+
def self.for(type, class_name)
|
21
|
+
case type
|
22
|
+
when Symbol
|
23
|
+
NATIVE_TYPE_CLASSES[type] || fail(UnknownNodeType.new(type))
|
24
|
+
when String
|
25
|
+
[class_name, type].join('::').constantize
|
26
|
+
when Class
|
27
|
+
type
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# An excception of this type is raised if `type` is given as a Symbol to `.for`, but does not
|
32
|
+
# represent one of the native `AwesomeXML` types.
|
33
|
+
class UnknownNodeType < StandardError
|
34
|
+
def initialize(type)
|
35
|
+
super("Cannot create node with unknown node type '#{type}'.")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A class that knows how to parse a point in time given a timestamp and a format string.
|
4
|
+
module AwesomeXML
|
5
|
+
class DateTime
|
6
|
+
include AwesomeXML::NativeType
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def parse_value
|
11
|
+
fail NoFormatProvided if options[:format].nil?
|
12
|
+
::DateTime.strptime(string, options[:format])
|
13
|
+
end
|
14
|
+
|
15
|
+
class NoFormatProvided < StandardError
|
16
|
+
def initialize
|
17
|
+
super('Please provide a format option to date_time nodes.')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A class that knows how to parse a duration given a duration string and a format string.
|
4
|
+
module AwesomeXML
|
5
|
+
class Duration
|
6
|
+
include AwesomeXML::NativeType
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def parse_value
|
11
|
+
fail NoFormatProvided if options[:format].nil?
|
12
|
+
string_chunks.zip(format_chunks).map do |string_chunk, format_chunk|
|
13
|
+
AwesomeXML::Duration::ChunkParser.new(string_chunk, format_chunk).duration
|
14
|
+
end.reduce(:+) || 0.seconds
|
15
|
+
end
|
16
|
+
|
17
|
+
def string_chunks
|
18
|
+
@string_chunks ||= chunk_string
|
19
|
+
end
|
20
|
+
|
21
|
+
def chunk_string
|
22
|
+
result = []
|
23
|
+
format_chunks.reduce(string.chars) do |chopped_string, format_chunk|
|
24
|
+
if format_chunk.parse_length.zero?
|
25
|
+
parse_length = chopped_string.find_index(format_chunk.delimiter) || chopped_string.length
|
26
|
+
else
|
27
|
+
parse_length = format_chunk.parse_length
|
28
|
+
end
|
29
|
+
result.append(chopped_string.first(parse_length).join)
|
30
|
+
chopped_string.drop(parse_length)
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def format_chunks
|
36
|
+
@format_chunks ||= AwesomeXML::Duration::Format.new(options[:format]).chunks
|
37
|
+
end
|
38
|
+
|
39
|
+
def split_at_character(string, character)
|
40
|
+
return string unless string.chars.include?(character)
|
41
|
+
split_after(string, string.chars.find_index(character) - 1)
|
42
|
+
end
|
43
|
+
|
44
|
+
def split_after(string, after_position)
|
45
|
+
[string.chars.first(after_position), string.chars.drop(after_position)].map(&:join)
|
46
|
+
end
|
47
|
+
|
48
|
+
class NoFormatProvided < StandardError
|
49
|
+
def initialize
|
50
|
+
super('Please provide a format option to duration nodes.')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A class that knows how to parse a String from a String. Shockingly, it doesn't do much.
|
4
|
+
module AwesomeXML
|
5
|
+
class Text
|
6
|
+
include AwesomeXML::NativeType
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def parse_value
|
11
|
+
string
|
12
|
+
end
|
13
|
+
|
14
|
+
def default_empty
|
15
|
+
''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: awesome_xml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Felix Lublasser
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-06-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.41'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.41'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: nokogiri
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.3'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.3'
|
97
|
+
description: Have XML data that you want to bend to your willand conform to your schema?
|
98
|
+
This gem is for you.
|
99
|
+
email:
|
100
|
+
- felix.lublasser@fromatob.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- lib/awesome_xml/class_methods.rb
|
110
|
+
- lib/awesome_xml/duration/chunk_parser.rb
|
111
|
+
- lib/awesome_xml/duration/format.rb
|
112
|
+
- lib/awesome_xml/duration/format/dynamic_chunk.rb
|
113
|
+
- lib/awesome_xml/duration/format/static_chunk.rb
|
114
|
+
- lib/awesome_xml/native_type.rb
|
115
|
+
- lib/awesome_xml/node_evaluator.rb
|
116
|
+
- lib/awesome_xml/node_xpath.rb
|
117
|
+
- lib/awesome_xml/type.rb
|
118
|
+
- lib/awesome_xml/types/date_time.rb
|
119
|
+
- lib/awesome_xml/types/duration.rb
|
120
|
+
- lib/awesome_xml/types/float.rb
|
121
|
+
- lib/awesome_xml/types/integer.rb
|
122
|
+
- lib/awesome_xml/types/text.rb
|
123
|
+
homepage: https://github.com/fromAtoB/awesome_xml
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 2.5.2
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: Parse data from XML documents into arbitrary ruby hashes.
|
147
|
+
test_files: []
|