shale 0.4.0 → 0.5.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +93 -7
  4. data/exe/shaleb +30 -6
  5. data/lib/shale/adapter/nokogiri/document.rb +87 -0
  6. data/lib/shale/adapter/nokogiri/node.rb +100 -0
  7. data/lib/shale/adapter/nokogiri.rb +11 -151
  8. data/lib/shale/adapter/ox/document.rb +80 -0
  9. data/lib/shale/adapter/ox/node.rb +88 -0
  10. data/lib/shale/adapter/ox.rb +9 -134
  11. data/lib/shale/adapter/rexml/document.rb +88 -0
  12. data/lib/shale/adapter/rexml/node.rb +99 -0
  13. data/lib/shale/adapter/rexml.rb +9 -150
  14. data/lib/shale/error.rb +35 -2
  15. data/lib/shale/mapper.rb +2 -2
  16. data/lib/shale/schema/{json_compiler → compiler}/boolean.rb +1 -1
  17. data/lib/shale/schema/{json_compiler/object.rb → compiler/complex.rb} +11 -8
  18. data/lib/shale/schema/{json_compiler → compiler}/date.rb +1 -1
  19. data/lib/shale/schema/{json_compiler → compiler}/float.rb +1 -1
  20. data/lib/shale/schema/{json_compiler → compiler}/integer.rb +1 -1
  21. data/lib/shale/schema/{json_compiler → compiler}/property.rb +6 -6
  22. data/lib/shale/schema/{json_compiler → compiler}/string.rb +1 -1
  23. data/lib/shale/schema/{json_compiler → compiler}/time.rb +1 -1
  24. data/lib/shale/schema/compiler/value.rb +21 -0
  25. data/lib/shale/schema/compiler/xml_complex.rb +50 -0
  26. data/lib/shale/schema/compiler/xml_property.rb +73 -0
  27. data/lib/shale/schema/json_compiler.rb +32 -34
  28. data/lib/shale/schema/json_generator.rb +3 -3
  29. data/lib/shale/schema/xml_compiler.rb +919 -0
  30. data/lib/shale/schema/xml_generator.rb +7 -7
  31. data/lib/shale/schema.rb +16 -0
  32. data/lib/shale/type/{composite.rb → complex.rb} +20 -2
  33. data/lib/shale/utils.rb +42 -7
  34. data/lib/shale/version.rb +1 -1
  35. data/lib/shale.rb +8 -19
  36. data/shale.gemspec +1 -1
  37. metadata +23 -15
  38. data/lib/shale/schema/json_compiler/utils.rb +0 -52
  39. data/lib/shale/schema/json_compiler/value.rb +0 -13
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Adapter
5
+ module Ox
6
+ # Wrapper around Ox::Element API
7
+ #
8
+ # @api private
9
+ class Node
10
+ # Initialize object with Ox element
11
+ #
12
+ # @param [::Ox::Element] node Ox element
13
+ #
14
+ # @api private
15
+ def initialize(node)
16
+ @node = node
17
+ end
18
+
19
+ # Return namespaces defined on document (Not supported by Ox)
20
+ #
21
+ # @return [Hash<String, String>]
22
+ #
23
+ # @example
24
+ # node.namespaces # => {}
25
+ #
26
+ # @api private
27
+ def namespaces
28
+ {}
29
+ end
30
+
31
+ # Return name of the node in the format of
32
+ # prefix:name when the node is namespaced or just name when it's not
33
+ #
34
+ # @return [String]
35
+ #
36
+ # @example without namespace
37
+ # node.name # => Bar
38
+ #
39
+ # @example with namespace
40
+ # node.name # => foo:Bar
41
+ #
42
+ # @api private
43
+ def name
44
+ @node.name
45
+ end
46
+
47
+ # Return all attributes associated with the node
48
+ #
49
+ # @return [Hash]
50
+ #
51
+ # @api private
52
+ def attributes
53
+ @node.attributes
54
+ end
55
+
56
+ # Return node's parent (Not supported by OX)
57
+ #
58
+ # @return [nil]
59
+ #
60
+ # @api private
61
+ def parent
62
+ # :noop:
63
+ end
64
+
65
+ # Return node's element children
66
+ #
67
+ # @return [Array<Shale::Adapter::Ox::Node>]
68
+ #
69
+ # @api private
70
+ def children
71
+ @node
72
+ .nodes
73
+ .filter { |e| e.is_a?(::Ox::Element) }
74
+ .map { |e| self.class.new(e) }
75
+ end
76
+
77
+ # Return first text child of a node
78
+ #
79
+ # @return [String]
80
+ #
81
+ # @api private
82
+ def text
83
+ @node.text
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -2,6 +2,10 @@
2
2
 
3
3
  require 'ox'
4
4
 
5
+ require_relative '../error'
6
+ require_relative 'ox/document'
7
+ require_relative 'ox/node'
8
+
5
9
  module Shale
6
10
  module Adapter
7
11
  # Ox adapter
@@ -12,11 +16,15 @@ module Shale
12
16
  #
13
17
  # @param [String] xml XML document
14
18
  #
15
- # @return [::Ox::Document, ::Ox::Element]
19
+ # @raise [ParseError] when XML document has errors
20
+ #
21
+ # @return [Shale::Adapter::Ox::Node]
16
22
  #
17
23
  # @api private
18
24
  def self.load(xml)
19
25
  Node.new(::Ox.parse(xml))
26
+ rescue ::Ox::ParseError => e
27
+ raise ParseError, "Document is invalid: #{e.message}"
20
28
  end
21
29
 
22
30
  # Serialize Ox document into XML
@@ -48,139 +56,6 @@ module Shale
48
56
  def self.create_document
49
57
  Document.new
50
58
  end
51
-
52
- # Wrapper around Ox API
53
- #
54
- # @api private
55
- class Document
56
- # Return Ox document
57
- #
58
- # @return [::Ox::Document]
59
- #
60
- # @api private
61
- attr_reader :doc
62
-
63
- # Initialize object
64
- #
65
- # @api private
66
- def initialize
67
- @doc = ::Ox::Document.new
68
- end
69
-
70
- # Create Ox element
71
- #
72
- # @param [String] name Name of the XML element
73
- #
74
- # @return [::Ox::Element]
75
- #
76
- # @api private
77
- def create_element(name)
78
- ::Ox::Element.new(name)
79
- end
80
-
81
- # Add XML namespace to document
82
- #
83
- # Ox doesn't support XML namespaces so this method does nothing.
84
- #
85
- # @param [String] prefix
86
- # @param [String] namespace
87
- #
88
- # @api private
89
- def add_namespace(prefix, namespace)
90
- # :noop:
91
- end
92
-
93
- # Add attribute to Ox element
94
- #
95
- # @param [::Ox::Element] element Ox element
96
- # @param [String] name Name of the XML attribute
97
- # @param [String] value Value of the XML attribute
98
- #
99
- # @api private
100
- def add_attribute(element, name, value)
101
- element[name] = value
102
- end
103
-
104
- # Add child element to Ox element
105
- #
106
- # @param [::Ox::Element] element Ox parent element
107
- # @param [::Ox::Element] child Ox child element
108
- #
109
- # @api private
110
- def add_element(element, child)
111
- element << child
112
- end
113
-
114
- # Add text node to Ox element
115
- #
116
- # @param [::Ox::Element] element Ox element
117
- # @param [String] text Text to add
118
- #
119
- # @api private
120
- def add_text(element, text)
121
- element << text
122
- end
123
- end
124
-
125
- # Wrapper around Ox::Element API
126
- #
127
- # @api private
128
- class Node
129
- # Initialize object with Ox element
130
- #
131
- # @param [::Ox::Element] node Ox element
132
- #
133
- # @api private
134
- def initialize(node)
135
- @node = node
136
- end
137
-
138
- # Return name of the node in the format of
139
- # prefix:name when the node is namespaced or just name when it's not
140
- #
141
- # @return [String]
142
- #
143
- # @example without namespace
144
- # node.name # => Bar
145
- #
146
- # @example with namespace
147
- # node.name # => foo:Bar
148
- #
149
- # @api private
150
- def name
151
- @node.name
152
- end
153
-
154
- # Return all attributes associated with the node
155
- #
156
- # @return [Hash]
157
- #
158
- # @api private
159
- def attributes
160
- @node.attributes
161
- end
162
-
163
- # Return node's element children
164
- #
165
- # @return [Array<Shale::Adapter::Ox::Node>]
166
- #
167
- # @api private
168
- def children
169
- @node
170
- .nodes
171
- .filter { |e| e.is_a?(::Ox::Element) }
172
- .map { |e| self.class.new(e) }
173
- end
174
-
175
- # Return first text child of a node
176
- #
177
- # @return [String]
178
- #
179
- # @api private
180
- def text
181
- @node.text
182
- end
183
- end
184
59
  end
185
60
  end
186
61
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Adapter
5
+ module REXML
6
+ # Wrapper around REXML API
7
+ #
8
+ # @api private
9
+ class Document
10
+ # Initialize object
11
+ #
12
+ # @api private
13
+ def initialize
14
+ context = { attribute_quote: :quote, prologue_quote: :quote }
15
+ @doc = ::REXML::Document.new(nil, context)
16
+ @namespaces = {}
17
+ end
18
+
19
+ # Return REXML document
20
+ #
21
+ # @return [::REXML::Document]
22
+ #
23
+ # @api private
24
+ def doc
25
+ if @doc.root
26
+ @namespaces.each do |prefix, namespace|
27
+ @doc.root.add_namespace(prefix, namespace)
28
+ end
29
+ end
30
+
31
+ @doc
32
+ end
33
+
34
+ # Create REXML element
35
+ #
36
+ # @param [String] name Name of the XML element
37
+ #
38
+ # @return [::REXML::Element]
39
+ #
40
+ # @api private
41
+ def create_element(name)
42
+ ::REXML::Element.new(name, nil, attribute_quote: :quote)
43
+ end
44
+
45
+ # Add XML namespace to document
46
+ #
47
+ # @param [String] prefix
48
+ # @param [String] namespace
49
+ #
50
+ # @api private
51
+ def add_namespace(prefix, namespace)
52
+ @namespaces[prefix] = namespace if prefix && namespace
53
+ end
54
+
55
+ # Add attribute to REXML element
56
+ #
57
+ # @param [::REXML::Element] element REXML element
58
+ # @param [String] name Name of the XML attribute
59
+ # @param [String] value Value of the XML attribute
60
+ #
61
+ # @api private
62
+ def add_attribute(element, name, value)
63
+ element.add_attribute(name, value)
64
+ end
65
+
66
+ # Add child element to REXML element
67
+ #
68
+ # @param [::REXML::Element] element REXML parent element
69
+ # @param [::REXML::Element] child REXML child element
70
+ #
71
+ # @api private
72
+ def add_element(element, child)
73
+ element.add_element(child)
74
+ end
75
+
76
+ # Add text node to REXML element
77
+ #
78
+ # @param [::REXML::Element] element REXML element
79
+ # @param [String] text Text to add
80
+ #
81
+ # @api private
82
+ def add_text(element, text)
83
+ element.add_text(text)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../utils'
4
+
5
+ module Shale
6
+ module Adapter
7
+ module REXML
8
+ # Wrapper around REXML::Element API
9
+ #
10
+ # @api private
11
+ class Node
12
+ # Initialize object with REXML element
13
+ #
14
+ # @param [::REXML::Element] node REXML element
15
+ #
16
+ # @api private
17
+ def initialize(node)
18
+ @node = node
19
+ end
20
+
21
+ # Return namespaces defined on document
22
+ #
23
+ # @return [Hash<String, String>]
24
+ #
25
+ # @example
26
+ # node.namespaces # => { 'foo' => 'http://foo.com', 'bar' => 'http://bar.com' }
27
+ #
28
+ # @api private
29
+ def namespaces
30
+ @node.namespaces
31
+ end
32
+
33
+ # Return name of the node in the format of
34
+ # namespace:name when the node is namespaced or just name when it's not
35
+ #
36
+ # @return [String]
37
+ #
38
+ # @example without namespace
39
+ # node.name # => Bar
40
+ #
41
+ # @example with namespace
42
+ # node.name # => http://foo:Bar
43
+ #
44
+ # @api private
45
+ def name
46
+ [Utils.presence(@node.namespace), @node.name].compact.join(':')
47
+ end
48
+
49
+ # Return all attributes associated with the node
50
+ #
51
+ # @return [Hash]
52
+ #
53
+ # @api private
54
+ def attributes
55
+ attributes = @node.attributes.values.map do |attribute|
56
+ attribute.is_a?(Hash) ? attribute.values : attribute
57
+ end.flatten
58
+
59
+ attributes.each_with_object({}) do |attribute, hash|
60
+ name = [Utils.presence(attribute.namespace), attribute.name].compact.join(':')
61
+ hash[name] = attribute.value
62
+ end
63
+ end
64
+
65
+ # Return node's parent
66
+ #
67
+ # @return [Shale::Adapter::REXML::Node, nil]
68
+ #
69
+ # @api private
70
+ def parent
71
+ if @node.parent && @node.parent.name != ''
72
+ self.class.new(@node.parent)
73
+ end
74
+ end
75
+
76
+ # Return node's element children
77
+ #
78
+ # @return [Array<Shale::Adapter::REXML::Node>]
79
+ #
80
+ # @api private
81
+ def children
82
+ @node
83
+ .children
84
+ .filter { |e| e.node_type == :element }
85
+ .map { |e| self.class.new(e) }
86
+ end
87
+
88
+ # Return first text child of a node
89
+ #
90
+ # @return [String]
91
+ #
92
+ # @api private
93
+ def text
94
+ @node.text
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rexml/document'
4
- require_relative '../utils'
4
+
5
+ require_relative '../error'
6
+ require_relative 'rexml/document'
7
+ require_relative 'rexml/node'
5
8
 
6
9
  module Shale
7
10
  module Adapter
@@ -13,12 +16,16 @@ module Shale
13
16
  #
14
17
  # @param [String] xml XML document
15
18
  #
16
- # @return [::REXML::Document]
19
+ # @raise [ParseError] when XML document has errors
20
+ #
21
+ # @return [Shale::Adapter::REXML::Node]
17
22
  #
18
23
  # @api private
19
24
  def self.load(xml)
20
25
  doc = ::REXML::Document.new(xml, ignore_whitespace_nodes: :all)
21
26
  Node.new(doc.root)
27
+ rescue ::REXML::ParseException => e
28
+ raise ParseError, "Document is invalid: #{e.message}"
22
29
  end
23
30
 
24
31
  # Serialize REXML document into XML
@@ -53,154 +60,6 @@ module Shale
53
60
  def self.create_document
54
61
  Document.new
55
62
  end
56
-
57
- # Wrapper around REXML API
58
- #
59
- # @api private
60
- class Document
61
- # Initialize object
62
- #
63
- # @api private
64
- def initialize
65
- context = { attribute_quote: :quote, prologue_quote: :quote }
66
- @doc = ::REXML::Document.new(nil, context)
67
- @namespaces = {}
68
- end
69
-
70
- # Return REXML document
71
- #
72
- # @return [::REXML::Document]
73
- #
74
- # @api private
75
- def doc
76
- if @doc.root
77
- @namespaces.each do |prefix, namespace|
78
- @doc.root.add_namespace(prefix, namespace)
79
- end
80
- end
81
-
82
- @doc
83
- end
84
-
85
- # Create REXML element
86
- #
87
- # @param [String] name Name of the XML element
88
- #
89
- # @return [::REXML::Element]
90
- #
91
- # @api private
92
- def create_element(name)
93
- ::REXML::Element.new(name, nil, attribute_quote: :quote)
94
- end
95
-
96
- # Add XML namespace to document
97
- #
98
- # @param [String] prefix
99
- # @param [String] namespace
100
- #
101
- # @api private
102
- def add_namespace(prefix, namespace)
103
- @namespaces[prefix] = namespace if prefix && namespace
104
- end
105
-
106
- # Add attribute to REXML element
107
- #
108
- # @param [::REXML::Element] element REXML element
109
- # @param [String] name Name of the XML attribute
110
- # @param [String] value Value of the XML attribute
111
- #
112
- # @api private
113
- def add_attribute(element, name, value)
114
- element.add_attribute(name, value)
115
- end
116
-
117
- # Add child element to REXML element
118
- #
119
- # @param [::REXML::Element] element REXML parent element
120
- # @param [::REXML::Element] child REXML child element
121
- #
122
- # @api private
123
- def add_element(element, child)
124
- element.add_element(child)
125
- end
126
-
127
- # Add text node to REXML element
128
- #
129
- # @param [::REXML::Element] element REXML element
130
- # @param [String] text Text to add
131
- #
132
- # @api private
133
- def add_text(element, text)
134
- element.add_text(text)
135
- end
136
- end
137
-
138
- # Wrapper around REXML::Element API
139
- #
140
- # @api private
141
- class Node
142
- # Initialize object with REXML element
143
- #
144
- # @param [::REXML::Element] node REXML element
145
- #
146
- # @api private
147
- def initialize(node)
148
- @node = node
149
- end
150
-
151
- # Return name of the node in the format of
152
- # namespace:name when the node is namespaced or just name when it's not
153
- #
154
- # @return [String]
155
- #
156
- # @example without namespace
157
- # node.name # => Bar
158
- #
159
- # @example with namespace
160
- # node.name # => http://foo:Bar
161
- #
162
- # @api private
163
- def name
164
- [Utils.presence(@node.namespace), @node.name].compact.join(':')
165
- end
166
-
167
- # Return all attributes associated with the node
168
- #
169
- # @return [Hash]
170
- #
171
- # @api private
172
- def attributes
173
- attributes = @node.attributes.values.map do |attribute|
174
- attribute.is_a?(Hash) ? attribute.values : attribute
175
- end.flatten
176
-
177
- attributes.each_with_object({}) do |attribute, hash|
178
- name = [Utils.presence(attribute.namespace), attribute.name].compact.join(':')
179
- hash[name] = attribute.value
180
- end
181
- end
182
-
183
- # Return node's element children
184
- #
185
- # @return [Array<Shale::Adapter::REXML::Node>]
186
- #
187
- # @api private
188
- def children
189
- @node
190
- .children
191
- .filter { |e| e.node_type == :element }
192
- .map { |e| self.class.new(e) }
193
- end
194
-
195
- # Return first text child of a node
196
- #
197
- # @return [String]
198
- #
199
- # @api private
200
- def text
201
- @node.text
202
- end
203
- end
204
63
  end
205
64
  end
206
65
  end
data/lib/shale/error.rb CHANGED
@@ -1,6 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shale
4
+ # Error message displayed when adapter is not set
5
+ # @api private
6
+ ADAPTER_NOT_SET_MESSAGE = <<~MSG
7
+ XML Adapter is not set.
8
+ To use Shale with XML documents you have to install parser and set adapter.
9
+
10
+ To use REXML:
11
+ require 'shale/adapter/rexml'
12
+ Shale.xml_adapter = Shale::Adapter::REXML
13
+
14
+ To use Nokogiri:
15
+ # Make sure Nokogiri is installed eg. execute: gem install nokogiri
16
+ require 'shale/adapter/nokogiri'
17
+ Shale.xml_adapter = Shale::Adapter::Nokogiri
18
+
19
+ To use OX:
20
+ # Make sure Ox is installed eg. execute: gem install ox
21
+ require 'shale/adapter/ox'
22
+ Shale.xml_adapter = Shale::Adapter::Ox
23
+ MSG
24
+
4
25
  # Error for assigning value to not existing attribute
5
26
  #
6
27
  # @api private
@@ -43,9 +64,21 @@ module Shale
43
64
  class NotAShaleMapperError < StandardError
44
65
  end
45
66
 
46
- # JSON Schema compilation error
67
+ # Schema compilation error
68
+ #
69
+ # @api private
70
+ class SchemaError < StandardError
71
+ end
72
+
73
+ # Parsing error
74
+ #
75
+ # @api private
76
+ class ParseError < StandardError
77
+ end
78
+
79
+ # Adapter error
47
80
  #
48
81
  # @api private
49
- class JSONSchemaError < StandardError
82
+ class AdapterError < StandardError
50
83
  end
51
84
  end
data/lib/shale/mapper.rb CHANGED
@@ -5,7 +5,7 @@ require_relative 'error'
5
5
  require_relative 'utils'
6
6
  require_relative 'mapping/dict'
7
7
  require_relative 'mapping/xml'
8
- require_relative 'type/composite'
8
+ require_relative 'type/complex'
9
9
 
10
10
  module Shale
11
11
  # Base class used for mapping
@@ -42,7 +42,7 @@ module Shale
42
42
  # person.to_json
43
43
  #
44
44
  # @api public
45
- class Mapper < Type::Composite
45
+ class Mapper < Type::Complex
46
46
  @attributes = {}
47
47
  @hash_mapping = Mapping::Dict.new
48
48
  @json_mapping = Mapping::Dict.new
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Shale
4
4
  module Schema
5
- class JSONCompiler
5
+ module Compiler
6
6
  # Class that maps Schema type to Shale Boolean type
7
7
  #
8
8
  # @api private