xommelier 0.1.21 → 0.1.22

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.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