xommelier 0.1.21 → 0.1.22

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.21'
2
+ VERSION = '0.1.22'
3
3
  end
data/lib/xommelier/xml.rb CHANGED
@@ -2,6 +2,7 @@ require 'xommelier'
2
2
  require 'nokogiri'
3
3
  require 'xommelier/xml/namespace'
4
4
  require 'active_support/concern'
5
+ require 'xommelier/xml/schema'
5
6
 
6
7
  module Xommelier
7
8
  module Xml
@@ -10,6 +11,8 @@ module Xommelier
10
11
  DEFAULT_NS = 'http://www.w3.org/XML/1998/namespace'
11
12
 
12
13
  module ClassMethods
14
+ include Schema
15
+
13
16
  # Defines namespace used in formats
14
17
  def xmlns(uri = nil, options = {}, &block)
15
18
  if uri
@@ -18,29 +21,6 @@ module Xommelier
18
21
  end
19
22
  instance_variable_get(:@_xmlns) || Xml.xmlns
20
23
  end
21
-
22
- def schema(schema = nil)
23
- if schema
24
- # If schema or schema path provided, set schema
25
- schema = Nokogiri::XML::Schema(open(schema).read) unless schema.is_a?(Nokogiri::XML::Node)
26
- instance_variable_set(:@_schema, schema)
27
- elsif !instance_variable_defined?(:@_schema)
28
- # Unless schema exists, try to autoload schema
29
- available_schema = available_schemas.find { |path| path =~ /#{xmlns.as}\.xsd/ }
30
- self.schema(available_schema) if available_schema
31
- else
32
- instance_variable_set(:@schema, nil)
33
- end
34
- instance_variable_get(:@_schema)
35
- end
36
-
37
- protected
38
-
39
- def available_schemas
40
- @_available_schemas ||= $:.map do |path|
41
- Dir[File.join(path, 'xommelier/schemas', '*.xsd')]
42
- end.flatten.uniq
43
- end
44
24
  end
45
25
 
46
26
  included do
@@ -49,6 +29,7 @@ module Xommelier
49
29
 
50
30
  # Define XML default namespace
51
31
  extend ClassMethods
32
+
52
33
  xmlns DEFAULT_NS, as: :xml
53
34
 
54
35
  # Inject common XML attributes to every XML element
@@ -1,4 +1,5 @@
1
1
  require 'xommelier/xml'
2
+ require 'xommelier/xml/element/namespace'
2
3
  require 'xommelier/xml/element/structure'
3
4
  require 'xommelier/xml/element/serialization'
4
5
  require 'active_support/core_ext/string/inflections'
@@ -8,6 +9,7 @@ require 'active_support/core_ext/class/attribute'
8
9
  module Xommelier
9
10
  module Xml
10
11
  class Element
12
+ include Xommelier::Xml::Element::Namespace
11
13
  include Xommelier::Xml::Element::Structure
12
14
  include Xommelier::Xml::Element::Serialization
13
15
 
@@ -16,10 +18,10 @@ module Xommelier
16
18
  def initialize(contents = {}, options = {})
17
19
  self.options = options
18
20
 
19
- @elements = {}
21
+ @elements = {}
20
22
  @attributes = {}
21
- @text = nil
22
- @errors = []
23
+ @text = nil
24
+ @errors = []
23
25
 
24
26
  set_default_values
25
27
 
@@ -42,39 +44,22 @@ module Xommelier
42
44
  @options.delete(:type)
43
45
  end
44
46
 
45
- def valid?
46
- validate
47
- @errors.empty? || @errors
48
- end
49
-
50
47
  def inspect
51
48
  %(#<#{self.class.name}:0x#{object_id.to_s(16)} #{inspect_contents}>)
52
49
  end
53
50
 
54
51
  private
55
52
 
56
- def validate
57
- @errors = []
58
- to_xml unless xml_document
59
- if schema
60
- schema.validate(xml_document).each do |error|
61
- @errors << error
62
- end
63
- else
64
- raise NoSchemaError.new(self)
65
- end
66
- end
67
-
68
53
  def inspect_contents
69
54
  [inspect_attributes, inspect_elements, inspect_text].compact.join(' ')
70
55
  end
71
56
 
72
57
  def inspect_attributes
73
- "@attributes={#{@attributes.map { |name, value| "#{name}: #{value.inspect}"}.join(', ')}}" if @attributes.any?
58
+ "@attributes={#{@attributes.map { |name, value| "#{name}: #{value.inspect}" }.join(', ')}}" if @attributes.any?
74
59
  end
75
60
 
76
61
  def inspect_elements
77
- "#{@elements.map { |name, value| "@#{name}=#{value.inspect}"}.join(' ')}" if @elements.any?
62
+ "#{@elements.map { |name, value| "@#{name}=#{value.inspect}" }.join(' ')}" if @elements.any?
78
63
  end
79
64
 
80
65
  def inspect_text
@@ -0,0 +1,83 @@
1
+ require 'xommelier/xml/element'
2
+ require 'active_support/concern'
3
+
4
+ module Xommelier
5
+ module Xml
6
+ class Element
7
+ module Namespace
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ # @return [Xommelier::Xml::Namespace] associated namespace
12
+ def xmlns(value = nil)
13
+ self.xmlns = value if value
14
+ @xmlns ||= find_namespace
15
+ end
16
+
17
+ # @param [Module, Xommelier::Xml::Namespace] value namespace object or module
18
+ def xmlns=(value)
19
+ @xmlns = case value
20
+ when Module
21
+ value.xmlns
22
+ else
23
+ value
24
+ end
25
+ end
26
+
27
+ # @return [Nokogiri::XML::Schema] schema associated with element's namespace or module
28
+ def schema
29
+ containing_module.schema
30
+ end
31
+
32
+ # @return [String] path to schema file
33
+ def schema_location
34
+ containing_module.schema_location
35
+ end
36
+
37
+ protected
38
+
39
+ # @return [Module, Class]
40
+ def containing_module
41
+ @containing_module ||= ("::#{name.gsub(/::[^:]+$/, '')}").constantize
42
+ end
43
+
44
+ # @return [Xommelier::Xml::Namespace]
45
+ def find_namespace
46
+ (self == containing_module ? Xommelier::Xml : containing_module).xmlns
47
+ end
48
+ end
49
+
50
+ # @return [true, false]
51
+ def valid?
52
+ validate
53
+ schema_validation_errors.empty?
54
+ end
55
+
56
+ # @return [Array<Nokogiri::XML::SyntaxError>]
57
+ attr_reader :schema_validation_errors
58
+ alias errors schema_validation_errors # compatibility
59
+
60
+ protected
61
+
62
+ # Validates document
63
+ def validate
64
+ @schema_validation_errors = []
65
+ if self.class.schema
66
+ #document = ensure_xml_document
67
+ document = Nokogiri::XML(to_xml)
68
+ self.class.schema.validate(document).each do |error|
69
+ @schema_validation_errors << error
70
+ end
71
+ if @schema_validation_errors.any?
72
+ puts self.class, self.class.schema_location, document
73
+ require 'pp'
74
+ pp @schema_validation_errors
75
+ end
76
+ else
77
+ raise NoSchemaError.new(self)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,6 +1,8 @@
1
1
  require 'xommelier/xml/element'
2
2
  require 'active_support/concern'
3
3
  require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/hash/slice'
5
+ require 'active_support/core_ext/hash/except'
4
6
  require 'nokogiri'
5
7
 
6
8
  module Xommelier
@@ -12,6 +14,7 @@ module Xommelier
12
14
  SERIALIZATION_OPTIONS = {
13
15
  encoding: 'utf-8'
14
16
  }
17
+ SAVE_OPTIONS = [:save_with, :indent_text, :indent]
15
18
 
16
19
  module ClassMethods
17
20
  def from_xml(xml, options = {})
@@ -45,7 +48,6 @@ module Xommelier
45
48
  xml = Nokogiri::XML(xml)
46
49
  end
47
50
  @_xml_node = options.delete(:node) { xml.at_xpath(element_xpath(xml.document, element_name)) }
48
- validate if options[:validate]
49
51
 
50
52
  if text? && @_xml_node.inner_html.present?
51
53
  self.text = @_xml_node.inner_html
@@ -63,6 +65,9 @@ module Xommelier
63
65
 
64
66
  def to_xml(options = {})
65
67
  options = SERIALIZATION_OPTIONS.merge(options)
68
+ save_options = options.slice(:encoding, *SAVE_OPTIONS)
69
+ options = options.except(*SAVE_OPTIONS)
70
+
66
71
  element_name = options.delete(:element_name) { self.element_name }
67
72
  element_name = element_name.to_s
68
73
  element_name << '_' if %w(text class id).include?(element_name)
@@ -109,7 +114,7 @@ module Xommelier
109
114
  end
110
115
  xml.text(@text) if respond_to?(:text)
111
116
  end.instance_variable_get(:@node)
112
- builder.to_xml
117
+ builder.to_xml(save_options)
113
118
  end
114
119
  alias_method :to_xommelier, :to_xml
115
120
 
@@ -133,6 +138,11 @@ module Xommelier
133
138
  end
134
139
  end
135
140
 
141
+ # @return [Nokogiri::XML::Node]
142
+ def to_nokogiri
143
+ ensure_xml_document.root
144
+ end
145
+
136
146
  def <=>(other)
137
147
  if text? && other.is_a?(String)
138
148
  text.to_s <=> other
@@ -191,6 +201,11 @@ module Xommelier
191
201
  @_xml_node.try(:document)
192
202
  end
193
203
 
204
+ def ensure_xml_document
205
+ to_xml unless xml_document
206
+ xml_document
207
+ end
208
+
194
209
  def xmlns_xpath(xml_document = self.xml_document)
195
210
  self.class.xmlns_xpath(xml_document)
196
211
  end
@@ -23,46 +23,16 @@ module Xommelier
23
23
 
24
24
  extend SingletonClassMethods
25
25
 
26
- delegate :xmlns, :schema, to: 'self.class'
26
+ delegate :xmlns, to: 'self.class'
27
27
  end
28
28
 
29
29
  module SingletonClassMethods
30
- def containing_module
31
- @containing_module ||= ("::#{name.gsub(/::[^:]+$/, '')}").constantize
32
- end
33
-
34
- def xmlns(value = nil)
35
- if value
36
- @xmlns = case value
37
- when Module
38
- value.xmlns
39
- else
40
- value
41
- end
42
- end
43
- @xmlns ||= find_namespace
44
- end
45
-
46
- alias_method :xmlns=, :xmlns
47
-
48
- def schema
49
- containing_module.schema
50
- end
51
-
52
30
  def element_name(element_name = nil)
53
31
  @element_name = element_name if element_name
54
32
  @element_name ||= find_element_name
55
33
  end
56
34
 
57
- private
58
-
59
- def find_namespace
60
- if self == containing_module
61
- Xommelier::Xml::DEFAULT_NS
62
- else
63
- containing_module.xmlns
64
- end
65
- end
35
+ protected
66
36
 
67
37
  def find_element_name
68
38
  name.demodulize.underscore.dasherize
@@ -70,6 +40,7 @@ module Xommelier
70
40
  end
71
41
 
72
42
  module ClassMethods
43
+ # @param [Xommelier::Xml::Element] child
73
44
  def inherited(child)
74
45
  child.elements = elements.dup
75
46
  child.attributes = attributes.dup
@@ -79,12 +50,33 @@ module Xommelier
79
50
  # @example
80
51
  # element :author, type: Xommelier::Atom::Person
81
52
  def element(name, options = {})
82
- options[:ns] ||= if options[:type].try(:<, Xml::Element)
83
- options[:type].xmlns
84
- else
85
- xmlns
86
- end
87
- element = Element.new(name, options)
53
+ # Set name, type and element name by reference if provided
54
+ name, options = nil, name if name.is_a?(Hash)
55
+
56
+ # Set type and element name from reference
57
+ if options.key?(:ref)
58
+ raise "#{options[:ref]} is not subclass of Xommelier::Element" unless referenceable?(options[:ref])
59
+
60
+ options[:type] = options.delete(:ref)
61
+
62
+ # Set element name from provided complex type
63
+ options[:as] ||= options[:type].element_name
64
+
65
+ # Set attribute name from element name
66
+ name ||= options[:as].underscore.to_sym
67
+ end
68
+
69
+ # Try to define element name from
70
+ options[:as] ||= name.to_s.camelize(:lower)
71
+
72
+ # Set namespace from element type or wrapper xmlns
73
+ options[:ns] ||= if referenceable?(options[:type])
74
+ options[:type].xmlns
75
+ else
76
+ xmlns
77
+ end
78
+
79
+ element = Element.new(name, options)
88
80
  elements[name] = element
89
81
  define_element_accessors(element)
90
82
  end
@@ -167,6 +159,10 @@ module Xommelier
167
159
  define_method(name, &block)
168
160
  alias_method "#{name}=", name
169
161
  end
162
+
163
+ def referenceable?(type)
164
+ type.is_a?(Class) && type < Xommelier::Xml::Element
165
+ end
170
166
  end
171
167
 
172
168
  protected
@@ -199,7 +195,7 @@ module Xommelier
199
195
 
200
196
  def write_element(name, value, index = nil)
201
197
  element = element_options(name)
202
- type = element.type
198
+ type = element.type
203
199
  unless value.is_a?(type)
204
200
  value = if element.complex_type? && !value.is_a?(Nokogiri::XML::Node)
205
201
  type.new(value)
@@ -0,0 +1,68 @@
1
+ require 'xommelier'
2
+
3
+ module Xommelier
4
+ module Xml
5
+ module Schema
6
+ def schema_location(new_location = nil)
7
+ self.schema_location = new_location if new_location
8
+
9
+ @_schema_location
10
+ end
11
+
12
+ def schema_location=(location)
13
+ return unless location
14
+ @_schema_location = location
15
+ # For loading schema containing imports we need to temporarily chdir,
16
+ # so relative file names will be properly discovered
17
+ Dir.chdir(File.dirname(location)) do
18
+ @_schema = Nokogiri::XML::Schema(File.read(File.basename(location)))
19
+ end
20
+ end
21
+
22
+ # @param [String, Nokogiri::XML::Node] schema
23
+ # @return [Nokogiri::XML::Schema, nil]
24
+ def schema(schema = nil)
25
+ self.schema = schema if schema
26
+
27
+ unless instance_variable_defined?(:@_schema)
28
+ # Unless schema exists, try to autoload schema
29
+ if _default_schema_location
30
+ self.schema_location = _default_schema_location
31
+ else
32
+ @_schema = nil
33
+ end
34
+ end
35
+ @_schema
36
+ end
37
+
38
+ def schema=(schema)
39
+ if schema
40
+ # If schema or schema path provided, set schema
41
+ if schema.is_a?(Nokogiri::XML::Schema)
42
+ @_schema = schema
43
+ elsif schema.is_a?(Nokogiri::XML::Node)
44
+ @_schema = Nokogiri::XML::Schema(schema)
45
+ else
46
+ self.schema_location = schema
47
+ end
48
+ end
49
+ @_schema
50
+ end
51
+
52
+ protected
53
+
54
+ def _available_schemas
55
+ @_available_schemas ||= $:.map do |path|
56
+ Dir[File.join(path, 'xommelier/schemas', '*.xsd')]
57
+ end.flatten.uniq
58
+ end
59
+
60
+ def _default_schema_location
61
+ @_default_schema_location ||= begin
62
+ file_name = /#{xmlns.as || name.demodulize.underscore}\.xsd\Z/
63
+ _available_schemas.find { |path| path =~ file_name }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end