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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rubocop.yml +32 -0
- data/.simplecov +4 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.sabre.md +167 -0
- data/CONTRIBUTING.md +25 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +56 -0
- data/LICENSE +27 -0
- data/LICENSE.sabre +27 -0
- data/README.md +30 -0
- data/Rakefile +17 -0
- data/lib/tilia/xml/context_stack_trait.rb +99 -0
- data/lib/tilia/xml/element/base.rb +73 -0
- data/lib/tilia/xml/element/cdata.rb +53 -0
- data/lib/tilia/xml/element/elements.rb +109 -0
- data/lib/tilia/xml/element/key_value.rb +110 -0
- data/lib/tilia/xml/element/uri.rb +98 -0
- data/lib/tilia/xml/element/xml_fragment.rb +128 -0
- data/lib/tilia/xml/element.rb +22 -0
- data/lib/tilia/xml/lib_xml_exception.rb +9 -0
- data/lib/tilia/xml/parse_exception.rb +7 -0
- data/lib/tilia/xml/reader.rb +240 -0
- data/lib/tilia/xml/service.rb +151 -0
- data/lib/tilia/xml/version.rb +9 -0
- data/lib/tilia/xml/writer.rb +261 -0
- data/lib/tilia/xml/xml_deserializable.rb +29 -0
- data/lib/tilia/xml/xml_serializable.rb +27 -0
- data/lib/tilia/xml.rb +23 -0
- data/test/test_helper.rb +4 -0
- data/test/xml/context_stack_test.rb +40 -0
- data/test/xml/element/cdata_test.rb +37 -0
- data/test/xml/element/eater.rb +60 -0
- data/test/xml/element/elements_test.rb +113 -0
- data/test/xml/element/key_value_test.rb +187 -0
- data/test/xml/element/mock.rb +52 -0
- data/test/xml/element/uri_test.rb +55 -0
- data/test/xml/element/xml_fragment_test.rb +121 -0
- data/test/xml/infite_loop_test.rb +47 -0
- data/test/xml/reader_test.rb +407 -0
- data/test/xml/service_test.rb +156 -0
- data/test/xml/writer_test.rb +260 -0
- data/tilia-xml.gemspec +15 -0
- metadata +132 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
module Tilia
|
2
|
+
module Xml
|
3
|
+
module Element
|
4
|
+
# 'Elements' is a simple list of elements, without values or attributes.
|
5
|
+
# For example, Elements will parse:
|
6
|
+
#
|
7
|
+
# <?xml version="1.0"?>
|
8
|
+
# <s:root xmlns:s="http://sabredav.org/ns">
|
9
|
+
# <s:elem1 />
|
10
|
+
# <s:elem2 />
|
11
|
+
# <s:elem3 />
|
12
|
+
# <s:elem4>content</s:elem4>
|
13
|
+
# <s:elem5 attr="val" />
|
14
|
+
# </s:root>
|
15
|
+
#
|
16
|
+
# Into:
|
17
|
+
#
|
18
|
+
# [
|
19
|
+
# "{http://sabredav.org/ns}elem1",
|
20
|
+
# "{http://sabredav.org/ns}elem2",
|
21
|
+
# "{http://sabredav.org/ns}elem3",
|
22
|
+
# "{http://sabredav.org/ns}elem4",
|
23
|
+
# "{http://sabredav.org/ns}elem5",
|
24
|
+
# ];
|
25
|
+
class Elements
|
26
|
+
include Element
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
# Value to serialize
|
31
|
+
#
|
32
|
+
# @return [Array]
|
33
|
+
attr_accessor :value
|
34
|
+
|
35
|
+
public
|
36
|
+
|
37
|
+
# Constructor
|
38
|
+
#
|
39
|
+
# @param [Array] value
|
40
|
+
def initialize(value = [])
|
41
|
+
@value = value
|
42
|
+
end
|
43
|
+
|
44
|
+
# The xml_serialize metod is called during xml writing.
|
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]
|
61
|
+
def xml_serialize(writer)
|
62
|
+
@value.each do |val|
|
63
|
+
writer.write_element(val)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# The deserialize method is called during xml parsing.
|
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
|
86
|
+
def self.xml_deserialize(reader)
|
87
|
+
# If there's no children, we don't do anything.
|
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
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Tilia
|
2
|
+
module Xml
|
3
|
+
module Element
|
4
|
+
# 'KeyValue' parses out all child elements from a single node, and outputs a
|
5
|
+
# key=>value struct.
|
6
|
+
#
|
7
|
+
# Attributes will be removed, and duplicate child elements are discarded.
|
8
|
+
# Complex values within the elements will be parsed by the 'standard' parser.
|
9
|
+
#
|
10
|
+
# For example, KeyValue will parse:
|
11
|
+
#
|
12
|
+
# <?xml version="1.0"?>
|
13
|
+
# <s:root xmlns:s="http://sabredav.org/ns">
|
14
|
+
# <s:elem1>value1</s:elem1>
|
15
|
+
# <s:elem2>value2</s:elem2>
|
16
|
+
# <s:elem3 />
|
17
|
+
# </s:root>
|
18
|
+
#
|
19
|
+
# Into:
|
20
|
+
#
|
21
|
+
# [
|
22
|
+
# "{http://sabredav.org/ns}elem1" => "value1",
|
23
|
+
# "{http://sabredav.org/ns}elem2" => "value2",
|
24
|
+
# "{http://sabredav.org/ns}elem3" => null,
|
25
|
+
# ];
|
26
|
+
class KeyValue
|
27
|
+
include Element
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
# Value to serialize
|
32
|
+
#
|
33
|
+
# @return [Array]
|
34
|
+
attr_accessor :value
|
35
|
+
|
36
|
+
public
|
37
|
+
|
38
|
+
# Constructor
|
39
|
+
#
|
40
|
+
# @param [Array] value
|
41
|
+
def initialize(value = [])
|
42
|
+
@value = value
|
43
|
+
end
|
44
|
+
|
45
|
+
# The xml_serialize metod is called during xml writing.
|
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]
|
62
|
+
def xml_serialize(writer)
|
63
|
+
writer.write(@value)
|
64
|
+
end
|
65
|
+
|
66
|
+
# The deserialize method is called during xml parsing.
|
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
|
85
|
+
def self.xml_deserialize(reader)
|
86
|
+
# If there's no children, we don't do anything.
|
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
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Tilia
|
2
|
+
module Xml
|
3
|
+
module Element
|
4
|
+
# Uri element.
|
5
|
+
#
|
6
|
+
# This represents a single uri. An example of how this may be encoded:
|
7
|
+
#
|
8
|
+
# <link>/foo/bar</link>
|
9
|
+
# <d:href xmlns:d="DAV:">http://example.org/hi</d:href>
|
10
|
+
#
|
11
|
+
# If the uri is relative, it will be automatically expanded to an absolute
|
12
|
+
# url during writing and reading, if the contextUri property is set on the
|
13
|
+
# reader and/or writer.
|
14
|
+
class Uri
|
15
|
+
include Element
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
# Uri element value.
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
attr_accessor :value
|
23
|
+
|
24
|
+
public
|
25
|
+
|
26
|
+
# Constructor
|
27
|
+
#
|
28
|
+
# @param [String] value
|
29
|
+
def initialize(value)
|
30
|
+
@value = value
|
31
|
+
end
|
32
|
+
|
33
|
+
# The xml_serialize metod is called during xml writing.
|
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]
|
50
|
+
def xml_serialize(writer)
|
51
|
+
writer.write_string(
|
52
|
+
::Tilia::Uri.resolve(
|
53
|
+
writer.context_uri,
|
54
|
+
@value
|
55
|
+
)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
# This method is called during xml parsing.
|
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
|
78
|
+
def self.xml_deserialize(reader)
|
79
|
+
new(
|
80
|
+
::Tilia::Uri.resolve(
|
81
|
+
reader.context_uri,
|
82
|
+
reader.read_text
|
83
|
+
)
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
# TODO: document
|
88
|
+
def ==(other)
|
89
|
+
if other.is_a? self.class
|
90
|
+
other.instance_eval { @value } == @value
|
91
|
+
else
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Tilia
|
2
|
+
module Xml
|
3
|
+
module Element
|
4
|
+
# The XmlFragment element allows you to extract a portion of your xml tree,
|
5
|
+
# and get a well-formed xml string.
|
6
|
+
#
|
7
|
+
# This goes a bit beyond `innerXml` and friends, as we'll also match all the
|
8
|
+
# correct namespaces.
|
9
|
+
#
|
10
|
+
# Please note that the XML fragment:
|
11
|
+
#
|
12
|
+
# 1. Will not have an <?xml declaration.
|
13
|
+
# 2. Or a DTD
|
14
|
+
# 3. It will have all the relevant xmlns attributes.
|
15
|
+
# 4. It may not have a root element.
|
16
|
+
class XmlFragment
|
17
|
+
include Element
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
attr_accessor :xml
|
22
|
+
|
23
|
+
public
|
24
|
+
|
25
|
+
def initialize(xml)
|
26
|
+
@xml = xml
|
27
|
+
end
|
28
|
+
|
29
|
+
# get_xml replaced by xml
|
30
|
+
|
31
|
+
# The xml_serialize metod is called during xml writing.
|
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]
|
48
|
+
def xml_serialize(writer)
|
49
|
+
reader = Reader.new
|
50
|
+
|
51
|
+
# Wrapping the xml in a container, so root-less values can still be
|
52
|
+
# parsed.
|
53
|
+
xml = <<XML
|
54
|
+
<?xml version="1.0"?>
|
55
|
+
<xml-fragment xmlns="http://sabre.io/ns">#{@xml}</xml-fragment>
|
56
|
+
XML
|
57
|
+
|
58
|
+
reader.xml(xml)
|
59
|
+
|
60
|
+
while reader.read
|
61
|
+
if reader.depth < 1
|
62
|
+
# Skipping the root node.
|
63
|
+
next
|
64
|
+
end
|
65
|
+
|
66
|
+
case reader.node_type
|
67
|
+
when ::LibXML::XML::Reader::TYPE_ELEMENT
|
68
|
+
writer.start_element(reader.clark)
|
69
|
+
empty = reader.empty_element?
|
70
|
+
|
71
|
+
while reader.move_to_next_attribute != 0
|
72
|
+
case reader.namespace_uri
|
73
|
+
when '', nil # RUBY namespace_uri = nil ...
|
74
|
+
writer.write_attribute(reader.local_name, reader.value)
|
75
|
+
when 'http://www.w3.org/2000/xmlns/'
|
76
|
+
# Skip namespace declarations
|
77
|
+
else
|
78
|
+
writer.write_attribute(reader.clark, reader.value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
writer.end_element if empty
|
83
|
+
when ::LibXML::XML::Reader::TYPE_CDATA,
|
84
|
+
::LibXML::XML::Reader::TYPE_TEXT
|
85
|
+
writer.write_string(reader.value)
|
86
|
+
when ::LibXML::XML::Reader::TYPE_END_ELEMENT
|
87
|
+
writer.end_element
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# The deserialize method is called during xml parsing.
|
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
|
111
|
+
def self.xml_deserialize(reader)
|
112
|
+
result = new(reader.read_inner_xml)
|
113
|
+
reader.next
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
# TODO: document
|
118
|
+
def ==(other)
|
119
|
+
if other.is_a? self.class
|
120
|
+
other.xml == @xml
|
121
|
+
else
|
122
|
+
false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Tilia
|
2
|
+
module Xml
|
3
|
+
# This is the XML element interface.
|
4
|
+
#
|
5
|
+
# Elements are responsible for serializing and deserializing part of an XML
|
6
|
+
# document into PHP values.
|
7
|
+
#
|
8
|
+
# It combines XmlSerializable and XmlDeserializable into one logical class
|
9
|
+
# that does both.
|
10
|
+
module Element
|
11
|
+
include XmlSerializable
|
12
|
+
include XmlDeserializable
|
13
|
+
|
14
|
+
require 'tilia/xml/element/base'
|
15
|
+
require 'tilia/xml/element/cdata'
|
16
|
+
require 'tilia/xml/element/elements'
|
17
|
+
require 'tilia/xml/element/key_value'
|
18
|
+
require 'tilia/xml/element/uri'
|
19
|
+
require 'tilia/xml/element/xml_fragment'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|