xommelier 0.1.16 → 0.1.18

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