validate_xml_xsi 0.3.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: aeccb816b7e38de2b2cc3b29dd0f3035e56f8bf2190f9d1281ae11ec73573efb
4
- data.tar.gz: '0953d2190b837f8b692b468c3e06d77c55cad671a03ed4eaf2e7219e31013cc3'
3
+ metadata.gz: cda44bf92902eaf418da297019b5390015d244cdb20da04142b01036146a54ff
4
+ data.tar.gz: c9ac5ba79dd64afc4d47cab9d8de4379e742db5008342f8888dcec63ebac5900
5
5
  SHA512:
6
- metadata.gz: 323fc0b7cd7b322ff35c72dd77ae1b33b5aee6b5bed953ed7b3e965cc7924bb98c36145a2296e279a6c6e208c9aa056e33809f00f3e9272d15b898425bc3e407
7
- data.tar.gz: 8d79a528c2fb9172b2d0d29bcd10f67d241a792fba510b613eed501645d73cb137ed3ba0af0223b5b4cd3510025268184a42e487810428b196d17664778501a1
6
+ metadata.gz: e5db59f5fbf7c3aaf61c931b0ea5227c9d08aaba1e65de797721f06c73b58939766f4390beac7ef919784d55b51abc4a4b21416f1edafdbe20b9ffec090565ff
7
+ data.tar.gz: a2217e8efdfb6b6ce53dec4fb7f9ec09147dd95fdc2a241a3ae277b7f3155867c33129780f7b8766b0039422fa76d2440346a72382662cec98cd1b43ae51ce2a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## v0.3.0
2
+
3
+ * Change overall structure to make XML_XSI::Schema a normal class (not just a singleton)
4
+ * Allow the Schema to return the xsd document string (so it can be used externally to build a XSD document tree)
5
+
1
6
  ## v0.2.4
2
7
 
3
8
  * Fix some silly syntax errors
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # validate_xml_xsi
2
2
 
3
- This gem validates XML against it's xsi using the xsi:schemaLocation elements to define the location of XSD documents.
3
+ This gem validates XML against it's XSD that is created from xsi:schemaLocation elements.
4
4
 
5
- It does this by first parsing the XML and searching for xsi:schemaLocation elements and then creating a schema document that imports the XSD's identified in the XML.
5
+ It does this by first parsing the XML and searching for elements that include xsi:schemaLocation attributes. It then creates an XSD schema document that includes the root namespace and further imports additional namespaces discovered in the XML.
6
6
 
7
- It then validates the document against that schema and outputs any error messages.
7
+ It then validates the document against that constructed schema document and outputs any error messages.
8
8
 
9
- Note: this gem utilizes the 'nokogiri' gem for XML parsing and schema validation.
9
+ Note: this gem utilizes the 'nokogiri' gem (which in turn relies on Gnome's libxml2) for the XML parsing and schema validation.
10
10
 
11
11
  ## Installation
12
12
 
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,60 +88,68 @@ 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
105
  @document = xml_doc
43
106
  ## Determine default/top/root namespace
44
- target_ns_href = nil
107
+ @ns_href = nil
45
108
  @document.namespaces.each do |ns_prefix, ns_href|
46
- target_ns_href = ns_href if ns_prefix.nil? || ns_prefix.empty? || ns_prefix.eql?('xmlns')
109
+ @ns_href = ns_href if ns_prefix.nil? || ns_prefix.empty? || ns_prefix.eql?('xmlns')
47
110
  end
48
- raise DocumentError.new("Unable to determine default (xmlns) namespace!") if target_ns_href.nil? || target_ns_href.empty?
111
+ raise NamespaceError.new if @ns_href.nil? || @ns_href.empty?
112
+
113
+ ## Determine schema locations found in the source document
114
+ schema_locations.merge!(XML_XSI::find_schema_locations(@document))
115
+
116
+ raise LocationError.new if schema_locations.empty?
117
+ raise LocationError.new(@ns_href) unless schema_locations.include?(@ns_href)
118
+
49
119
  ## Build an all-in-one XSD document that imports all of the separate schema locations
50
120
  @xsd = "<?xml version=\"1.0\"?>\n"
51
121
  @xsd << "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n" \
52
- " targetNamespace=\"#{target_ns_href}\"\n" \
53
- " version=\"1.0\">\n"
54
- ## Include the default xml namespace
55
- schemata_by_ns = {}
56
- ## Iterate over all the elements and find any xsi:schemaLocation attributes
57
- ## and build a hash of all of the namespaces and locations
58
- @document.search('//*[@xsi:schemaLocation]').each do |elem|
59
- elem['xsi:schemaLocation'].scan(/(\S+)\s+(\S+)/).each do |ns_set|
60
- if ns_loc = schemata_by_ns[ns_set.first]
61
- unless ns_loc.eql?(ns_set.last)
62
- raise DocumentError.new("MISMATCHING namespace: #{ns_set.first} -> #{ns_loc} VS #{ns_set.last}")
63
- end
64
- else
65
- schemata_by_ns[ns_set.first] = ns_set.last
66
- end
67
- end
68
- end
69
- schemata_by_ns.each do |ns_href, ns_file|
70
- @xsd << (ns_href.eql?(target_ns_href) ?
71
- " <xsd:include schemaLocation=\"#{ns_file}\"/>\n" :
72
- " <xsd:import namespace=\"#{ns_href}\" schemaLocation=\"#{ns_file}\"/>\n")
122
+ " targetNamespace=\"#{@ns_href}\"\n" \
123
+ " version=\"1.0\">\n"
124
+
125
+ ## Minimally we need the target namespace location or we have nothing to include
126
+ @xsi_file = schema_locations.delete(@ns_href)
127
+ @xsd << " <xsd:include schemaLocation=\"#{@xsi_file}\"/>\n" unless @xsi_file.nil?
128
+
129
+ ## Now add imports for the other defined schemaLocations
130
+ schema_locations.each do |ns_href, ns_file|
131
+ @xsd << " <xsd:import namespace=\"#{ns_href}\" schemaLocation=\"#{ns_file}\"/>\n"
73
132
  end
74
133
  @xsd << "</xsd:schema>\n"
75
134
 
76
135
  ## Create the Schema objects
77
- @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
78
143
  end
79
144
 
145
+ def errors; @schema.errors; end
146
+
80
147
  def validate
81
- errors = []
82
- @schema.errors.each do |err|
83
- errors << ValidationError.new(:XSD, err)
84
- end
85
148
  errs = @schema.validate(@document)
86
- errs.each do |err|
87
- fname = (err.file.nil?) ? @document.filename : err.file
88
- errors << ValidationError.new(:XML, err, fname)
149
+ errs.map do |err|
150
+ fname = err.file.nil? ? @document.filename : err.file
151
+ ValidationError.new(:XML, err, fname)
89
152
  end
90
- errors
91
153
  end
92
154
  end
93
155
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "validate_xml_xsi"
3
- spec.version = "0.3.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,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validate_xml_xsi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Hansen
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-03-25 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bundler
@@ -52,7 +51,20 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: 1.13.2
55
- description:
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
56
68
  email:
57
69
  - david@hansen4.net
58
70
  executables:
@@ -76,7 +88,6 @@ metadata:
76
88
  homepage_uri: https://github.com/d-hansen/validate_xml_xsi
77
89
  source_code_uri: https://github.com/d-hansen/validate_xml_xsi
78
90
  changelog_uri: https://github.com/d-hansen/validate_xml_xsi/blob/master/CHANGELOG.md
79
- post_install_message:
80
91
  rdoc_options: []
81
92
  require_paths:
82
93
  - lib
@@ -91,8 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
102
  - !ruby/object:Gem::Version
92
103
  version: '0'
93
104
  requirements: []
94
- rubygems_version: 3.3.8
95
- signing_key:
105
+ rubygems_version: 4.0.2
96
106
  specification_version: 4
97
107
  summary: Validate XML against it's embedded XSI elements that define the XSD's.
98
108
  test_files: []