xml-mapping_extensions 0.4.3 → 0.4.4
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 +4 -4
- data/CHANGES.md +4 -0
- data/README.md +61 -1
- data/lib/xml/mapping_extensions/version.rb +1 -1
- data/lib/xml/mapping_extensions.rb +59 -3
- data/spec/unit/xml/mapping_extensions/fallback_mapping_spec.rb +97 -0
- data/spec/unit/xml/mapping_extensions/mapping_extensions_spec.rb +55 -43
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36f81c099c7ffafcad6ffa1af533b3a5f9ced684
|
4
|
+
data.tar.gz: 09a3a094c1855d14b2e3f89d72ba210bc0a7ede1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b94a791a3a778e2e457e177a04c424011b012a422532aebfc9257a97ec08a3866b991a87b13df49f6ef8d65960e22fee4149426897437c5f74131e5b9ef8a14e
|
7
|
+
data.tar.gz: 247e5a8e25895cdbad0f3bddf62aafdde2dae6f89e77770e86fa22bd05d89741b0d36b342779851e2812cc55c7247e8d424639ce6c8b028fae5dac92e7e9a3b7
|
data/CHANGES.md
CHANGED
data/README.md
CHANGED
@@ -19,7 +19,7 @@ Additional mapping nodes and other utility code for working with
|
|
19
19
|
- [Reading](#reading)
|
20
20
|
- [Writing](#writing)
|
21
21
|
- [Namespaces](#namespaces)
|
22
|
-
|
22
|
+
- [Fallback mappings](#fallback-mappings)
|
23
23
|
|
24
24
|
## Extension methods
|
25
25
|
|
@@ -264,3 +264,63 @@ puts obj.write_xml
|
|
264
264
|
<px:child>child 2</px:child>
|
265
265
|
</px:element>
|
266
266
|
```
|
267
|
+
|
268
|
+
## Fallback mappings
|
269
|
+
|
270
|
+
The [XML::Mapping](http://multi-io.github.io/xml-mapping/) library provides an “alternate mapping”
|
271
|
+
mechanism allowing
|
272
|
+
[multiple XML mappings per class](http://multi-io.github.io/xml-mapping/#label-Multiple+Mappings+per+Class).
|
273
|
+
However, it requires each mapping to be exhaustive -- an alternate mapping must redefine all attribute
|
274
|
+
mappings defined in the primary, or else ignore those attributes. Sometimes, however, it is
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
class ValidatingElement
|
278
|
+
include ::XML::Mapping
|
279
|
+
|
280
|
+
root_element_name 'element'
|
281
|
+
text_node :name, '@name'
|
282
|
+
text_node :value, '@value'
|
283
|
+
|
284
|
+
use_mapping :strict
|
285
|
+
numeric_node :value, '@value', writer: proc { |obj, xml| xml.add_attribute('value', Float(obj.value)) }
|
286
|
+
end
|
287
|
+
|
288
|
+
invalid_string = '<element name="abc" value="efg"/>'
|
289
|
+
ValidatingElement.parse_xml(invalid_string, mapping: :strict)
|
290
|
+
# ArgumentError: invalid value for Float(): "efg"
|
291
|
+
|
292
|
+
elem = ValidatingElement.parse_xml(invalid_string) # OK
|
293
|
+
# => #<XML::Mapping::ValidatingElement:0x007fa5631ae9c8>
|
294
|
+
elem.write_xml
|
295
|
+
# => "<element name='abc' value='efg'/>"
|
296
|
+
|
297
|
+
elem.write_xml(mapping: :strict)
|
298
|
+
# ArgumentError: invalid value for Float(): "efg"
|
299
|
+
```
|
300
|
+
|
301
|
+
So far, so good; but say we set a valid value and try to output with the
|
302
|
+
`:strict` mapping?
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
elem.value = 123
|
306
|
+
elem.write_xml(mapping: :strict)
|
307
|
+
# => <validating-element value='123'/>
|
308
|
+
```
|
309
|
+
|
310
|
+
Since the `:strict` mapping doesn't define a root element name, we get the
|
311
|
+
default (based on the class name), and since it doesn't define a mapping
|
312
|
+
for the `name` attribute, we lose that entirely.
|
313
|
+
|
314
|
+
But if we add a fallback mapping --
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
class ValidatingElement
|
318
|
+
fallback_mapping :strict, :_default
|
319
|
+
end
|
320
|
+
|
321
|
+
elem.write_xml(mapping: :strict)
|
322
|
+
# => <element value='123' name='abc'/>
|
323
|
+
```
|
324
|
+
|
325
|
+
-- the `:strict` mapping now gets the root element name `element`,
|
326
|
+
and the `name` attribute, as defined under the `:_default` mapping.
|
@@ -48,7 +48,18 @@ module XML
|
|
48
48
|
io.string
|
49
49
|
end
|
50
50
|
|
51
|
+
# Additional accessors needed for `#fallback_mapping`.
|
52
|
+
class Node
|
53
|
+
attr_reader :attrname
|
54
|
+
attr_accessor :mapping
|
55
|
+
end
|
56
|
+
|
51
57
|
module ClassMethods
|
58
|
+
|
59
|
+
# Gets the configured root element names for this object.
|
60
|
+
# @return [Hash[Symbol, String]] the root element names for this object, by mapping.
|
61
|
+
attr_reader :root_element_names
|
62
|
+
|
52
63
|
# Create a new instance of this class from the XML contained in
|
53
64
|
# `xml`, which can be a string, REXML document, or REXML element
|
54
65
|
# @param xml [String, REXML::Document, REXML::Element]
|
@@ -66,6 +77,26 @@ module XML
|
|
66
77
|
load_from_xml(element, options)
|
67
78
|
end
|
68
79
|
|
80
|
+
# Configures one mapping as a fallback for another, allowing mappings
|
81
|
+
# that differ e.g. from the default only in some small detail. Creates
|
82
|
+
# mapping nodes based on the fallback for all attributes for which a
|
83
|
+
# mapping node is present in the fallback but not in the primary, and
|
84
|
+
# likewise sets the root element name for the primary to the root element
|
85
|
+
# name for the fallback if it has not already been set.
|
86
|
+
#
|
87
|
+
# @param mapping [Symbol] the primary mapping
|
88
|
+
# @param fallback [Symbol] the fallback mapping to be used if the primary
|
89
|
+
# mapping lacks a mapping node or root element name
|
90
|
+
def fallback_mapping(mapping, fallback)
|
91
|
+
mapping_nodes = nodes_by_attrname(mapping)
|
92
|
+
add_fallback_nodes(mapping_nodes, mapping, fallback)
|
93
|
+
xml_mapping_nodes_hash[mapping] = mapping_nodes.values
|
94
|
+
|
95
|
+
return if root_element_names[mapping]
|
96
|
+
fallback_name = root_element_names[fallback]
|
97
|
+
root_element_name(fallback_name, mapping: mapping)
|
98
|
+
end
|
99
|
+
|
69
100
|
private
|
70
101
|
|
71
102
|
# Whether the argument can be parsed as an `REXML::Document`, i.e. whether
|
@@ -76,9 +107,34 @@ module XML
|
|
76
107
|
def can_parse(arg)
|
77
108
|
arg.is_a?(String) ||
|
78
109
|
(arg.respond_to?(:read) &&
|
79
|
-
|
80
|
-
|
81
|
-
|
110
|
+
arg.respond_to?(:readline) &&
|
111
|
+
arg.respond_to?(:nil?) &&
|
112
|
+
arg.respond_to?(:eof?))
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns the nodes for the specified mapping as a hash accessible by
|
116
|
+
# their attribute names.
|
117
|
+
# @param mapping [Symbol] the mapping
|
118
|
+
# @return [Hash[Symbol, Node]] the nodes
|
119
|
+
def nodes_by_attrname(mapping)
|
120
|
+
nodes = xml_mapping_nodes(mapping: mapping)
|
121
|
+
nodes.map { |node| [node.attrname, node] }.to_h
|
122
|
+
end
|
123
|
+
|
124
|
+
# Creates mapping nodes based on the fallback for all attributes for which a
|
125
|
+
# mapping node is present in the fallback but not in the primary.
|
126
|
+
# @param mapping_nodes [Hash[Symbol, Node]] The primary nodes
|
127
|
+
# @param mapping [Symbol] The primary mapping
|
128
|
+
# @param fallback [Symbol] The fallback mapping
|
129
|
+
def add_fallback_nodes(mapping_nodes, mapping, fallback)
|
130
|
+
xml_mapping_nodes(mapping: fallback).each do |fallback_node|
|
131
|
+
attrname = fallback_node.attrname
|
132
|
+
next if mapping_nodes.key?(attrname)
|
133
|
+
|
134
|
+
node = fallback_node.clone
|
135
|
+
fallback_node.mapping = mapping
|
136
|
+
mapping_nodes[attrname] = node
|
137
|
+
end
|
82
138
|
end
|
83
139
|
end
|
84
140
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module XML
|
4
|
+
module Mapping
|
5
|
+
class FMSpecObject
|
6
|
+
include ::XML::Mapping
|
7
|
+
|
8
|
+
root_element_name 'element'
|
9
|
+
|
10
|
+
numeric_node :attribute, '@attribute'
|
11
|
+
text_node :text, 'text()'
|
12
|
+
array_node :children, 'child', class: String
|
13
|
+
|
14
|
+
use_mapping :alternate
|
15
|
+
text_node :attribute, '@attribute'
|
16
|
+
array_node :children, 'children',
|
17
|
+
reader: (proc do |obj, xml|
|
18
|
+
children_elem = xml.elements['children']
|
19
|
+
obj.children = children_elem ? children_elem.text.split(',') : []
|
20
|
+
end),
|
21
|
+
writer: (proc do |obj, xml|
|
22
|
+
children_text = obj.children.join(',') if obj.children && !obj.children.empty?
|
23
|
+
xml.add_element('children').text = children_text if children_text
|
24
|
+
end)
|
25
|
+
|
26
|
+
fallback_mapping :alternate, :_default
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#save_to_xml' do
|
30
|
+
it 'writes an XML string' do
|
31
|
+
obj = FMSpecObject.new
|
32
|
+
obj.attribute = 123
|
33
|
+
obj.text = 'element text'
|
34
|
+
obj.children = ['child 1', 'child 2']
|
35
|
+
saved_xml = obj.save_to_xml
|
36
|
+
expected_xml = '<element attribute="123">element text<child>child 1</child><child>child 2</child></element>'
|
37
|
+
expect(saved_xml).to be_xml(expected_xml)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'accepts an alternate mapping' do
|
41
|
+
obj = FMSpecObject.new
|
42
|
+
obj.attribute = 'elvis'
|
43
|
+
obj.text = 'element text'
|
44
|
+
obj.children = ['child 1', 'child 2']
|
45
|
+
saved_xml = obj.save_to_xml(mapping: :alternate)
|
46
|
+
expected_xml = '<element attribute="elvis">element text<children>child 1,child 2</children></element>'
|
47
|
+
expect(saved_xml).to be_xml(expected_xml)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#parse_xml' do
|
52
|
+
it 'parses a String' do
|
53
|
+
xml_string = '<element attribute="123">element text<child>child 1</child><child>child 2</child></element>'
|
54
|
+
obj = FMSpecObject.parse_xml(xml_string)
|
55
|
+
expect(obj.attribute).to eq(123)
|
56
|
+
expect(obj.text).to eq('element text')
|
57
|
+
expect(obj.children).to eq(['child 1', 'child 2'])
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'accepts an alternate mapping' do
|
61
|
+
xml_string = '<element attribute="elvis">element text<children>child 1,child 2</children></element>'
|
62
|
+
obj = FMSpecObject.parse_xml(xml_string, mapping: :alternate)
|
63
|
+
expect(obj.attribute).to eq('elvis')
|
64
|
+
expect(obj.text).to eq('element text')
|
65
|
+
expect(obj.children).to eq(['child 1', 'child 2'])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class ValidatingElement
|
70
|
+
include ::XML::Mapping
|
71
|
+
|
72
|
+
root_element_name 'element'
|
73
|
+
text_node :name, '@name'
|
74
|
+
text_node :value, '@value'
|
75
|
+
|
76
|
+
use_mapping :strict
|
77
|
+
numeric_node :value, '@value', writer: proc { |obj, xml| xml.add_attribute('value', Float(obj.value)) }
|
78
|
+
|
79
|
+
fallback_mapping :strict, :_default
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#fallback_mapping' do
|
83
|
+
it 'round-trips' do
|
84
|
+
invalid_string = '<element name="abc" value="efg"/>'
|
85
|
+
expect { ValidatingElement.parse_xml(invalid_string, mapping: :strict) }.to raise_error(ArgumentError)
|
86
|
+
|
87
|
+
elem = ValidatingElement.parse_xml(invalid_string) # OK
|
88
|
+
expect(elem.write_xml).to be_xml("<element name='abc' value='efg'/>")
|
89
|
+
|
90
|
+
expect { elem.write_xml(mapping: :strict) }.to raise_error(ArgumentError)
|
91
|
+
|
92
|
+
elem.value = 123.4
|
93
|
+
expect(elem.write_xml(mapping: :strict)).to be_xml("<element value='123.4' name='abc'/>")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -14,7 +14,15 @@ module XML
|
|
14
14
|
array_node :children, 'child', class: String
|
15
15
|
|
16
16
|
use_mapping :alternate
|
17
|
-
|
17
|
+
array_node :children, 'children',
|
18
|
+
reader: (proc do |obj, xml|
|
19
|
+
children_elem = xml.elements['children']
|
20
|
+
obj.children = children_elem ? children_elem.text.split(',') : []
|
21
|
+
end),
|
22
|
+
writer: (proc do |obj, xml|
|
23
|
+
children_text = obj.children.join(',') if obj.children && !obj.children.empty?
|
24
|
+
xml.add_element('children').text = children_text if children_text
|
25
|
+
end)
|
18
26
|
|
19
27
|
def <=>(other)
|
20
28
|
return nil unless self.class == other.class
|
@@ -45,67 +53,71 @@ module XML
|
|
45
53
|
it 'accepts an alternate mapping' do
|
46
54
|
obj = MXSpecObject.new
|
47
55
|
obj.attribute = 123
|
48
|
-
obj.text_alt = 'element text'
|
49
56
|
obj.children = ['child 1', 'child 2']
|
50
|
-
|
57
|
+
saved_xml = obj.save_to_xml(mapping: :alternate)
|
51
58
|
xml_string = obj.write_xml(mapping: :alternate)
|
52
59
|
expect(xml_string).to be_a(String)
|
53
|
-
expect(xml_string).to be_xml(
|
60
|
+
expect(xml_string).to be_xml(saved_xml)
|
54
61
|
end
|
55
62
|
end
|
56
63
|
|
57
|
-
|
58
|
-
describe '#parse_xml' do
|
64
|
+
describe '#parse_xml' do
|
59
65
|
|
60
|
-
|
61
|
-
|
66
|
+
before(:each) do
|
67
|
+
@xml_string = '<element attribute="123">
|
62
68
|
element text
|
63
69
|
<child>child 1</child>
|
64
70
|
<child>child 2</child>
|
65
71
|
</element>'
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
72
|
+
@xml_document = REXML::Document.new(@xml_string)
|
73
|
+
@xml_element = @xml_document.root
|
74
|
+
@expected_element = MXSpecObject.load_from_xml(@xml_element)
|
75
|
+
end
|
70
76
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
77
|
+
it 'parses a String' do
|
78
|
+
obj = MXSpecObject.parse_xml(@xml_string)
|
79
|
+
expect(obj).to eq(@expected_element)
|
80
|
+
end
|
75
81
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
82
|
+
it 'parses a REXML::Document' do
|
83
|
+
obj = MXSpecObject.parse_xml(@xml_document)
|
84
|
+
expect(obj).to eq(@expected_element)
|
85
|
+
end
|
80
86
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
87
|
+
it 'parses a REXML::Element' do
|
88
|
+
obj = MXSpecObject.parse_xml(@xml_element)
|
89
|
+
expect(obj).to eq(@expected_element)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'parses an IO' do
|
93
|
+
xml_io = StringIO.new(@xml_string)
|
94
|
+
obj = MXSpecObject.parse_xml(xml_io)
|
95
|
+
expect(obj).to eq(@expected_element)
|
96
|
+
end
|
85
97
|
|
86
|
-
|
87
|
-
|
88
|
-
|
98
|
+
it 'parses a file' do
|
99
|
+
xml_file = Tempfile.new(%w(parse_xml_spec.xml))
|
100
|
+
begin
|
101
|
+
xml_file.write(@xml_string)
|
102
|
+
xml_file.rewind
|
103
|
+
obj = MXSpecObject.parse_xml(xml_file)
|
89
104
|
expect(obj).to eq(@expected_element)
|
105
|
+
ensure
|
106
|
+
xml_file.close(true)
|
90
107
|
end
|
108
|
+
end
|
91
109
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
ensure
|
100
|
-
xml_file.close(true)
|
101
|
-
end
|
102
|
-
end
|
110
|
+
it 'accepts an alternate mapping' do
|
111
|
+
@xml_string = '<element attribute="123">
|
112
|
+
element text
|
113
|
+
<children>child 1,child 2</children>
|
114
|
+
</element>'
|
115
|
+
@xml_document = REXML::Document.new(@xml_string)
|
116
|
+
@xml_element = @xml_document.root
|
103
117
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
expect(obj).to eq(@expected_element)
|
108
|
-
end
|
118
|
+
@expected_element = MXSpecObject.load_from_xml(@xml_element, mapping: :alternate)
|
119
|
+
obj = MXSpecObject.parse_xml(@xml_element, mapping: :alternate)
|
120
|
+
expect(obj).to eq(@expected_element)
|
109
121
|
end
|
110
122
|
end
|
111
123
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xml-mapping_extensions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Moles
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07-
|
11
|
+
date: 2016-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mime-types
|
@@ -202,6 +202,7 @@ files:
|
|
202
202
|
- spec/spec_helper.rb
|
203
203
|
- spec/unit/xml/mapping_extensions/array_node_spec.rb
|
204
204
|
- spec/unit/xml/mapping_extensions/date_node_spec.rb
|
205
|
+
- spec/unit/xml/mapping_extensions/fallback_mapping_spec.rb
|
205
206
|
- spec/unit/xml/mapping_extensions/mapping_extensions_spec.rb
|
206
207
|
- spec/unit/xml/mapping_extensions/mime_type_node_spec.rb
|
207
208
|
- spec/unit/xml/mapping_extensions/namespace_spec.rb
|
@@ -241,6 +242,7 @@ test_files:
|
|
241
242
|
- spec/spec_helper.rb
|
242
243
|
- spec/unit/xml/mapping_extensions/array_node_spec.rb
|
243
244
|
- spec/unit/xml/mapping_extensions/date_node_spec.rb
|
245
|
+
- spec/unit/xml/mapping_extensions/fallback_mapping_spec.rb
|
244
246
|
- spec/unit/xml/mapping_extensions/mapping_extensions_spec.rb
|
245
247
|
- spec/unit/xml/mapping_extensions/mime_type_node_spec.rb
|
246
248
|
- spec/unit/xml/mapping_extensions/namespace_spec.rb
|