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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dbe79de11a54ebfff237869fbef6f4ccb9ce8b063dc3305299cb1fcb7856298b
4
- data.tar.gz: 549031f8aa72adca7434f9294bfef40e2b2ce70b8980b04960cd20ab320dace4
3
+ metadata.gz: cda44bf92902eaf418da297019b5390015d244cdb20da04142b01036146a54ff
4
+ data.tar.gz: c9ac5ba79dd64afc4d47cab9d8de4379e742db5008342f8888dcec63ebac5900
5
5
  SHA512:
6
- metadata.gz: 1336127c5245a94b7a8915f5d87372e17ab52bad43c32d1c07ef165d26db0ca1182baefcc826bd58cd401e213a2f7c04129dfea28d7c950d9fe1e06e2bb6780a
7
- data.tar.gz: b60f8dcc6904c9b706f338a7afa7438fd98b7029a2a06b9f07010a63e3cddd1f0913bad0bc5ec2d608c3446c137339bb2ae8640251e7b4775ac006b9799e0c75
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
- errors = XML_XSI::Schema::new(XML_XSI.parse(fname)).validate
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
@@ -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, parent_xml_doc = nil)
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
- target_ns_href = nil
107
+ @ns_href = nil
48
108
  @document.namespaces.each do |ns_prefix, ns_href|
49
- target_ns_href = ns_href if ns_prefix.nil? || ns_prefix.empty? || ns_prefix.eql?('xmlns')
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, optionally inheriting their location declarations from a parent document
56
- schema_locations = parent_xml_doc.nil? ? {} : self.class.find_schema_locations(parent_xml_doc)
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
- ## If we still don't have a file location for the target namespace, attempt to look for
60
- ## one based on the name of the root node (assuming that where the namespace was declared).
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
- " targetNamespace=\"#{target_ns_href}\"\n" \
76
- " version=\"1.0\">\n"
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
- target_ns_file = schema_locations.delete(target_ns_href)
80
- @xsd << " <xsd:include schemaLocation=\"#{target_ns_file}\"/>\n" unless target_ns_file.nil?
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
- @schema = Nokogiri::XML::Schema.new(@xsd)
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.each do |err|
99
- fname = (err.file.nil?) ? @document.filename : err.file
100
- errors << ValidationError.new(:XML, err, fname)
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "validate_xml_xsi"
3
- spec.version = "0.4.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.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: