validate_xml_xsi 0.4.0 → 0.5.0
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 +4 -4
- data/bin/validate_xml_xsi +3 -1
- data/lib/validate_xml_xsi.rb +87 -64
- data/validate_xml_xsi.gemspec +2 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cda44bf92902eaf418da297019b5390015d244cdb20da04142b01036146a54ff
|
|
4
|
+
data.tar.gz: c9ac5ba79dd64afc4d47cab9d8de4379e742db5008342f8888dcec63ebac5900
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5db59f5fbf7c3aaf61c931b0ea5227c9d08aaba1e65de797721f06c73b58939766f4390beac7ef919784d55b51abc4a4b21416f1edafdbe20b9ffec090565ff
|
|
7
|
+
data.tar.gz: a2217e8efdfb6b6ce53dec4fb7f9ec09147dd95fdc2a241a3ae277b7f3155867c33129780f7b8766b0039422fa76d2440346a72382662cec98cd1b43ae51ce2a
|
data/bin/validate_xml_xsi
CHANGED
|
@@ -3,7 +3,9 @@ require 'validate_xml_xsi'
|
|
|
3
3
|
|
|
4
4
|
while ARGV.size > 0
|
|
5
5
|
fname = ARGV.shift
|
|
6
|
-
|
|
6
|
+
xml_doc = XML_XSI::parse(fname)
|
|
7
|
+
xsd = XML_XSI::Schema.new(xml_doc)
|
|
8
|
+
errors = xsd.validate
|
|
7
9
|
if errors.empty?
|
|
8
10
|
puts "XML Schema check complete - NO ERRORS!"
|
|
9
11
|
else
|
data/lib/validate_xml_xsi.rb
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
require 'nokogiri'
|
|
3
3
|
|
|
4
|
+
## Monkey-patch Nokogiri::XML::SyntaxError::aggregate to also keep the ENTIRE LIST of errors - ARGH!
|
|
5
|
+
Nokogiri::XML::SyntaxError.class_eval do
|
|
6
|
+
class << self
|
|
7
|
+
alias_method :orig_aggregate, :aggregate
|
|
8
|
+
def aggregate(errors)
|
|
9
|
+
agg_err = orig_aggregate(errors)
|
|
10
|
+
agg_err.instance_variable_set(:@aggregate, errors)
|
|
11
|
+
agg_err.define_singleton_method(:aggregate) { instance_variable_get(:@aggregate) }
|
|
12
|
+
agg_err
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
4
17
|
class XML_XSI
|
|
5
18
|
def self.parse(obj)
|
|
6
19
|
filename = nil
|
|
@@ -11,8 +24,49 @@ class XML_XSI
|
|
|
11
24
|
xml_doc
|
|
12
25
|
end
|
|
13
26
|
|
|
27
|
+
def self.find_schema_locations(xml_doc)
|
|
28
|
+
## Include the default xml namespace
|
|
29
|
+
schema_locations = {}
|
|
30
|
+
|
|
31
|
+
## Determine if the document has reference to the namespace "http://www.w3.org/2001/XMLSchema-instance"
|
|
32
|
+
## which is used for defining schemaLocations
|
|
33
|
+
xsi_prefix = xml_doc.namespaces.invert['http://www.w3.org/2001/XMLSchema-instance']&.delete_prefix('xmlns:')
|
|
34
|
+
|
|
35
|
+
## Iterate over all the elements and find any xsi:schemaLocation attributes
|
|
36
|
+
## and build a hash of all of the namespaces and locations
|
|
37
|
+
unless xsi_prefix.nil?
|
|
38
|
+
xsi_loc = "#{xsi_prefix}:schemaLocation"
|
|
39
|
+
xml_doc.search("//*[@#{xsi_loc}]").each do |elem|
|
|
40
|
+
elem[xsi_loc].scan(/(\S+)\s+(\S+)/).each do |ns_set|
|
|
41
|
+
if ns_loc = schema_locations[ns_set.first]
|
|
42
|
+
unless ns_loc.eql?(ns_set.last)
|
|
43
|
+
raise DocumentError.new("MISMATCHING namespace: #{ns_set.first} -> #{ns_loc} VS #{ns_set.last}")
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
schema_locations[ns_set.first] = ns_set.last
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
schema_locations
|
|
52
|
+
end
|
|
53
|
+
|
|
14
54
|
class Schema
|
|
15
55
|
class DocumentError < StandardError; end
|
|
56
|
+
class NamespaceError < StandardError
|
|
57
|
+
def initialize
|
|
58
|
+
super("Unable to determine default/target (xmlns) namespace!")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
class LocationError < StandardError
|
|
62
|
+
def initialize(ns_href = nil)
|
|
63
|
+
msg = ns_href.nil? ?
|
|
64
|
+
"No schema locations defined or provided" :
|
|
65
|
+
"No location for the default (xmlns) namespace schema: #{ns_href}"
|
|
66
|
+
super(msg)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
## Unified Error reportign class for both XSD and XML errors
|
|
16
70
|
class ValidationError < StandardError
|
|
17
71
|
attr_reader :type, :file, :line, :column, :level, :message, :description, :error
|
|
18
72
|
def initialize(type, err, filename = nil)
|
|
@@ -34,50 +88,43 @@ class XML_XSI
|
|
|
34
88
|
end
|
|
35
89
|
end
|
|
36
90
|
|
|
91
|
+
class Erroneous
|
|
92
|
+
attr_reader :errors
|
|
93
|
+
def initialize(ex)
|
|
94
|
+
errs = ex.respond_to?(:aggregate) ? ex.aggregate : [ex]
|
|
95
|
+
@errors = errs.map { |err| ValidationError.new(:XSD, err) }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
37
99
|
attr_reader :xsd
|
|
38
|
-
def initialize(xml_doc,
|
|
100
|
+
def initialize(xml_doc, schema_locations = {})
|
|
101
|
+
raise ArgumentError.new("Provided schema locations must be a Hash") unless schema_locations.is_a?(Hash)
|
|
39
102
|
unless xml_doc.is_a?(Nokogiri::XML::Document)
|
|
40
103
|
raise DocumentError.new("invalid Nokogiri::XML::Document - #{xml_doc.class.name}")
|
|
41
104
|
end
|
|
42
|
-
unless parent_xml_doc.nil? || parent_xml_doc.is_a?(Nokogiri::XML::Document)
|
|
43
|
-
raise DocumentError.new("invalid parent Nokogiri::XML::Document - #{parent_xml_doc.class.name}")
|
|
44
|
-
end
|
|
45
105
|
@document = xml_doc
|
|
46
106
|
## Determine default/top/root namespace
|
|
47
|
-
|
|
107
|
+
@ns_href = nil
|
|
48
108
|
@document.namespaces.each do |ns_prefix, ns_href|
|
|
49
|
-
|
|
50
|
-
end
|
|
51
|
-
if target_ns_href.nil? || target_ns_href.empty?
|
|
52
|
-
raise DocumentError.new("Unable to determine a default (xmlns) namespace!")
|
|
109
|
+
@ns_href = ns_href if ns_prefix.nil? || ns_prefix.empty? || ns_prefix.eql?('xmlns')
|
|
53
110
|
end
|
|
111
|
+
raise NamespaceError.new if @ns_href.nil? || @ns_href.empty?
|
|
54
112
|
|
|
55
|
-
## Determine schema locations
|
|
56
|
-
schema_locations
|
|
57
|
-
schema_locations.merge!(self.class.find_schema_locations(@document))
|
|
113
|
+
## Determine schema locations found in the source document
|
|
114
|
+
schema_locations.merge!(XML_XSI::find_schema_locations(@document))
|
|
58
115
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if !schema_locations.include?(target_ns_href) &&
|
|
62
|
-
@document.root.namespace.href.eql?(target_ns_href)
|
|
63
|
-
root_file_xsd = "#{@document.root.name}.xsd"
|
|
64
|
-
schema_locations[target_ns_href] = root_file_xsd if File.exist?(root_file_xsd)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
unless schema_locations.include?(target_ns_href)
|
|
68
|
-
## XXX - Another possibility would be to default to a file named after the node name declaring the xmlns
|
|
69
|
-
raise DocumentError.new("Unable to locate a source/file for the default (xmlns) namespace schema!")
|
|
70
|
-
end
|
|
116
|
+
raise LocationError.new if schema_locations.empty?
|
|
117
|
+
raise LocationError.new(@ns_href) unless schema_locations.include?(@ns_href)
|
|
71
118
|
|
|
72
119
|
## Build an all-in-one XSD document that imports all of the separate schema locations
|
|
73
120
|
@xsd = "<?xml version=\"1.0\"?>\n"
|
|
74
121
|
@xsd << "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n" \
|
|
75
|
-
|
|
76
|
-
|
|
122
|
+
" targetNamespace=\"#{@ns_href}\"\n" \
|
|
123
|
+
" version=\"1.0\">\n"
|
|
77
124
|
|
|
78
125
|
## Minimally we need the target namespace location or we have nothing to include
|
|
79
|
-
|
|
80
|
-
@xsd << " <xsd:include schemaLocation=\"#{
|
|
126
|
+
@xsi_file = schema_locations.delete(@ns_href)
|
|
127
|
+
@xsd << " <xsd:include schemaLocation=\"#{@xsi_file}\"/>\n" unless @xsi_file.nil?
|
|
81
128
|
|
|
82
129
|
## Now add imports for the other defined schemaLocations
|
|
83
130
|
schema_locations.each do |ns_href, ns_file|
|
|
@@ -86,47 +133,23 @@ class XML_XSI
|
|
|
86
133
|
@xsd << "</xsd:schema>\n"
|
|
87
134
|
|
|
88
135
|
## Create the Schema objects
|
|
89
|
-
|
|
136
|
+
begin
|
|
137
|
+
@schema = Nokogiri::XML::Schema.new(@xsd)
|
|
138
|
+
rescue Nokogiri::XML::SyntaxError => ex
|
|
139
|
+
## Trap the errors so we can finish initialization and then
|
|
140
|
+
## report the errors in a sane manner
|
|
141
|
+
@schema = Erroneous.new(ex)
|
|
142
|
+
end
|
|
90
143
|
end
|
|
91
144
|
|
|
145
|
+
def errors; @schema.errors; end
|
|
146
|
+
|
|
92
147
|
def validate
|
|
93
|
-
errors = []
|
|
94
|
-
@schema.errors.each do |err|
|
|
95
|
-
errors << ValidationError.new(:XSD, err)
|
|
96
|
-
end
|
|
97
148
|
errs = @schema.validate(@document)
|
|
98
|
-
errs.
|
|
99
|
-
fname =
|
|
100
|
-
|
|
101
|
-
end
|
|
102
|
-
errors
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def self.find_schema_locations(xml_doc)
|
|
106
|
-
## Include the default xml namespace
|
|
107
|
-
schema_locations = {}
|
|
108
|
-
|
|
109
|
-
## Determine if the document has reference to the namespace "http://www.w3.org/2001/XMLSchema-instance"
|
|
110
|
-
## which is used for defining schemaLocations
|
|
111
|
-
xsi_prefix = xml_doc.namespaces.invert['http://www.w3.org/2001/XMLSchema-instance']&.delete_prefix('xmlns:')
|
|
112
|
-
|
|
113
|
-
## Iterate over all the elements and find any xsi:schemaLocation attributes
|
|
114
|
-
## and build a hash of all of the namespaces and locations
|
|
115
|
-
unless xsi_prefix.nil?
|
|
116
|
-
xsi_loc = "#{xsi_prefix}:schemaLocation"
|
|
117
|
-
xml_doc.search("//*[@#{xsi_loc}]").each do |elem|
|
|
118
|
-
elem[xsi_loc].scan(/(\S+)\s+(\S+)/).each do |ns_set|
|
|
119
|
-
if ns_loc = schema_locations[ns_set.first]
|
|
120
|
-
unless ns_loc.eql?(ns_set.last)
|
|
121
|
-
raise DocumentError.new("MISMATCHING namespace: #{ns_set.first} -> #{ns_loc} VS #{ns_set.last}")
|
|
122
|
-
end
|
|
123
|
-
else
|
|
124
|
-
schema_locations[ns_set.first] = ns_set.last
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
149
|
+
errs.map do |err|
|
|
150
|
+
fname = err.file.nil? ? @document.filename : err.file
|
|
151
|
+
ValidationError.new(:XML, err, fname)
|
|
128
152
|
end
|
|
129
|
-
schema_locations
|
|
130
153
|
end
|
|
131
154
|
end
|
|
132
155
|
end
|
data/validate_xml_xsi.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |spec|
|
|
2
2
|
spec.name = "validate_xml_xsi"
|
|
3
|
-
spec.version = "0.
|
|
3
|
+
spec.version = "0.5.0"
|
|
4
4
|
spec.authors = ["David Hansen"]
|
|
5
5
|
spec.email = ["david@hansen4.net"]
|
|
6
6
|
|
|
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
spec.add_development_dependency "rake", ">= 13.0"
|
|
27
27
|
|
|
28
28
|
spec.add_dependency "nokogiri", ">= 1.13.2"
|
|
29
|
+
spec.add_dependency "libxml-ruby", ">= 5.0.0"
|
|
29
30
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: validate_xml_xsi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Hansen
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: 1.13.2
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: libxml-ruby
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 5.0.0
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 5.0.0
|
|
54
68
|
email:
|
|
55
69
|
- david@hansen4.net
|
|
56
70
|
executables:
|