tilia-xml 1.2.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rubocop.yml +32 -0
  4. data/.simplecov +4 -0
  5. data/.travis.yml +3 -0
  6. data/CHANGELOG.sabre.md +167 -0
  7. data/CONTRIBUTING.md +25 -0
  8. data/Gemfile +15 -0
  9. data/Gemfile.lock +56 -0
  10. data/LICENSE +27 -0
  11. data/LICENSE.sabre +27 -0
  12. data/README.md +30 -0
  13. data/Rakefile +17 -0
  14. data/lib/tilia/xml/context_stack_trait.rb +99 -0
  15. data/lib/tilia/xml/element/base.rb +73 -0
  16. data/lib/tilia/xml/element/cdata.rb +53 -0
  17. data/lib/tilia/xml/element/elements.rb +109 -0
  18. data/lib/tilia/xml/element/key_value.rb +110 -0
  19. data/lib/tilia/xml/element/uri.rb +98 -0
  20. data/lib/tilia/xml/element/xml_fragment.rb +128 -0
  21. data/lib/tilia/xml/element.rb +22 -0
  22. data/lib/tilia/xml/lib_xml_exception.rb +9 -0
  23. data/lib/tilia/xml/parse_exception.rb +7 -0
  24. data/lib/tilia/xml/reader.rb +240 -0
  25. data/lib/tilia/xml/service.rb +151 -0
  26. data/lib/tilia/xml/version.rb +9 -0
  27. data/lib/tilia/xml/writer.rb +261 -0
  28. data/lib/tilia/xml/xml_deserializable.rb +29 -0
  29. data/lib/tilia/xml/xml_serializable.rb +27 -0
  30. data/lib/tilia/xml.rb +23 -0
  31. data/test/test_helper.rb +4 -0
  32. data/test/xml/context_stack_test.rb +40 -0
  33. data/test/xml/element/cdata_test.rb +37 -0
  34. data/test/xml/element/eater.rb +60 -0
  35. data/test/xml/element/elements_test.rb +113 -0
  36. data/test/xml/element/key_value_test.rb +187 -0
  37. data/test/xml/element/mock.rb +52 -0
  38. data/test/xml/element/uri_test.rb +55 -0
  39. data/test/xml/element/xml_fragment_test.rb +121 -0
  40. data/test/xml/infite_loop_test.rb +47 -0
  41. data/test/xml/reader_test.rb +407 -0
  42. data/test/xml/service_test.rb +156 -0
  43. data/test/xml/writer_test.rb +260 -0
  44. data/tilia-xml.gemspec +15 -0
  45. metadata +132 -0
@@ -0,0 +1,240 @@
1
+ require 'libxml'
2
+ require 'stringio'
3
+ LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER)
4
+
5
+ module Tilia
6
+ module Xml
7
+ # The Reader class expands upon PHP's built-in XMLReader.
8
+ #
9
+ # The intended usage, is to assign certain XML elements to PHP classes. These
10
+ # need to be registered using the element_map public property.
11
+ #
12
+ # After this is done, a single call to parse() will parse the entire document,
13
+ # and delegate sub-sections of the document to element classes.
14
+ class Reader
15
+ include Tilia::Xml::ContextStackTrait
16
+
17
+ # Returns the current nodename in clark-notation.
18
+ #
19
+ # For example: "{http://www.w3.org/2005/Atom}feed".
20
+ # Or if no namespace is defined: "{}feed".
21
+ #
22
+ # This method returns null if we're not currently on an element.
23
+ #
24
+ # @return [String, nil]
25
+ def clark
26
+ return nil unless local_name
27
+
28
+ "{#{namespace_uri}}#{local_name}"
29
+ end
30
+
31
+ # Reads the entire document.
32
+ #
33
+ # This function returns an array with the following three elements:
34
+ # * name - The root element name.
35
+ # * value - The value for the root element.
36
+ # * attributes - An array of attributes.
37
+ #
38
+ # This function will also disable the standard libxml error handler (which
39
+ # usually just results in PHP errors), and throw exceptions instead.
40
+ #
41
+ # @return [Hash]
42
+ def parse
43
+ begin
44
+ nil while node_type != ::LibXML::XML::Reader::TYPE_ELEMENT && read # noop
45
+
46
+ result = parse_current_element
47
+ rescue ::LibXML::XML::Error => e
48
+ raise Tilia::Xml::LibXmlException, e.to_s
49
+ end
50
+
51
+ result
52
+ end
53
+
54
+ # parse_get_elements parses everything in the current sub-tree,
55
+ # and returns a an array of elements.
56
+ #
57
+ # Each element has a 'name', 'value' and 'attributes' key.
58
+ #
59
+ # If the the element didn't contain sub-elements, an empty array is always
60
+ # returned. If there was any text inside the element, it will be
61
+ # discarded.
62
+ #
63
+ # If the element_map argument is specified, the existing element_map will
64
+ # be overridden while parsing the tree, and restored after this process.
65
+ #
66
+ # @param [Hash] element_map
67
+ # @return [Array]
68
+ def parse_get_elements(element_map = nil)
69
+ result = parse_inner_tree(element_map)
70
+
71
+ return [] unless result.is_a?(Array)
72
+ result
73
+ end
74
+
75
+ # Parses all elements below the current element.
76
+ #
77
+ # This method will return a string if this was a text-node, or an array if
78
+ # there were sub-elements.
79
+ #
80
+ # If there's both text and sub-elements, the text will be discarded.
81
+ #
82
+ # If the element_map argument is specified, the existing element_map will
83
+ # be overridden while parsing the tree, and restored after this process.
84
+ #
85
+ # @param [Hash] element_map
86
+ # @return [Array, String]
87
+ def parse_inner_tree(element_map = nil)
88
+ text = nil
89
+ elements = []
90
+
91
+ if node_type == ::LibXML::XML::Reader::TYPE_ELEMENT && self.empty_element?
92
+ # Easy!
93
+ self.next
94
+ return nil
95
+ end
96
+
97
+ unless element_map.nil?
98
+ push_context
99
+ @element_map = element_map
100
+ end
101
+
102
+ return false unless read
103
+
104
+ loop do
105
+ # RUBY: Skip is_valid block
106
+
107
+ case node_type
108
+ when ::LibXML::XML::Reader::TYPE_ELEMENT
109
+ elements << parse_current_element
110
+ when ::LibXML::XML::Reader::TYPE_TEXT,
111
+ ::LibXML::XML::Reader::TYPE_CDATA
112
+ text ||= ''
113
+ text += value
114
+ read
115
+ when ::LibXML::XML::Reader::TYPE_END_ELEMENT
116
+ # Ensuring we are moving the cursor after the end element.
117
+ read
118
+ break
119
+ when ::LibXML::XML::Reader::TYPE_NONE
120
+ fail 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
+ else
122
+ # Advance to the next element
123
+ read
124
+ end
125
+ end
126
+
127
+ pop_context unless element_map.nil?
128
+
129
+ elements.any? ? elements : text
130
+ end
131
+
132
+ # Reads all text below the current element, and returns this as a string.
133
+ #
134
+ # @return [String]
135
+ def read_text
136
+ result = ''
137
+ previous_depth = depth
138
+
139
+ while read && depth != previous_depth
140
+ result += value if [
141
+ ::LibXML::XML::Reader::TYPE_TEXT,
142
+ ::LibXML::XML::Reader::TYPE_CDATA,
143
+ ::LibXML::XML::Reader::TYPE_WHITESPACE
144
+ ].include? node_type
145
+ end
146
+
147
+ result
148
+ end
149
+
150
+ # Parses the current XML element.
151
+ #
152
+ # This method returns arn array with 3 properties:
153
+ # * name - A clark-notation XML element name.
154
+ # * value - The parsed value.
155
+ # * attributes - A key-value list of attributes.
156
+ #
157
+ # @return [Hash]
158
+ def parse_current_element
159
+ name = clark
160
+
161
+ attributes = {}
162
+
163
+ attributes = parse_attributes if self.has_attributes?
164
+
165
+ if @element_map.key? name
166
+ deserializer = @element_map[name]
167
+
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
+ {
180
+ 'name' => name,
181
+ 'value' => value,
182
+ 'attributes' => attributes
183
+ }
184
+ end
185
+
186
+ # Grabs all the attributes from the current element, and returns them as a
187
+ # key-value array.
188
+ #
189
+ # If the attributes are part of the same namespace, they will simply be
190
+ # short keys. If they are defined on a different namespace, the attribute
191
+ # name will be retured in clark-notation.
192
+ #
193
+ # @return [Hash]
194
+ def parse_attributes
195
+ attributes = {}
196
+
197
+ while move_to_next_attribute != 0
198
+ if namespace_uri
199
+ # Ignoring 'xmlns', it doesn't make any sense.
200
+ next if namespace_uri == 'http://www.w3.org/2000/xmlns/'
201
+
202
+ name = clark
203
+ attributes[name] = value
204
+ else
205
+ attributes[local_name] = value
206
+ end
207
+ end
208
+
209
+ move_to_element
210
+
211
+ attributes
212
+ end
213
+
214
+ # TODO: document this
215
+ def initialize
216
+ initialize_context_stack_attributes
217
+ end
218
+
219
+ # TODO: documentation
220
+ def xml(input)
221
+ fail 'XML document already loaded' if @reader
222
+
223
+ if input.is_a? String
224
+ @reader = ::LibXML::XML::Reader.string(input)
225
+ elsif input.is_a? File
226
+ @reader = ::LibXML::XML::Reader.file(input)
227
+ elsif input.is_a? StringIO
228
+ @reader = ::LibXML::XML::Reader.io(input)
229
+ else
230
+ fail 'Unable to load XML document'
231
+ end
232
+ end
233
+
234
+ # TODO: documentation
235
+ def method_missing(name, *args)
236
+ @reader.send(name, *args)
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,151 @@
1
+ module Tilia
2
+ module Xml
3
+ # XML parsing and writing service.
4
+ #
5
+ # You are encouraged to make a instance of this for your application and
6
+ # potentially extend it, as a central API point for dealing with xml and
7
+ # configuring the reader and writer.
8
+ class Service
9
+ # This is the element map. It contains a list of XML elements (in clark
10
+ # notation) as keys and PHP class names as values.
11
+ #
12
+ # The PHP class names must implement Sabre\Xml\Element.
13
+ #
14
+ # Values may also be a callable. In that case the function will be called
15
+ # directly.
16
+ #
17
+ # @return [Hash]
18
+ attr_accessor :element_map
19
+
20
+ # This is a list of namespaces that you want to give default prefixes.
21
+ #
22
+ # You must make sure you create this entire list before starting to write.
23
+ # They should be registered on the root element.
24
+ #
25
+ # @return [Hash]
26
+ attr_accessor :namespace_map
27
+
28
+ # Returns a fresh XML Reader
29
+ #
30
+ # @return [Reader]
31
+ def reader
32
+ reader = Reader.new
33
+ reader.element_map = @element_map
34
+ reader
35
+ end
36
+
37
+ # Returns a fresh xml writer
38
+ #
39
+ # @return [Writer]
40
+ def writer
41
+ writer = Writer.new
42
+ writer.namespace_map = @namespace_map
43
+ writer
44
+ end
45
+
46
+ # Parses a document in full.
47
+ #
48
+ # Input may be specified as a string or readable stream resource.
49
+ # The returned value is the value of the root document.
50
+ #
51
+ # Specifying the context_uri allows the parser to figure out what the URI
52
+ # of the document was. This allows relative URIs within the document to be
53
+ # expanded easily.
54
+ #
55
+ # The root_element_name is specified by reference and will be populated
56
+ # with the root element name of the document.
57
+ #
58
+ # @param [String, File, StringIO] input
59
+ # @param [String, nil] context_uri
60
+ # @param [String, nil] root_element_name
61
+ # @raise [ParseException]
62
+ # @return [Array, Object, String]
63
+ def parse(input, context_uri = nil, root_element_name = nil)
64
+ # Skip php short commings
65
+ reader = self.reader
66
+ reader.context_uri = context_uri
67
+ reader.xml(input)
68
+
69
+ result = reader.parse
70
+ root_element_name.replace(result['name'])
71
+ result['value']
72
+ end
73
+
74
+ # Parses a document in full, and specify what the expected root element
75
+ # name is.
76
+ #
77
+ # This function works similar to parse, but the difference is that the
78
+ # user can specify what the expected name of the root element should be,
79
+ # in clark notation.
80
+ #
81
+ # This is useful in cases where you expected a specific document to be
82
+ # passed, and reduces the amount of if statements.
83
+ #
84
+ # @param [String] root_element_name
85
+ # @param [String, File, StringIO] input
86
+ # @param [String, nil] context_uri
87
+ # @return [void]
88
+ def expect(root_element_name, input, context_uri = nil)
89
+ # Skip php short commings
90
+ reader = self.reader
91
+ reader.context_uri = context_uri
92
+ reader.xml(input)
93
+
94
+ result = reader.parse
95
+ if root_element_name != result['name']
96
+ fail Tilia::Xml::ParseException, "Expected #{root_element_name} but received #{result['name']} as the root element"
97
+ end
98
+ result['value']
99
+ end
100
+
101
+ # Generates an XML document in one go.
102
+ #
103
+ # The $rootElement must be specified in clark notation.
104
+ # The value must be a string, an array or an object implementing
105
+ # XmlSerializable. Basically, anything that's supported by the Writer
106
+ # object.
107
+ #
108
+ # context_uri can be used to specify a sort of 'root' of the PHP application,
109
+ # in case the xml document is used as a http response.
110
+ #
111
+ # This allows an implementor to easily create URI's relative to the root
112
+ # of the domain.
113
+ #
114
+ # @param [String] root_element_name
115
+ # @param [String, Array, XmlSerializable] value
116
+ # @param [String, nil] context_uri
117
+ # @return [void]
118
+ def write(root_element_name, value, context_uri = nil)
119
+ writer = self.writer
120
+ writer.open_memory
121
+ writer.context_uri = context_uri
122
+ writer.set_indent(true)
123
+ writer.start_document
124
+ writer.write_element(root_element_name, value)
125
+ writer.output_memory
126
+ end
127
+
128
+ # Parses a clark-notation string, and returns the namespace and element
129
+ # name components.
130
+ #
131
+ # If the string was invalid, it will throw an InvalidArgumentException.
132
+ #
133
+ # @param [String] str
134
+ # @raise [InvalidArgumentException]
135
+ # @return [Array]
136
+ def self.parse_clark_notation(str)
137
+ if str =~ /^{([^}]*)}(.*)/
138
+ [Regexp.last_match[1], Regexp.last_match[2]]
139
+ else
140
+ fail ArgumentError, "'#{str}' is not a valid clark-notation formatted string"
141
+ end
142
+ end
143
+
144
+ # TODO: document
145
+ def initialize
146
+ @element_map = {}
147
+ @namespace_map = {}
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,9 @@
1
+ module Tilia
2
+ module Xml
3
+ # This class contains the version number for this package.
4
+ class Version
5
+ # Full version number
6
+ VERSION = '1.2.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,261 @@
1
+ require 'libxml'
2
+ module Tilia
3
+ module Xml
4
+ # The XML Writer class.
5
+ #
6
+ # This class works exactly as PHP's built-in XMLWriter, with a few additions.
7
+ #
8
+ # Namespaces can be registered beforehand, globally. When the first element is
9
+ # written, namespaces will automatically be declared.
10
+ #
11
+ # The write_attribute, startElement and write_element can now take a
12
+ # clark-notation element name (example: {http://www.w3.org/2005/Atom}link).
13
+ #
14
+ # If, when writing the namespace is a known one a prefix will automatically be
15
+ # selected, otherwise a random prefix will be generated.
16
+ #
17
+ # Instead of standard string values, the writer can take Element classes (as
18
+ # defined by this library) to delegate the serialization.
19
+ #
20
+ # The write() method can take array structures to quickly write out simple xml
21
+ # trees.
22
+ class Writer
23
+ include ContextStackTrait
24
+
25
+ protected
26
+
27
+ # Any namespace that the writer is asked to write, will be added here.
28
+ #
29
+ # Any of these elements will get a new namespace definition *every single
30
+ # time* they are used, but this array allows the writer to make sure that
31
+ # the prefixes are consistent anyway.
32
+ #
33
+ # @return [Hash]
34
+ attr_accessor :adhoc_namespaces
35
+
36
+ # When the first element is written, this flag is set to true.
37
+ #
38
+ # This ensures that the namespaces in the namespaces map are only written
39
+ # once.
40
+ #
41
+ # @return [Boolean]
42
+ attr_accessor :namespaces_written
43
+
44
+ public
45
+
46
+ # Writes a value to the output stream.
47
+ #
48
+ # The following values are supported:
49
+ # 1. Scalar values will be written as-is, as text.
50
+ # 2. Null values will be skipped (resulting in a short xml tag).
51
+ # 3. If a value is an instance of an Element class, writing will be
52
+ # delegated to the object.
53
+ # 4. If a value is an array, two formats are supported.
54
+ #
55
+ # Array format 1:
56
+ # [
57
+ # "{namespace}name1" => "..",
58
+ # "{namespace}name2" => "..",
59
+ # ]
60
+ #
61
+ # One element will be created for each key in this array. The values of
62
+ # this array support any format this method supports (this method is
63
+ # called recursively).
64
+ #
65
+ # Array format 2:
66
+ #
67
+ # [
68
+ # [
69
+ # "name" => "{namespace}name1"
70
+ # "value" => "..",
71
+ # "attributes" => [
72
+ # "attr" => "attribute value",
73
+ # ]
74
+ # ],
75
+ # [
76
+ # "name" => "{namespace}name1"
77
+ # "value" => "..",
78
+ # "attributes" => [
79
+ # "attr" => "attribute value",
80
+ # ]
81
+ # ]
82
+ # ]
83
+ #
84
+ # @param value
85
+ # @return [void]
86
+ def write(value)
87
+ if value.is_a?(Numeric) || value.is_a?(String)
88
+ write_string(value.to_s)
89
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
90
+ write_string(value.to_s)
91
+ elsif value.is_a? XmlSerializable
92
+ value.xml_serialize(self)
93
+ elsif value.nil?
94
+ # noop
95
+ elsif value.is_a?(Hash) || value.is_a?(Array)
96
+ # Code for ruby implementation
97
+ if value.is_a?(Array)
98
+ hash = {}
99
+ value.each_with_index do |v, i|
100
+ hash[i] = v
101
+ end
102
+ value = hash
103
+ end
104
+
105
+ value.each do |name, item|
106
+ if name.is_a? Fixnum
107
+ # This item has a numeric index. We expect to be an array with a name and a value.
108
+ unless item.is_a?(Hash) && item.key?('name') && item.key?('value')
109
+ fail ArgumentError, 'When passing an array to ->write with numeric indices, every item must be an array containing the "name" and "value" key'
110
+ end
111
+
112
+ attributes = item.key?('attributes') ? item['attributes'] : []
113
+ name = item['name']
114
+ item = item['value']
115
+ elsif item.is_a?(Hash) && item.key?('value')
116
+ # This item has a text index. We expect to be an array with a value and optional attributes.
117
+ attributes = item.key?('attributes') ? item['attributes'] : []
118
+ item = item['value']
119
+ else
120
+ # If it's an array with text-indices, we expect every item's
121
+ # key to be an xml element name in clark notation.
122
+ # No attributes can be passed.
123
+ attributes = []
124
+ end
125
+
126
+ start_element(name)
127
+ write_attributes(attributes)
128
+ write(item)
129
+ end_element
130
+ end
131
+ else
132
+ fail ArgumentError, "The writer cannot serialize objects of type: #{value.class}"
133
+ end
134
+ end
135
+
136
+ # Starts an element.
137
+ #
138
+ # @param [String] name
139
+ # @return [Boolean]
140
+ def start_element(name)
141
+ if name[0] == '{'
142
+ (namespace, local_name) = Service.parse_clark_notation(name)
143
+
144
+ if @namespace_map.key? namespace
145
+ result = start_element_ns(@namespace_map[namespace], local_name, nil)
146
+ else
147
+ # An empty namespace means it's the global namespace. This is
148
+ # allowed, but it mustn't get a prefix.
149
+ if namespace == ''
150
+ result = start_element(local_name)
151
+ write_attribute('xmlns', '')
152
+ else
153
+ unless @adhoc_namespaces.key? namespace
154
+ @adhoc_namespaces[namespace] = 'x' + (@adhoc_namespaces.size + 1).to_s
155
+ end
156
+ result = start_element_ns(@adhoc_namespaces[namespace], local_name, namespace)
157
+ end
158
+ end
159
+ else
160
+ result = @writer.start_element(name)
161
+ end
162
+
163
+ unless @namespaces_written
164
+ @namespace_map.each do |ns, prefix|
165
+ write_attribute((prefix ? 'xmlns:' + prefix : 'xmlns'), ns)
166
+ end
167
+ @namespaces_written = true
168
+ end
169
+
170
+ result
171
+ end
172
+
173
+ # Write a full element tag.
174
+ #
175
+ # This method automatically closes the element as well.
176
+ #
177
+ # @param [String] name
178
+ # @param [String] content
179
+ # @return [Boolean]
180
+ def write_element(name, content = nil)
181
+ start_element(name)
182
+ write(content) unless content.nil?
183
+ end_element
184
+ end
185
+
186
+ # Writes a list of attributes.
187
+ #
188
+ # Attributes are specified as a key->value array.
189
+ #
190
+ # The key is an attribute name. If the key is a 'localName', the current
191
+ # xml namespace is assumed. If it's a 'clark notation key', this namespace
192
+ # will be used instead.
193
+ #
194
+ # @param [Hash] attributes
195
+ # @return [void]
196
+ def write_attributes(attributes)
197
+ attributes.each do |name, value|
198
+ write_attribute(name, value)
199
+ end
200
+ end
201
+
202
+ # Writes a new attribute.
203
+ #
204
+ # The name may be specified in clark-notation.
205
+ #
206
+ # Returns true when successful.
207
+ #
208
+ # @param [String] name
209
+ # @param [String] value
210
+ # @return [Boolean]
211
+ def write_attribute(name, value)
212
+ if name[0] == '{'
213
+ (namespace, local_name) = Service.parse_clark_notation(name)
214
+ if @namespace_map.key? namespace
215
+ # It's an attribute with a namespace we know
216
+ write_attribute(
217
+ @namespace_map[namespace] + ':' + local_name,
218
+ value
219
+ )
220
+ else
221
+ # We don't know the namespace, we must add it in-line
222
+ @adhoc_namespaces[namespace] = 'x' + (@adhoc_namespaces.size + 1).to_s unless @adhoc_namespaces.key?(namespace)
223
+
224
+ write_attribute_ns(
225
+ @adhoc_namespaces[namespace],
226
+ local_name,
227
+ namespace,
228
+ value
229
+ )
230
+ end
231
+ else
232
+ @writer.write_attribute(name, value)
233
+ end
234
+ end
235
+
236
+ # TODO: document this
237
+ def initialize
238
+ @adhoc_namespaces = {}
239
+ @namespaces_written = false
240
+ initialize_context_stack_attributes
241
+ end
242
+
243
+ # TODO: documentation
244
+ def open_memory
245
+ fail 'XML document already created' if @writer
246
+
247
+ @writer = ::LibXML::XML::Writer.string
248
+ end
249
+
250
+ # TODO: documentation
251
+ def output_memory
252
+ @writer.result
253
+ end
254
+
255
+ # TODO: documentation
256
+ def method_missing(name, *args)
257
+ @writer.send(name, *args)
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,29 @@
1
+ module Tilia
2
+ module Xml
3
+ # Implementing the XmlDeserializable interface allows you to use a class as a
4
+ # deserializer for a specific element.
5
+ module XmlDeserializable
6
+ # The deserialize method is called during xml parsing.
7
+ #
8
+ # This method is called statictly, this is because in theory this method
9
+ # may be used as a type of constructor, or factory method.
10
+ #
11
+ # Often you want to return an instance of the current class, but you are
12
+ # free to return other data as well.
13
+ #
14
+ # You are responsible for advancing the reader to the next element. Not
15
+ # doing anything will result in a never-ending loop.
16
+ #
17
+ # If you just want to skip parsing for this element altogether, you can
18
+ # just call $reader->next();
19
+ #
20
+ # $reader->parseInnerTree() will parse the entire sub-tree, and advance to
21
+ # the next element.
22
+ #
23
+ # @param [Reader] _reader
24
+ # @return mixed
25
+ def xml_deserialize(_reader)
26
+ end
27
+ end
28
+ end
29
+ end