tilia-xml 1.2.0

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