tilia-xml 1.2.0.2 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|