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