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