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,27 @@
1
+ module Tilia
2
+ module Xml
3
+ # Objects implementing XmlSerializable can control how they are represented in
4
+ # Xml.
5
+ module XmlSerializable
6
+ # The xmlSerialize metod is called during xml writing.
7
+ #
8
+ # Use the $writer argument to write its own xml serialization.
9
+ #
10
+ # An important note: do _not_ create a parent element. Any element
11
+ # implementing XmlSerializble should only ever write what's considered
12
+ # its 'inner xml'.
13
+ #
14
+ # The parent of the current element is responsible for writing a
15
+ # containing element.
16
+ #
17
+ # This allows serializers to be re-used for different element names.
18
+ #
19
+ # If you are opening new elements, you must also close them again.
20
+ #
21
+ # @param [Writer] _writer
22
+ # @return [void]
23
+ def xml_serialize(_writer)
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/tilia/xml.rb ADDED
@@ -0,0 +1,23 @@
1
+ # Namespace for Tilia library
2
+ module Tilia
3
+ # Load active support core extensions
4
+ require 'active_support'
5
+ require 'active_support/core_ext'
6
+
7
+ # Tilia libraries
8
+ require 'tilia/uri'
9
+
10
+ # Namespace of the Tilia::Xml library
11
+ module Xml
12
+ require 'tilia/xml/xml_deserializable'
13
+ require 'tilia/xml/xml_serializable'
14
+ require 'tilia/xml/context_stack_trait'
15
+ require 'tilia/xml/element'
16
+ require 'tilia/xml/parse_exception'
17
+ require 'tilia/xml/lib_xml_exception'
18
+ require 'tilia/xml/reader'
19
+ require 'tilia/xml/service'
20
+ require 'tilia/xml/version'
21
+ require 'tilia/xml/writer'
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ require 'simplecov'
2
+ require 'minitest/autorun'
3
+
4
+ require 'tilia/xml'
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ module Tilia
4
+ module Xml
5
+ class ContextStackTest < Minitest::Test
6
+ def setup
7
+ @stack = ContextStackMock.new
8
+ end
9
+
10
+ def test_push_and_pull
11
+ @stack.context_uri = '/foo/bar'
12
+ @stack.element_map['{DAV:}foo'] = 'Bar'
13
+ @stack.namespace_map['DAV:'] = 'd'
14
+
15
+ @stack.push_context
16
+
17
+ assert_equal('/foo/bar', @stack.context_uri)
18
+ assert_equal('Bar', @stack.element_map['{DAV:}foo'])
19
+ assert_equal('d', @stack.namespace_map['DAV:'])
20
+
21
+ @stack.context_uri = '/gir/zim'
22
+ @stack.element_map['{DAV:}foo'] = 'newBar'
23
+ @stack.namespace_map['DAV:'] = 'dd'
24
+
25
+ @stack.pop_context
26
+
27
+ assert_equal('/foo/bar', @stack.context_uri)
28
+ assert_equal('Bar', @stack.element_map['{DAV:}foo'])
29
+ assert_equal('d', @stack.namespace_map['DAV:'])
30
+ end
31
+ end
32
+
33
+ class ContextStackMock
34
+ include ContextStackTrait
35
+ def initialize
36
+ initialize_context_stack_attributes
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ module Tilia
4
+ module Xml
5
+ class CDataTest < Minitest::Test
6
+ def test_deserialize
7
+ input = <<BLA
8
+ <?xml version="1.0"?>
9
+ <root xmlns="http://sabredav.org/ns">
10
+ <blabla />
11
+ </root>
12
+ BLA
13
+ reader = Reader.new
14
+ reader.element_map = { '{http://sabredav.org/ns}blabla' => Element::Cdata }
15
+ reader.xml(input)
16
+ assert_raises(RuntimeError) { reader.parse }
17
+ end
18
+
19
+ def test_serialize
20
+ writer = Writer.new
21
+ writer.namespace_map = { 'http://sabredav.org/ns' => nil }
22
+ writer.open_memory
23
+ writer.start_document
24
+ writer.set_indent(true)
25
+ writer.write('{http://sabredav.org/ns}root' => Element::Cdata.new('<foo&bar>'))
26
+ output = writer.output_memory
27
+
28
+ expected = <<XML
29
+ <?xml version="1.0"?>
30
+ <root xmlns="http://sabredav.org/ns"><![CDATA[<foo&bar>]]></root>
31
+ XML
32
+
33
+ assert_equal(expected, output)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ # The intention for this reader class, is to read past the end element. This
2
+ # should trigger a ParseException
3
+ module Tilia
4
+ module Xml
5
+ module Element
6
+ class Eater
7
+ include Element
8
+
9
+ # The serialize method is called during xml writing.
10
+ #
11
+ # It should use the writer argument to encode this object into Xml.
12
+ #
13
+ # Important note: it is not needed to create the parent element. The
14
+ # parent element is already created, and we only have to worry about
15
+ # attributes, child elements and text (if any).
16
+ #
17
+ # Important note 2: If you are writing any new elements, you are also
18
+ # responsible for closing them.
19
+ #
20
+ # @param [Writer] writer
21
+ # @return [void]
22
+ def xml_serialize(writer)
23
+ writer.start_element('{http://sabredav.org/ns}elem1')
24
+ writer.write_string('hiiii!')
25
+ writer.end_element
26
+ end
27
+
28
+ # The deserialize method is called during xml parsing.
29
+ #
30
+ # This method is called statictly, this is because in theory this method
31
+ # may be used as a type of constructor, or factory method.
32
+ #
33
+ # Often you want to return an instance of the current class, but you are
34
+ # free to return other data as well.
35
+ #
36
+ # Important note 2: You are responsible for advancing the reader to the
37
+ # next element. Not doing anything will result in a never-ending loop.
38
+ #
39
+ # If you just want to skip parsing for this element altogether, you can
40
+ # just call reader->next();
41
+ #
42
+ # reader->parseSubTree() will parse the entire sub-tree, and advance to
43
+ # the next element.
44
+ #
45
+ # @param [Reader] reader
46
+ # @return mixed
47
+ def self.xml_deserialize(reader)
48
+ reader.next
49
+
50
+ count = 1
51
+ while count > 0
52
+ reader.read
53
+ count -= 1 if reader.node_type == ::LibXML::XML::Reader::TYPE_END_ELEMENT
54
+ end
55
+ reader.read
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,113 @@
1
+ require 'test_helper'
2
+
3
+ module Tilia
4
+ module Xml
5
+ class ElementsTest < Minitest::Test
6
+ def test_deserialize
7
+ input = <<BLA
8
+ <?xml version="1.0"?>
9
+ <root xmlns="http://sabredav.org/ns">
10
+ <listThingy>
11
+ <elem1 />
12
+ <elem2 />
13
+ <elem3 />
14
+ <elem4 attr="val" />
15
+ <elem5>content</elem5>
16
+ <elem6><subnode /></elem6>
17
+ </listThingy>
18
+ <listThingy />
19
+ <otherThing>
20
+ <elem1 />
21
+ <elem2 />
22
+ <elem3 />
23
+ </otherThing>
24
+ </root>
25
+ BLA
26
+ reader = Reader.new
27
+ reader.element_map = { '{http://sabredav.org/ns}listThingy' => Element::Elements }
28
+ reader.xml(input)
29
+
30
+ output = reader.parse
31
+
32
+ expected = {
33
+ 'name' => '{http://sabredav.org/ns}root',
34
+ 'value' => [
35
+ {
36
+ 'name' => '{http://sabredav.org/ns}listThingy',
37
+ 'value' => [
38
+ '{http://sabredav.org/ns}elem1',
39
+ '{http://sabredav.org/ns}elem2',
40
+ '{http://sabredav.org/ns}elem3',
41
+ '{http://sabredav.org/ns}elem4',
42
+ '{http://sabredav.org/ns}elem5',
43
+ '{http://sabredav.org/ns}elem6'
44
+ ],
45
+ 'attributes' => {}
46
+ },
47
+ {
48
+ 'name' => '{http://sabredav.org/ns}listThingy',
49
+ 'value' => [],
50
+ 'attributes' => {}
51
+ },
52
+ {
53
+ 'name' => '{http://sabredav.org/ns}otherThing',
54
+ 'value' => [
55
+ {
56
+ 'name' => '{http://sabredav.org/ns}elem1',
57
+ 'value' => nil,
58
+ 'attributes' => {}
59
+ },
60
+ {
61
+ 'name' => '{http://sabredav.org/ns}elem2',
62
+ 'value' => nil,
63
+ 'attributes' => {}
64
+ },
65
+ {
66
+ 'name' => '{http://sabredav.org/ns}elem3',
67
+ 'value' => nil,
68
+ 'attributes' => {}
69
+ }
70
+ ],
71
+ 'attributes' => {}
72
+ }
73
+ ],
74
+ 'attributes' => {}
75
+ }
76
+
77
+ assert_equal(expected, output)
78
+ end
79
+
80
+ def test_serialize
81
+ value = [
82
+ '{http://sabredav.org/ns}elem1',
83
+ '{http://sabredav.org/ns}elem2',
84
+ '{http://sabredav.org/ns}elem3',
85
+ '{http://sabredav.org/ns}elem4',
86
+ '{http://sabredav.org/ns}elem5',
87
+ '{http://sabredav.org/ns}elem6'
88
+ ]
89
+
90
+ writer = Writer.new
91
+ writer.namespace_map = { 'http://sabredav.org/ns' => nil }
92
+ writer.open_memory
93
+ writer.start_document
94
+ writer.set_indent(true)
95
+ writer.write('{http://sabredav.org/ns}root' => Element::Elements.new(value))
96
+ output = writer.output_memory
97
+
98
+ expected = <<XML
99
+ <?xml version="1.0"?>
100
+ <root xmlns="http://sabredav.org/ns">
101
+ <elem1/>
102
+ <elem2/>
103
+ <elem3/>
104
+ <elem4/>
105
+ <elem5/>
106
+ <elem6/>
107
+ </root>
108
+ XML
109
+ assert_equal(expected, output)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,187 @@
1
+ require 'test_helper'
2
+
3
+ module Tilia
4
+ module Xml
5
+ class KeyValueTest < Minitest::Test
6
+ def test_deserialize
7
+ input = <<BLA
8
+ <?xml version="1.0"?>
9
+ <root xmlns="http://sabredav.org/ns">
10
+ <struct>
11
+ <elem1 />
12
+ <elem2>hi</elem2>
13
+ <elem3>
14
+ <elem4>foo</elem4>
15
+ <elem5>foo &amp; bar</elem5>
16
+ </elem3>
17
+ <elem6>Hi<!-- ignore me -->there</elem6>
18
+ </struct>
19
+ <struct />
20
+ <otherThing>
21
+ <elem1 />
22
+ </otherThing>
23
+ </root>
24
+ BLA
25
+
26
+ reader = Tilia::Xml::Reader.new
27
+ reader.element_map = { '{http://sabredav.org/ns}struct' => Tilia::Xml::Element::KeyValue }
28
+ reader.xml(input)
29
+
30
+ output = reader.parse
31
+
32
+ expected = {
33
+ 'name' => '{http://sabredav.org/ns}root',
34
+ 'value' => [
35
+ {
36
+ 'name' => '{http://sabredav.org/ns}struct',
37
+ 'value' => {
38
+ '{http://sabredav.org/ns}elem1' => nil,
39
+ '{http://sabredav.org/ns}elem2' => 'hi',
40
+ '{http://sabredav.org/ns}elem3' => [
41
+ {
42
+ 'name' => '{http://sabredav.org/ns}elem4',
43
+ 'value' => 'foo',
44
+ 'attributes' => {}
45
+ },
46
+ {
47
+ 'name' => '{http://sabredav.org/ns}elem5',
48
+ 'value' => 'foo & bar',
49
+ 'attributes' => {}
50
+ }
51
+ ],
52
+ '{http://sabredav.org/ns}elem6' => 'Hithere'
53
+ },
54
+ 'attributes' => {}
55
+ },
56
+ {
57
+ 'name' => '{http://sabredav.org/ns}struct',
58
+ 'value' => {},
59
+ 'attributes' => {}
60
+ },
61
+ {
62
+ 'name' => '{http://sabredav.org/ns}otherThing',
63
+ 'value' => [
64
+ {
65
+ 'name' => '{http://sabredav.org/ns}elem1',
66
+ 'value' => nil,
67
+ 'attributes' => {}
68
+ }
69
+ ],
70
+ 'attributes' => {}
71
+ }
72
+ ],
73
+ 'attributes' => {}
74
+ }
75
+
76
+ assert_equal(expected, output)
77
+ end
78
+
79
+ # This test was added to find out why an element gets eaten by the
80
+ # SabreDAV MKCOL parser.
81
+ def test_element_eater
82
+ input = <<BLA
83
+ <?xml version="1.0"?>
84
+ <mkcol xmlns="DAV:">
85
+ <set>
86
+ <prop>
87
+ <resourcetype><collection /></resourcetype>
88
+ <displayname>bla</displayname>
89
+ </prop>
90
+ </set>
91
+ </mkcol>
92
+ BLA
93
+
94
+ reader = Tilia::Xml::Reader.new
95
+ reader.element_map = {
96
+ '{DAV:}set' => Tilia::Xml::Element::KeyValue,
97
+ '{DAV:}prop' => Tilia::Xml::Element::KeyValue,
98
+ '{DAV:}resourcetype' => Tilia::Xml::Element::Elements
99
+ }
100
+ reader.xml(input)
101
+
102
+ expected = {
103
+ 'name' => '{DAV:}mkcol',
104
+ 'value' => [
105
+ {
106
+ 'name' => '{DAV:}set',
107
+ 'value' => {
108
+ '{DAV:}prop' => {
109
+ '{DAV:}resourcetype' => [
110
+ '{DAV:}collection'
111
+ ],
112
+ '{DAV:}displayname' => 'bla'
113
+ }
114
+ },
115
+ 'attributes' => {}
116
+ }
117
+ ],
118
+ 'attributes' => {}
119
+ }
120
+
121
+ assert_equal(expected, reader.parse)
122
+ end
123
+
124
+ def test_serialize
125
+ value = {
126
+ '{http://sabredav.org/ns}elem1' => nil,
127
+ '{http://sabredav.org/ns}elem2' => 'textValue',
128
+ '{http://sabredav.org/ns}elem3' => {
129
+ '{http://sabredav.org/ns}elem4' => 'text2',
130
+ '{http://sabredav.org/ns}elem5' => nil
131
+ },
132
+ '{http://sabredav.org/ns}elem6' => 'text3'
133
+ }
134
+
135
+ writer = Tilia::Xml::Writer.new
136
+ writer.namespace_map = { 'http://sabredav.org/ns' => nil }
137
+ writer.open_memory
138
+ writer.start_document
139
+ writer.set_indent(true)
140
+ writer.write('{http://sabredav.org/ns}root' => Tilia::Xml::Element::KeyValue.new(value))
141
+ output = writer.output_memory
142
+
143
+ expected = <<XML
144
+ <?xml version="1.0"?>
145
+ <root xmlns="http://sabredav.org/ns">
146
+ <elem1/>
147
+ <elem2>textValue</elem2>
148
+ <elem3>
149
+ <elem4>text2</elem4>
150
+ <elem5/>
151
+ </elem3>
152
+ <elem6>text3</elem6>
153
+ </root>
154
+ XML
155
+ assert_equal(expected, output)
156
+ end
157
+
158
+ # I discovered that when there's no whitespace between elements, elements
159
+ # can get skipped.
160
+ def test_element_skip_problem
161
+ input = <<BLA
162
+ <?xml version="1.0" encoding="utf-8"?>
163
+ <root xmlns="http://sabredav.org/ns">
164
+ <elem3>val3</elem3><elem4>val4</elem4><elem5>val5</elem5></root>
165
+ BLA
166
+
167
+ reader = Tilia::Xml::Reader.new
168
+ reader.element_map = { '{http://sabredav.org/ns}root' => Tilia::Xml::Element::KeyValue }
169
+ reader.xml(input)
170
+
171
+ output = reader.parse
172
+
173
+ expected = {
174
+ 'name' => '{http://sabredav.org/ns}root',
175
+ 'value' => {
176
+ '{http://sabredav.org/ns}elem3' => 'val3',
177
+ '{http://sabredav.org/ns}elem4' => 'val4',
178
+ '{http://sabredav.org/ns}elem5' => 'val5'
179
+ },
180
+ 'attributes' => {}
181
+ }
182
+
183
+ assert_equal(expected, output)
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,52 @@
1
+ module Tilia
2
+ module Xml
3
+ module Element
4
+ class Mock
5
+ include Element
6
+
7
+ # The serialize method is called during xml writing.
8
+ #
9
+ # It should use the writer argument to encode this object into XML.
10
+ #
11
+ # Important note: it is not needed to create the parent element. The
12
+ # parent element is already created, and we only have to worry about
13
+ # attributes, child elements and text (if any).
14
+ #
15
+ # Important note 2: If you are writing any new elements, you are also
16
+ # responsible for closing them.
17
+ #
18
+ # @param [Writer] writer
19
+ # @return [void]
20
+ def xml_serialize(writer)
21
+ writer.start_element('{http://sabredav.org/ns}elem1')
22
+ writer.write_string('hiiii!')
23
+ writer.end_element
24
+ end
25
+
26
+ # The deserialize method is called during xml parsing.
27
+ #
28
+ # This method is called statictly, this is because in theory this method
29
+ # may be used as a type of constructor, or factory method.
30
+ #
31
+ # Often you want to return an instance of the current class, but you are
32
+ # free to return other data as well.
33
+ #
34
+ # Important note 2: You are responsible for advancing the reader to the
35
+ # next element. Not doing anything will result in a never-ending loop.
36
+ #
37
+ # If you just want to skip parsing for this element altogether, you can
38
+ # just call reader->next();
39
+ #
40
+ # reader->parseSubTree() will parse the entire sub-tree, and advance to
41
+ # the next element.
42
+ #
43
+ # @param [Reader] reader
44
+ # @return mixed
45
+ def self.xml_deserialize(reader)
46
+ reader.next
47
+ 'foobar'
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+
3
+ module Tilia
4
+ module Xml
5
+ class UriTest < Minitest::Test
6
+ def test_deserialize
7
+ input = <<BLA
8
+ <?xml version="1.0"?>
9
+ <root xmlns="http://sabredav.org/ns">
10
+ <uri>/foo/bar</uri>
11
+ </root>
12
+ BLA
13
+ reader = Tilia::Xml::Reader.new
14
+ reader.context_uri = 'http://example.org/'
15
+ reader.element_map = { '{http://sabredav.org/ns}uri' => Tilia::Xml::Element::Uri }
16
+ reader.xml(input)
17
+ output = reader.parse
18
+
19
+ expected = {
20
+ 'name' => '{http://sabredav.org/ns}root',
21
+ 'value' => [
22
+ {
23
+ 'name' => '{http://sabredav.org/ns}uri',
24
+ 'value' => Tilia::Xml::Element::Uri.new('http://example.org/foo/bar'),
25
+ 'attributes' => {}
26
+ }
27
+ ],
28
+ 'attributes' => {}
29
+ }
30
+
31
+ assert_equal(expected, output)
32
+ end
33
+
34
+ def test_serialize
35
+ writer = Tilia::Xml::Writer.new
36
+ writer.namespace_map = { 'http://sabredav.org/ns' => nil }
37
+ writer.open_memory
38
+ writer.start_document
39
+ writer.set_indent(true)
40
+ writer.context_uri = 'http://example.org/'
41
+ writer.write('{http://sabredav.org/ns}root' => { '{http://sabredav.org/ns}uri' => Tilia::Xml::Element::Uri.new('/foo/bar') })
42
+ output = writer.output_memory
43
+
44
+ expected = <<XML
45
+ <?xml version="1.0"?>
46
+ <root xmlns="http://sabredav.org/ns">
47
+ <uri>http://example.org/foo/bar</uri>
48
+ </root>
49
+ XML
50
+
51
+ assert_equal(expected, output)
52
+ end
53
+ end
54
+ end
55
+ end