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.
- checksums.yaml +15 -0
- data/lib/xommelier/rss.rb +2 -2
- data/lib/xommelier/schemas/atom.xsd +241 -240
- data/lib/xommelier/schemas/opensearch.xsd +1 -0
- data/lib/xommelier/schemas/rss.xsd +23 -22
- data/lib/xommelier/schemas/sitemap.xsd +1 -0
- data/lib/xommelier/schemas/xml.xsd +287 -0
- data/lib/xommelier/schemas/xsd.xsl +997 -0
- data/lib/xommelier/version.rb +1 -1
- data/lib/xommelier/xml.rb +4 -23
- data/lib/xommelier/xml/element.rb +7 -22
- data/lib/xommelier/xml/element/namespace.rb +83 -0
- data/lib/xommelier/xml/element/serialization.rb +17 -2
- data/lib/xommelier/xml/element/structure.rb +35 -39
- data/lib/xommelier/xml/schema.rb +68 -0
- data/spec/fixtures/atom.rng.xml +597 -597
- data/spec/functional/xommelier/rss/rss/building_spec.rb +1 -0
- metadata +9 -20
data/lib/xommelier/version.rb
CHANGED
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
|
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,
|
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
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
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
|