xommelier 0.1.16 → 0.1.18

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.
@@ -1,3 +1,3 @@
1
1
  module Xommelier
2
- VERSION = '0.1.16'
2
+ VERSION = '0.1.18'
3
3
  end
@@ -20,7 +20,7 @@ module Xommelier
20
20
  options[:module] ||= self
21
21
  instance_variable_set(:@_xmlns, Xommelier::Xml::Namespace.new(uri, options, &block))
22
22
  end
23
- instance_variable_get(:@_xmlns)
23
+ instance_variable_get(:@_xmlns) || Xml.xmlns
24
24
  end
25
25
 
26
26
  def schema(schema = nil)
@@ -7,13 +7,6 @@ require 'active_support/core_ext/class/attribute'
7
7
 
8
8
  module Xommelier
9
9
  module Xml
10
- DEFAULT_OPTIONS = {
11
- type: String
12
- }
13
- DEFAULT_ELEMENT_OPTIONS = DEFAULT_OPTIONS.merge(
14
- count: :one
15
- )
16
-
17
10
  class Element
18
11
  include Xommelier::Xml::Element::Structure
19
12
  include Xommelier::Xml::Element::Serialization
@@ -28,13 +21,7 @@ module Xommelier
28
21
  @text = nil
29
22
  @errors = []
30
23
 
31
- self.class.attributes.each do |name, options|
32
- send("#{name}=", options[:default]) if options.key?(:default)
33
- end
34
-
35
- self.class.elements.each do |name, options|
36
- send("#{name}=", options[:default]) if options.key?(:default)
37
- end
24
+ set_default_values
38
25
 
39
26
  case contents
40
27
  when Hash
@@ -68,10 +55,12 @@ module Xommelier
68
55
  def validate
69
56
  @errors = []
70
57
  to_xml unless xml_document
71
- if xmlns.schema
72
- xmlns.schema.validate(xml_document).each do |error|
58
+ if schema
59
+ schema.validate(xml_document).each do |error|
73
60
  @errors << error
74
61
  end
62
+ else
63
+ raise NoSchemaError.new(self)
75
64
  end
76
65
  end
77
66
 
@@ -26,13 +26,13 @@ module Xommelier
26
26
  [ns, element].compact.join(':')
27
27
  end
28
28
 
29
- def element_xpath(xmldoc = nil, name = nil)
30
- ns_element(xmlns_xpath(xmldoc), name || element_name)
29
+ def element_xpath(xml_doc = nil, name = nil)
30
+ ns_element(xmlns_xpath(xml_doc), name || element_name)
31
31
  end
32
32
 
33
- def xmlns_xpath(xml_document = nil)
34
- if xml_document
35
- prefix = xml_document.namespaces.key(xmlns.try(:uri))
33
+ def xmlns_xpath(xml_doc = nil)
34
+ if xml_doc
35
+ prefix = xml_doc.namespaces.key(xmlns.try(:uri))
36
36
  (prefix =~ /:/) ? prefix[6..-1] : prefix
37
37
  else
38
38
  xmlns.as
@@ -41,8 +41,7 @@ module Xommelier
41
41
  end
42
42
 
43
43
  def from_xml(xml, options = {})
44
- case xml
45
- when IO, String
44
+ if IO === xml || String === xml
46
45
  xml = Nokogiri::XML(xml)
47
46
  end
48
47
  @_xml_node = options.delete(:node) { xml.at_xpath(element_xpath(xml.document, element_name)) }
@@ -52,12 +51,12 @@ module Xommelier
52
51
  self.text = @_xml_node.inner_html
53
52
  end
54
53
 
55
- self.class.attributes.each do |name, options|
56
- deserialize_attribute(name, options)
54
+ self.class.attributes.values.each do |attribute|
55
+ deserialize_attribute(attribute)
57
56
  end
58
57
 
59
- self.class.elements.each do |name, options|
60
- deserialize_element(name, options)
58
+ self.class.elements.values.each do |element|
59
+ deserialize_element(element)
61
60
  end
62
61
  end
63
62
  alias_method :from_xommelier, :from_xml
@@ -72,7 +71,7 @@ module Xommelier
72
71
  builder = options.delete(:builder)
73
72
  attribute_values = {}
74
73
  namespaces = builder.doc.namespaces
75
- prefix = options[:prefix] || builder.doc.namespaces.key(xmlns.uri)[6..-1].presence
74
+ prefix = options[:prefix] || namespaces.key(xmlns.uri).try(:[], 6..-1).presence
76
75
  else # Root element
77
76
  builder = Nokogiri::XML::Builder.new(options)
78
77
  attribute_values = children_namespaces.inject({xmlns: xmlns.uri}) do |hash, ns|
@@ -86,25 +85,26 @@ module Xommelier
86
85
  end
87
86
  current_xmlns = builder.doc.namespaces[prefix ? "xmlns:#{prefix}" : 'xmlns']
88
87
  attributes.each do |name, value|
89
- attribute_options = attribute_options(name)
90
- attribute_name = attribute_options[:attribute_name]
91
- ns = attribute_options[:ns]
88
+ attribute = attribute_options(name)
89
+ attribute_name = attribute.attribute_name
90
+ ns = attribute.ns
92
91
  if ns.uri != current_xmlns
93
92
  if ns.as == :xml
94
- attribute_name = "xml:#{attribute_options[:attribute_name]}"
95
- elsif attr_prefix = namespaces.key(ns.uri).try(:[], 6..-1).presence
96
- attribute_name = "#{attr_prefix}:#{attribute_options[:attribute_name]}"
93
+ attribute_name = "xml:#{attribute_name}"
94
+ elsif (attr_prefix = namespaces.key(ns.uri).try(:[], 6..-1).presence)
95
+ attribute_name = "#{attr_prefix}:#{attribute_name}"
97
96
  end
98
97
  end
99
98
  serialize_attribute(attribute_name, value, attribute_values)
100
99
  end
101
100
  @_xml_node = (prefix ? builder[prefix] : builder).
102
101
  send(element_name, attribute_values) do |xml|
103
- self.class.elements.each do |name, element_options|
102
+ self.class.elements.each do |name, element|
104
103
  value = elements.fetch(name, options[:default])
105
104
  if value
106
- serialize_element(name, value, xml,
107
- element_options.merge(overriden_xmlns: xmlns))
105
+ element.override(xmlns: xmlns) do
106
+ serialize_element(name, value, xml, element)
107
+ end
108
108
  end
109
109
  end
110
110
  xml.text(@text) if respond_to?(:text)
@@ -117,7 +117,7 @@ module Xommelier
117
117
  attributes.dup.tap do |hash|
118
118
  @elements.each do |name, value|
119
119
  options = element_options(name)
120
- type = options[:type]
120
+ type = options.type
121
121
  value = Array.wrap(value)
122
122
  if type < Xml::Element
123
123
  value = value.map(&:to_hash)
@@ -168,16 +168,16 @@ module Xommelier
168
168
 
169
169
  delegate :ns_element, to: 'self.class'
170
170
 
171
- def element_xpath(xmldoc = self.xml_document, name = nil)
172
- self.class.element_xpath(xmldoc, name)
171
+ def element_xpath(xml_doc = self.xml_document, name = nil)
172
+ self.class.element_xpath(xml_doc, name)
173
173
  end
174
174
 
175
175
  def children_namespaces(namespaces = Set[xmlns])
176
176
  elements.inject(namespaces) do |result, (name, children)|
177
- element_options = self.class.elements[name]
178
- result << element_options[:ns]
179
- result += attributes.keys.map { |name| attribute_options(name)[:ns] }
180
- if element_options[:type] < Xml::Element
177
+ element = self.class.elements[name]
178
+ result << element.ns
179
+ result += attributes.keys.map { |attr_name| attribute_options(attr_name).ns }
180
+ if element.type < Xml::Element
181
181
  Array(children).each do |child|
182
182
  result += child.children_namespaces
183
183
  end
@@ -198,60 +198,67 @@ module Xommelier
198
198
  attributes[name] = value.to_xommelier
199
199
  end
200
200
 
201
- def deserialize_attribute(name, options = nil)
202
- options ||= self.element_options(name)
203
- ns = options[:ns]
201
+ # @param [Xommelier::Xml::Element::Structure::Attribute] attribute
202
+ def deserialize_attribute(attribute)
203
+ ns = attribute.ns
204
204
  if ns.default? || ns == xmlns
205
- send(name, @_xml_node[options[:attribute_name]])
205
+ send(attribute.writer, @_xml_node[attribute.attribute_name])
206
206
  else
207
- send(name, @_xml_node.attribute_with_ns(options[:attribute_name].to_s, ns.uri.to_s).try(:value))
207
+ send(attribute.writer, @_xml_node.attribute_with_ns(attribute.attribute_name, ns.uri.to_s).try(:value))
208
208
  end
209
209
  end
210
210
 
211
- def deserialize_element(name, options = nil)
212
- options ||= self.element_options(name)
213
- type = options[:type]
214
- nodes = @_xml_node.xpath("./#{ns_element(options[:ns].as, options[:element_name])}", options[:ns].to_hash)
211
+ # @param [Xommelier::Xml::Element::Structure::Element] element
212
+ def deserialize_element(element)
213
+ nodes = @_xml_node.xpath("./#{ns_element(element.ns.as, element.element_name)}", element.ns.to_hash)
215
214
  if nodes.any?
216
- case options[:count]
217
- when :any, :many
218
- children = nodes.map { |node| typecast_element(type, node, options) }
219
- send options[:plural], children
215
+ if element.multiple?
216
+ children = nodes.map { |node| typecast_element(node, element) }
217
+ send(element.plural_writer, children)
220
218
  else
221
- send(name, typecast_element(type, nodes[0], options))
219
+ send(element.writer, typecast_element(nodes[0], element))
222
220
  end
223
221
  end
224
222
  end
225
223
 
226
- def typecast_element(type, node, options)
227
- if type < Xml::Element
228
- type.from_xommelier(xml_document, options.merge(node: node))
224
+ # @param [Nokogiri::XML::Node] node
225
+ # @param [Xommelier::Xml::Element::Structure::Element] options
226
+ def typecast_element(node, options)
227
+ if options.type < Xml::Element
228
+ options.type.from_xommelier(xml_document, node: node)
229
229
  else
230
- type.from_xommelier(node.text)
230
+ options.type.from_xommelier(node.text)
231
231
  end
232
232
  end
233
233
 
234
- def serialize_element(name, value, xml, options = {})
235
- case options[:count]
236
- when :any, :many
237
- single_element = options.merge(count: :one)
238
- value.each { |item| serialize_element(name, item, xml, single_element) }
234
+ # @param [Object] name
235
+ # @param [Object] value
236
+ # @param [Object] xml
237
+ # @param [Xommelier::Xml::Element::Structure::Element] element
238
+ def serialize_element(name, value, xml, element)
239
+ if element.multiple?
240
+ element.override(multiple: false) do
241
+ value.each do |item|
242
+ serialize_element(name, item, xml, element)
243
+ end
244
+ end
239
245
  else
240
- xmlns = options[:overriden_xmlns] || self.xmlns
241
- prefix = if options[:prefix]
242
- options[:prefix]
243
- elsif options[:ns].try(:!=, xmlns)
244
- xml.doc.namespaces.key(options[:ns].uri)[6..-1].presence
245
- else
246
- nil
246
+ xmlns = element.overridden_xmlns || self.xmlns
247
+ prefix = if xmlns != element.ns
248
+ xml.doc.namespaces.key(element.ns.uri)[6..-1].presence
247
249
  end
248
250
  case value
249
251
  when Xommelier::Xml::Element
250
- value.to_xommelier(builder: xml, element_name: options[:element_name], prefix: prefix, ns: options[:ns])
252
+ value.to_xommelier(
253
+ builder: xml,
254
+ element_name: element.element_name,
255
+ prefix: prefix,
256
+ ns: element.ns
257
+ )
251
258
  else
252
- element_name = options[:element_name].to_s
253
- element_name << '_' if %w(text class id).include?(element_name)
254
- (prefix ? xml[prefix] : xml).send(element_name) { xml.text(value.to_xommelier) }
259
+ (prefix ? xml[prefix] : xml).send(element.serializable_element_name) do
260
+ xml.text(value.to_xommelier)
261
+ end
255
262
  end
256
263
  end
257
264
  end
@@ -1,4 +1,5 @@
1
1
  require 'xommelier/xml/element'
2
+ require 'xommelier/xml/element/structure/property'
2
3
  require 'active_support/concern'
3
4
  require 'active_support/core_ext/module/delegation'
4
5
  require 'active_support/core_ext/object/with_options'
@@ -14,16 +15,15 @@ module Xommelier
14
15
 
15
16
  included do
16
17
  class_attribute :elements, :attributes
17
- attr_writer :element_name
18
18
 
19
- self.elements = {}
19
+ self.elements = {}
20
20
  self.attributes = {}
21
21
 
22
- class << self
23
- include SingletonClassMethods
24
- end
22
+ attr_writer :element_name
23
+
24
+ extend SingletonClassMethods
25
25
 
26
- delegate :xmlns, to: 'self.class'
26
+ delegate :xmlns, :schema, to: 'self.class'
27
27
  end
28
28
 
29
29
  module SingletonClassMethods
@@ -42,8 +42,13 @@ module Xommelier
42
42
  end
43
43
  @xmlns ||= find_namespace
44
44
  end
45
+
45
46
  alias_method :xmlns=, :xmlns
46
47
 
48
+ def schema
49
+ containing_module.schema
50
+ end
51
+
47
52
  def element_name(element_name = nil)
48
53
  @element_name = element_name if element_name
49
54
  @element_name ||= find_element_name
@@ -66,29 +71,27 @@ module Xommelier
66
71
 
67
72
  module ClassMethods
68
73
  def inherited(child)
69
- child.elements = elements.dup
70
- child.attributes = attributes.dup
74
+ child.elements = elements.dup
75
+ child.attributes = attributes.dup
71
76
  end
72
77
 
73
78
  # Defines containing element
74
79
  # @example
75
80
  # element :author, type: Xommelier::Atom::Person
76
81
  def element(name, options = {})
77
- options[:element_name] = options.delete(:as) { name }
78
- options[:ns] ||= if options[:type].try(:<, Xml::Element)
79
- options[:type].xmlns
80
- else
81
- xmlns
82
- end
83
- elements[name] = DEFAULT_ELEMENT_OPTIONS.merge(options)
82
+ options[:ns] ||= if options[:type].try(:<, Xml::Element)
83
+ options[:type].xmlns
84
+ else
85
+ xmlns
86
+ end
87
+ elements[name] = Element.new(name, options)
84
88
  define_element_accessors(name)
85
89
  end
86
90
 
87
91
  # Defines containing attribute
88
92
  def attribute(name, options = {})
89
- options[:attribute_name] = options.delete(:as) { name }
90
- options[:ns] ||= xmlns
91
- attributes[name] = DEFAULT_OPTIONS.merge(options)
93
+ options[:ns] ||= xmlns
94
+ attributes[name] = Attribute.new(name, options)
92
95
  define_attribute_accessors(name)
93
96
  end
94
97
 
@@ -98,7 +101,7 @@ module Xommelier
98
101
  end
99
102
 
100
103
  def any(&block)
101
- with_options(count: :any) { |any| any.instance_eval(&block) }
104
+ with_options(count: :any) { |any| any.instance_eval(&block) }
102
105
  end
103
106
 
104
107
  def many(&block)
@@ -109,73 +112,71 @@ module Xommelier
109
112
  with_options(count: :may) { |may| may.instance_eval(&block) }
110
113
  end
111
114
 
112
- def root; end
113
-
114
115
  private
115
116
 
116
117
  def define_element_accessors(name)
117
- element_options = elements[name]
118
- case element_options[:count]
119
- when :one, :may
120
- name = name.to_sym
121
- define_method(name) do |*args|
122
- if args[0]
123
- write_element(name, args[0])
124
- end
125
- read_element(name)
126
- end
127
- alias_method "#{name}=", name
128
- when :many, :any
129
- plural = name.to_s.pluralize.to_sym
130
- element_options[:plural] = plural
131
-
132
- define_method(plural) do |*args|
133
- if args.any?
134
- args.flatten.each_with_index do |object, index|
135
- write_element(name, object, index)
136
- end
137
- end
118
+ element = elements[name]
119
+ if element.multiple?
120
+ # Define plural accessors
121
+ plural = element.plural
122
+
123
+ rw_accessor(plural) do |*args|
124
+ args.flatten.each_with_index do |object, index|
125
+ write_element(name, object, index)
126
+ end if args.any?
127
+
138
128
  @elements[name] ||= []
139
129
  end
140
- alias_method "#{plural}=", plural
141
-
142
- unless plural == name
143
- define_method(name) do |*args|
144
- if args[0]
145
- send(plural, [args[0]])
146
- else
147
- send(plural)[0]
148
- end
130
+
131
+ # Define singular accessors for first element
132
+ unless element.numbers_equal?
133
+ rw_accessor(name) do |*args|
134
+ send(plural, [args[0]]) if args[0]
135
+ send(plural)[0]
149
136
  end
150
- alias_method "#{name}=", name
137
+ end
138
+ else
139
+ # Define singular accessors
140
+ name = name.to_sym
141
+ rw_accessor(name) do |*args|
142
+ write_element(name, args[0]) if args[0]
143
+ read_element(name)
151
144
  end
152
145
  end
153
146
  end
154
147
 
155
148
  def define_attribute_accessors(name)
156
- define_method(name) do |*args|
157
- if args[0]
158
- write_attribute(name.to_s, args[0])
159
- end
149
+ rw_accessor(name) do |*args|
150
+ write_attribute(name.to_s, args[0]) if args[0]
160
151
  read_attribute(name)
161
152
  end
162
- alias_method "#{name}=", name
163
153
  end
164
154
 
165
155
  def define_text_accessors
166
- define_method(:text) do |*args|
167
- if args[0]
168
- write_text(args[0])
169
- end
156
+ rw_accessor(:text) do |*args|
157
+ write_text(args[0]) if args[0]
170
158
  read_text
171
159
  end
172
- alias_method :text=, :text
173
160
  alias_attribute :content, :text
174
161
  end
162
+
163
+ protected
164
+
165
+ # Defines read-write accessor for +name+ and provides alias for write-only version
166
+ def rw_accessor(name, &block)
167
+ define_method(name, &block)
168
+ alias_method "#{name}=", name
169
+ end
175
170
  end
176
171
 
177
172
  protected
178
173
 
174
+ def set_default_values
175
+ self.class.attributes.merge(self.class.elements).each do |name, property|
176
+ send("#{name}=", property.default) if property.default?
177
+ end
178
+ end
179
+
179
180
  def options=(options = {})
180
181
  if @options.key?(:element_name)
181
182
  element_name(@options.delete(:element_name))
@@ -184,12 +185,11 @@ module Xommelier
184
185
  end
185
186
 
186
187
  def element_name(value = nil)
187
- if value
188
- @element_name = value
189
- end
188
+ @element_name = value if value
190
189
  @element_name ||= self.class.element_name
191
190
  end
192
191
 
192
+ # @return [Xommelier::Xml::Element::Structure::Element]
193
193
  def element_options(name)
194
194
  self.class.elements[name.to_sym]
195
195
  end
@@ -199,7 +199,7 @@ module Xommelier
199
199
  end
200
200
 
201
201
  def write_element(name, value, index = nil)
202
- type = element_options(name)[:type]
202
+ type = element_options(name).type
203
203
  unless value.is_a?(type)
204
204
  value = if (type < Xommelier::Xml::Element) && !value.is_a?(Nokogiri::XML::Node)
205
205
  type.new(value)
@@ -208,7 +208,7 @@ module Xommelier
208
208
  end
209
209
  end
210
210
  if index
211
- @elements[name.to_sym] ||= []
211
+ @elements[name.to_sym] ||= []
212
212
  @elements[name.to_sym][index] = value
213
213
  else
214
214
  @elements[name.to_sym] = value
@@ -219,6 +219,7 @@ module Xommelier
219
219
  @elements.delete(name.to_sym)
220
220
  end
221
221
 
222
+ # @return [Xommelier::Xml::Element::Structure::Attribute]
222
223
  def attribute_options(name)
223
224
  self.class.attributes[name.to_sym]
224
225
  end
@@ -228,7 +229,7 @@ module Xommelier
228
229
  end
229
230
 
230
231
  def write_attribute(name, value)
231
- type = attribute_options(name)[:type]
232
+ type = attribute_options(name).type
232
233
  value = type.from_xommelier(value) unless value.is_a?(type)
233
234
  @attributes[name.to_sym] = value
234
235
  end