tilia-xml 1.2.0.2 → 1.3.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 +4 -4
- data/.simplecov +1 -2
- data/CHANGELOG.sabre.md +21 -0
- data/Gemfile +2 -7
- data/Gemfile.lock +22 -17
- data/LICENSE +1 -1
- data/LICENSE.sabre +1 -1
- data/lib/tilia/xml.rb +3 -0
- data/lib/tilia/xml/context_stack_trait.rb +27 -23
- data/lib/tilia/xml/deserializer.rb +241 -0
- data/lib/tilia/xml/element/base.rb +2 -43
- data/lib/tilia/xml/element/cdata.rb +1 -26
- data/lib/tilia/xml/element/elements.rb +4 -66
- data/lib/tilia/xml/element/key_value.rb +3 -65
- data/lib/tilia/xml/element/uri.rb +2 -45
- data/lib/tilia/xml/element/xml_fragment.rb +2 -42
- data/lib/tilia/xml/reader.rb +33 -28
- data/lib/tilia/xml/serializer.rb +194 -0
- data/lib/tilia/xml/service.rb +98 -14
- data/lib/tilia/xml/version.rb +1 -1
- data/lib/tilia/xml/writer.rb +42 -88
- data/lib/tilia/xml/xml_deserializable.rb +4 -4
- data/lib/tilia/xml/xml_serializable.rb +3 -3
- data/test/test_helper.rb +77 -0
- data/test/xml/context_stack_test.rb +0 -3
- data/test/xml/deserializer/enum_test.rb +55 -0
- data/test/xml/deserializer/key_value_test.rb +64 -0
- data/test/xml/deserializer/repeating_elements_test.rb +33 -0
- data/test/xml/deserializer/value_object_test.rb +163 -0
- data/test/xml/reader_test.rb +35 -16
- data/test/xml/serializer/enum_test.rb +36 -0
- data/test/xml/serializer/repeating_elements_test.rb +37 -0
- data/test/xml/service_test.rb +68 -0
- data/test/xml/writer_test.rb +120 -17
- data/tilia-xml.gemspec +4 -4
- metadata +23 -9
@@ -9,13 +9,6 @@ module Tilia
|
|
9
9
|
class Base
|
10
10
|
include Element
|
11
11
|
|
12
|
-
protected
|
13
|
-
|
14
|
-
# PHP value to serialize.
|
15
|
-
attr_accessor :value
|
16
|
-
|
17
|
-
public
|
18
|
-
|
19
12
|
# Constructor
|
20
13
|
#
|
21
14
|
# @param value
|
@@ -23,46 +16,12 @@ module Tilia
|
|
23
16
|
@value = value
|
24
17
|
end
|
25
18
|
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# Use the writer argument to write its own xml serialization.
|
29
|
-
#
|
30
|
-
# An important note: do _not_ create a parent element. Any element
|
31
|
-
# implementing XmlSerializable should only ever write what's considered
|
32
|
-
# its 'inner xml'.
|
33
|
-
#
|
34
|
-
# The parent of the current element is responsible for writing a
|
35
|
-
# containing element.
|
36
|
-
#
|
37
|
-
# This allows serializers to be re-used for different element names.
|
38
|
-
#
|
39
|
-
# If you are opening new elements, you must also close them again.
|
40
|
-
#
|
41
|
-
# @param [Writer] writer
|
42
|
-
# @return [void]
|
19
|
+
# (see XmlSerializable#xml_serialize)
|
43
20
|
def xml_serialize(writer)
|
44
21
|
writer.write(@value)
|
45
22
|
end
|
46
23
|
|
47
|
-
#
|
48
|
-
#
|
49
|
-
# This method is called statictly, this is because in theory this method
|
50
|
-
# may be used as a type of constructor, or factory method.
|
51
|
-
#
|
52
|
-
# Often you want to return an instance of the current class, but you are
|
53
|
-
# free to return other data as well.
|
54
|
-
#
|
55
|
-
# Important note 2: You are responsible for advancing the reader to the
|
56
|
-
# next element. Not doing anything will result in a never-ending loop.
|
57
|
-
#
|
58
|
-
# If you just want to skip parsing for this element altogether, you can
|
59
|
-
# just call reader->next();
|
60
|
-
#
|
61
|
-
# reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
62
|
-
# the next element.
|
63
|
-
#
|
64
|
-
# @param [Reader] reader
|
65
|
-
# @return mixed
|
24
|
+
# (see XmlDeserializable#xml_deserialize)
|
66
25
|
def self.xml_deserialize(reader)
|
67
26
|
sub_tree = reader.parse_inner_tree
|
68
27
|
sub_tree
|
@@ -11,15 +11,6 @@ module Tilia
|
|
11
11
|
class Cdata
|
12
12
|
include XmlSerializable
|
13
13
|
|
14
|
-
protected
|
15
|
-
|
16
|
-
# CDATA element value.
|
17
|
-
#
|
18
|
-
# @return [String]
|
19
|
-
attr_accessor :value
|
20
|
-
|
21
|
-
public
|
22
|
-
|
23
14
|
# Constructor
|
24
15
|
#
|
25
16
|
# @param [String] value
|
@@ -27,23 +18,7 @@ module Tilia
|
|
27
18
|
@value = value
|
28
19
|
end
|
29
20
|
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# Use the writer argument to write its own xml serialization.
|
33
|
-
#
|
34
|
-
# An important note: do _not_ create a parent element. Any element
|
35
|
-
# implementing XmlSerializble should only ever write what's considered
|
36
|
-
# its 'inner xml'.
|
37
|
-
#
|
38
|
-
# The parent of the current element is responsible for writing a
|
39
|
-
# containing element.
|
40
|
-
#
|
41
|
-
# This allows serializers to be re-used for different element names.
|
42
|
-
#
|
43
|
-
# If you are opening new elements, you must also close them again.
|
44
|
-
#
|
45
|
-
# @param [Writer] writer
|
46
|
-
# @return [void]
|
21
|
+
# (see XmlSerializable#xml_serialize)
|
47
22
|
def xml_serialize(writer)
|
48
23
|
writer.write_cdata(@value)
|
49
24
|
end
|
@@ -25,15 +25,6 @@ module Tilia
|
|
25
25
|
class Elements
|
26
26
|
include Element
|
27
27
|
|
28
|
-
protected
|
29
|
-
|
30
|
-
# Value to serialize
|
31
|
-
#
|
32
|
-
# @return [Array]
|
33
|
-
attr_accessor :value
|
34
|
-
|
35
|
-
public
|
36
|
-
|
37
28
|
# Constructor
|
38
29
|
#
|
39
30
|
# @param [Array] value
|
@@ -41,67 +32,14 @@ module Tilia
|
|
41
32
|
@value = value
|
42
33
|
end
|
43
34
|
|
44
|
-
#
|
45
|
-
#
|
46
|
-
# Use the writer argument to write its own xml serialization.
|
47
|
-
#
|
48
|
-
# An important note: do _not_ create a parent element. Any element
|
49
|
-
# implementing XmlSerializble should only ever write what's considered
|
50
|
-
# its 'inner xml'.
|
51
|
-
#
|
52
|
-
# The parent of the current element is responsible for writing a
|
53
|
-
# containing element.
|
54
|
-
#
|
55
|
-
# This allows serializers to be re-used for different element names.
|
56
|
-
#
|
57
|
-
# If you are opening new elements, you must also close them again.
|
58
|
-
#
|
59
|
-
# @param [Writer] writer
|
60
|
-
# @return [void]
|
35
|
+
# (see XmlSerializable#xml_serialize)
|
61
36
|
def xml_serialize(writer)
|
62
|
-
@value
|
63
|
-
writer.write_element(val)
|
64
|
-
end
|
37
|
+
Serializer.enum(writer, @value)
|
65
38
|
end
|
66
39
|
|
67
|
-
#
|
68
|
-
#
|
69
|
-
# This method is called statictly, this is because in theory this method
|
70
|
-
# may be used as a type of constructor, or factory method.
|
71
|
-
#
|
72
|
-
# Often you want to return an instance of the current class, but you are
|
73
|
-
# free to return other data as well.
|
74
|
-
#
|
75
|
-
# Important note 2: You are responsible for advancing the reader to the
|
76
|
-
# next element. Not doing anything will result in a never-ending loop.
|
77
|
-
#
|
78
|
-
# If you just want to skip parsing for this element altogether, you can
|
79
|
-
# just call reader->next();
|
80
|
-
#
|
81
|
-
# reader->parseSubTree() will parse the entire sub-tree, and advance to
|
82
|
-
# the next element.
|
83
|
-
#
|
84
|
-
# @param [Reader] reader
|
85
|
-
# @return mixed
|
40
|
+
# (see XmlDeserializable#xml_deserialize)
|
86
41
|
def self.xml_deserialize(reader)
|
87
|
-
|
88
|
-
if reader.empty_element?
|
89
|
-
reader.next
|
90
|
-
return []
|
91
|
-
end
|
92
|
-
reader.read
|
93
|
-
current_depth = reader.depth
|
94
|
-
|
95
|
-
values = []
|
96
|
-
loop do
|
97
|
-
if reader.node_type == ::LibXML::XML::Reader::TYPE_ELEMENT
|
98
|
-
values << reader.clark
|
99
|
-
end
|
100
|
-
break unless reader.depth >= current_depth && reader.next
|
101
|
-
end
|
102
|
-
|
103
|
-
reader.next
|
104
|
-
values
|
42
|
+
Deserializer.enum(reader)
|
105
43
|
end
|
106
44
|
end
|
107
45
|
end
|
@@ -26,15 +26,6 @@ module Tilia
|
|
26
26
|
class KeyValue
|
27
27
|
include Element
|
28
28
|
|
29
|
-
protected
|
30
|
-
|
31
|
-
# Value to serialize
|
32
|
-
#
|
33
|
-
# @return [Array]
|
34
|
-
attr_accessor :value
|
35
|
-
|
36
|
-
public
|
37
|
-
|
38
29
|
# Constructor
|
39
30
|
#
|
40
31
|
# @param [Array] value
|
@@ -42,67 +33,14 @@ module Tilia
|
|
42
33
|
@value = value
|
43
34
|
end
|
44
35
|
|
45
|
-
#
|
46
|
-
#
|
47
|
-
# Use the writer argument to write its own xml serialization.
|
48
|
-
#
|
49
|
-
# An important note: do _not_ create a parent element. Any element
|
50
|
-
# implementing XmlSerializble should only ever write what's considered
|
51
|
-
# its 'inner xml'.
|
52
|
-
#
|
53
|
-
# The parent of the current element is responsible for writing a
|
54
|
-
# containing element.
|
55
|
-
#
|
56
|
-
# This allows serializers to be re-used for different element names.
|
57
|
-
#
|
58
|
-
# If you are opening new elements, you must also close them again.
|
59
|
-
#
|
60
|
-
# @param [Writer] writer
|
61
|
-
# @return [void]
|
36
|
+
# (see XmlSerializable#xml_serialize)
|
62
37
|
def xml_serialize(writer)
|
63
38
|
writer.write(@value)
|
64
39
|
end
|
65
40
|
|
66
|
-
#
|
67
|
-
#
|
68
|
-
# This method is called staticly, this is because in theory this method
|
69
|
-
# may be used as a type of constructor, or factory method.
|
70
|
-
#
|
71
|
-
# Often you want to return an instance of the current class, but you are
|
72
|
-
# free to return other data as well.
|
73
|
-
#
|
74
|
-
# Important note 2: You are responsible for advancing the reader to the
|
75
|
-
# next element. Not doing anything will result in a never-ending loop.
|
76
|
-
#
|
77
|
-
# If you just want to skip parsing for this element altogether, you can
|
78
|
-
# just call reader->next();
|
79
|
-
#
|
80
|
-
# reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
81
|
-
# the next element.
|
82
|
-
#
|
83
|
-
# @param [Reader] reader
|
84
|
-
# @return mixed
|
41
|
+
# (see XmlDeserializable#xml_deserialize)
|
85
42
|
def self.xml_deserialize(reader)
|
86
|
-
|
87
|
-
if reader.empty_element?
|
88
|
-
reader.next
|
89
|
-
return {}
|
90
|
-
end
|
91
|
-
|
92
|
-
values = {}
|
93
|
-
|
94
|
-
reader.read
|
95
|
-
loop do
|
96
|
-
if reader.node_type == ::LibXML::XML::Reader::TYPE_ELEMENT
|
97
|
-
clark = reader.clark
|
98
|
-
values[clark] = reader.parse_current_element['value']
|
99
|
-
else
|
100
|
-
reader.read
|
101
|
-
end
|
102
|
-
break unless reader.node_type != ::LibXML::XML::Reader::TYPE_END_ELEMENT
|
103
|
-
end
|
104
|
-
reader.read
|
105
|
-
values
|
43
|
+
Deserializer.key_value(reader)
|
106
44
|
end
|
107
45
|
end
|
108
46
|
end
|
@@ -14,15 +14,6 @@ module Tilia
|
|
14
14
|
class Uri
|
15
15
|
include Element
|
16
16
|
|
17
|
-
protected
|
18
|
-
|
19
|
-
# Uri element value.
|
20
|
-
#
|
21
|
-
# @return [String]
|
22
|
-
attr_accessor :value
|
23
|
-
|
24
|
-
public
|
25
|
-
|
26
17
|
# Constructor
|
27
18
|
#
|
28
19
|
# @param [String] value
|
@@ -30,23 +21,7 @@ module Tilia
|
|
30
21
|
@value = value
|
31
22
|
end
|
32
23
|
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# Use the writer argument to write its own xml serialization.
|
36
|
-
#
|
37
|
-
# An important note: do _not_ create a parent element. Any element
|
38
|
-
# implementing XmlSerializble should only ever write what's considered
|
39
|
-
# its 'inner xml'.
|
40
|
-
#
|
41
|
-
# The parent of the current element is responsible for writing a
|
42
|
-
# containing element.
|
43
|
-
#
|
44
|
-
# This allows serializers to be re-used for different element names.
|
45
|
-
#
|
46
|
-
# If you are opening new elements, you must also close them again.
|
47
|
-
#
|
48
|
-
# @param [Writer] writer
|
49
|
-
# @return [void]
|
24
|
+
# (see XmlSerializable#xml_serialize)
|
50
25
|
def xml_serialize(writer)
|
51
26
|
writer.write_string(
|
52
27
|
::Tilia::Uri.resolve(
|
@@ -56,25 +31,7 @@ module Tilia
|
|
56
31
|
)
|
57
32
|
end
|
58
33
|
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# This method is called statically, this is because in theory this method
|
62
|
-
# may be used as a type of constructor, or factory method.
|
63
|
-
#
|
64
|
-
# Often you want to return an instance of the current class, but you are
|
65
|
-
# free to return other data as well.
|
66
|
-
#
|
67
|
-
# Important note 2: You are responsible for advancing the reader to the
|
68
|
-
# next element. Not doing anything will result in a never-ending loop.
|
69
|
-
#
|
70
|
-
# If you just want to skip parsing for this element altogether, you can
|
71
|
-
# just call reader->next();
|
72
|
-
#
|
73
|
-
# reader->parseSubTree() will parse the entire sub-tree, and advance to
|
74
|
-
# the next element.
|
75
|
-
#
|
76
|
-
# @param [Reader] reader
|
77
|
-
# @return mixed
|
34
|
+
# (see XmlDeserializable#xml_deserialize)
|
78
35
|
def self.xml_deserialize(reader)
|
79
36
|
new(
|
80
37
|
::Tilia::Uri.resolve(
|
@@ -16,35 +16,13 @@ module Tilia
|
|
16
16
|
class XmlFragment
|
17
17
|
include Element
|
18
18
|
|
19
|
-
protected
|
20
|
-
|
21
|
-
attr_writer :xml
|
22
|
-
|
23
|
-
public
|
24
|
-
|
25
19
|
def initialize(xml)
|
26
20
|
@xml = xml
|
27
21
|
end
|
28
22
|
|
29
23
|
attr_reader :xml
|
30
24
|
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# Use the writer argument to write its own xml serialization.
|
34
|
-
#
|
35
|
-
# An important note: do _not_ create a parent element. Any element
|
36
|
-
# implementing XmlSerializble should only ever write what's considered
|
37
|
-
# its 'inner xml'.
|
38
|
-
#
|
39
|
-
# The parent of the current element is responsible for writing a
|
40
|
-
# containing element.
|
41
|
-
#
|
42
|
-
# This allows serializers to be re-used for different element names.
|
43
|
-
#
|
44
|
-
# If you are opening new elements, you must also close them again.
|
45
|
-
#
|
46
|
-
# @param [Writer] writer
|
47
|
-
# @return [void]
|
25
|
+
# (see XmlSerializable#xml_serialize)
|
48
26
|
def xml_serialize(writer)
|
49
27
|
reader = Reader.new
|
50
28
|
|
@@ -89,25 +67,7 @@ XML
|
|
89
67
|
end
|
90
68
|
end
|
91
69
|
|
92
|
-
#
|
93
|
-
#
|
94
|
-
# This method is called statictly, this is because in theory this method
|
95
|
-
# may be used as a type of constructor, or factory method.
|
96
|
-
#
|
97
|
-
# Often you want to return an instance of the current class, but you are
|
98
|
-
# free to return other data as well.
|
99
|
-
#
|
100
|
-
# You are responsible for advancing the reader to the next element. Not
|
101
|
-
# doing anything will result in a never-ending loop.
|
102
|
-
#
|
103
|
-
# If you just want to skip parsing for this element altogether, you can
|
104
|
-
# just call reader->next();
|
105
|
-
#
|
106
|
-
# reader->parseInnerTree() will parse the entire sub-tree, and advance to
|
107
|
-
# the next element.
|
108
|
-
#
|
109
|
-
# @param [Reader] reader
|
110
|
-
# @return mixed
|
70
|
+
# (see XmlDeserializable#xml_deserialize)
|
111
71
|
def self.xml_deserialize(reader)
|
112
72
|
result = new(reader.read_inner_xml)
|
113
73
|
reader.next
|
data/lib/tilia/xml/reader.rb
CHANGED
@@ -88,7 +88,7 @@ module Tilia
|
|
88
88
|
text = nil
|
89
89
|
elements = []
|
90
90
|
|
91
|
-
if node_type == ::LibXML::XML::Reader::TYPE_ELEMENT &&
|
91
|
+
if node_type == ::LibXML::XML::Reader::TYPE_ELEMENT && empty_element?
|
92
92
|
# Easy!
|
93
93
|
self.next
|
94
94
|
return nil
|
@@ -117,7 +117,7 @@ module Tilia
|
|
117
117
|
read
|
118
118
|
break
|
119
119
|
when ::LibXML::XML::Reader::TYPE_NONE
|
120
|
-
|
120
|
+
raise Tilia::Xml::ParseException, 'We hit the end of the document prematurely. This likely means that some parser "eats" too many elements. Do not attempt to continue parsing.'
|
121
121
|
else
|
122
122
|
# Advance to the next element
|
123
123
|
read
|
@@ -160,22 +160,10 @@ module Tilia
|
|
160
160
|
|
161
161
|
attributes = {}
|
162
162
|
|
163
|
-
attributes = parse_attributes if
|
163
|
+
attributes = parse_attributes if has_attributes?
|
164
164
|
|
165
|
-
|
166
|
-
deserializer = @element_map[name]
|
165
|
+
value = deserializer_for_element_name(name).call(self)
|
167
166
|
|
168
|
-
if deserializer.is_a?(Class) && deserializer.include?(XmlDeserializable)
|
169
|
-
value = deserializer.xml_deserialize(self)
|
170
|
-
elsif deserializer.is_a? Proc
|
171
|
-
value = deserializer.call(self)
|
172
|
-
else
|
173
|
-
# Omit php stuff for error creation
|
174
|
-
fail "Could not use this type as a deserializer: #{deserializer.inspect}"
|
175
|
-
end
|
176
|
-
else
|
177
|
-
value = Element::Base.xml_deserialize(self)
|
178
|
-
end
|
179
167
|
{
|
180
168
|
'name' => name,
|
181
169
|
'value' => value,
|
@@ -211,27 +199,44 @@ module Tilia
|
|
211
199
|
attributes
|
212
200
|
end
|
213
201
|
|
214
|
-
#
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
# TODO: documentation
|
202
|
+
# Fakes PHP method xml
|
203
|
+
#
|
204
|
+
# Creates a new XML::Reader instance
|
205
|
+
#
|
206
|
+
# @return [XML::Reader]
|
220
207
|
def xml(input)
|
221
|
-
|
208
|
+
raise 'XML document already loaded' if @reader
|
222
209
|
|
223
|
-
if input.is_a?
|
210
|
+
if input.is_a?(String)
|
224
211
|
@reader = ::LibXML::XML::Reader.string(input)
|
225
|
-
elsif input.is_a?
|
212
|
+
elsif input.is_a?(File)
|
226
213
|
@reader = ::LibXML::XML::Reader.file(input)
|
227
|
-
elsif input.is_a?
|
214
|
+
elsif input.is_a?(StringIO)
|
228
215
|
@reader = ::LibXML::XML::Reader.io(input)
|
229
216
|
else
|
230
|
-
|
217
|
+
raise 'Unable to load XML document'
|
231
218
|
end
|
232
219
|
end
|
233
220
|
|
234
|
-
#
|
221
|
+
# Returns the function that should be used to parse the element identified
|
222
|
+
# by it's clark-notation name.
|
223
|
+
#
|
224
|
+
# @param [String] name
|
225
|
+
# @return [#call]
|
226
|
+
def deserializer_for_element_name(name)
|
227
|
+
return Element::Base.method(:xml_deserialize) unless @element_map.key?(name)
|
228
|
+
|
229
|
+
deserializer = @element_map[name]
|
230
|
+
return deserializer if deserializer.respond_to?(:call)
|
231
|
+
|
232
|
+
return deserializer.method(:xml_deserialize) if deserializer.include?(XmlDeserializable)
|
233
|
+
|
234
|
+
raise "Could not use this type as a deserializer: #{deserializer.inspect} for element: #{name}"
|
235
|
+
end
|
236
|
+
|
237
|
+
# Delegates missing methods to XML::Reader instance
|
238
|
+
#
|
239
|
+
# @return [void]
|
235
240
|
def method_missing(name, *args)
|
236
241
|
@reader.send(name, *args)
|
237
242
|
end
|