validate_xml_xsi 0.2.4 → 0.4.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: 4252059ac1bc3c6ae4aa11ca57703dea71d54a107483129d76955f371a6b56b7
4
- data.tar.gz: a1c8bf3d962e546e85d8a39a156d562144df7b12a6c38724e74d264287ac304c
3
+ metadata.gz: dbe79de11a54ebfff237869fbef6f4ccb9ce8b063dc3305299cb1fcb7856298b
4
+ data.tar.gz: 549031f8aa72adca7434f9294bfef40e2b2ce70b8980b04960cd20ab320dace4
5
5
  SHA512:
6
- metadata.gz: dbc3ee5c3c8d136b280b8bf7414934c4b8d1e18b2f6ae37a92ff5314bd1ce9028528c69876c64b52ebe8a3d54ed579255c7de327118e5ebaf6b73ccea4b02af1
7
- data.tar.gz: d1524cf149f1439ecab4215100de7718465b5a7147796382ecaf42fd08a63030a584bfe6c39d0f46cf298c07f1128b885f75c78dfe3c0d4566a1a0f47ae4a97c
6
+ metadata.gz: 1336127c5245a94b7a8915f5d87372e17ab52bad43c32d1c07ef165d26db0ca1182baefcc826bd58cd401e213a2f7c04129dfea28d7c950d9fe1e06e2bb6780a
7
+ data.tar.gz: b60f8dcc6904c9b706f338a7afa7438fd98b7029a2a06b9f07010a63e3cddd1f0913bad0bc5ec2d608c3446c137339bb2ae8640251e7b4775ac006b9799e0c75
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
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
+
6
+ ## v0.2.4
7
+
8
+ * Fix some silly syntax errors
9
+
1
10
  ## v0.2.3
2
11
 
3
12
  * Rename XML_XSI::Schema::parse_schema to just XML_XSI::Schema::parse
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,7 @@ require 'validate_xml_xsi'
3
3
 
4
4
  while ARGV.size > 0
5
5
  fname = ARGV.shift
6
- errors = XML_XSI::Schema::validate(fname)
6
+ errors = XML_XSI::Schema::new(XML_XSI.parse(fname)).validate
7
7
  if errors.empty?
8
8
  puts "XML Schema check complete - NO ERRORS!"
9
9
  else
@@ -14,63 +14,119 @@ class XML_XSI
14
14
  class Schema
15
15
  class DocumentError < StandardError; end
16
16
  class ValidationError < StandardError
17
- Params = [:type, :file, :line, :column, :message, :err]
18
- attr_reader *Params, :description
19
- def initialize(*args)
20
- Params.each.with_index { |param, idx| instance_variable_set("@#{param.to_s}", args[idx]) }
21
- @description = "#{@type.to_s} ERROR [#{@file}:#{@line}:#{@column}]: #{@message}".freeze
17
+ attr_reader :type, :file, :line, :column, :level, :message, :description, :error
18
+ def initialize(type, err, filename = nil)
19
+ @type = type
20
+ @error = err
21
+ @file = filename.nil? ? @error.file : filename
22
+ @line = @error.line
23
+ @column = @error.column
24
+ if /((?:ERROR)|(?:WARNING)): /.match(@error.message)
25
+ @level = $1
26
+ @message = $'
27
+ @description = "#{@type.to_s} #{@level.to_s} [#{@file}:#{@line}:#{@column}]: #{@message}".freeze
28
+ else
29
+ @level = ''
30
+ @message = @error.message
31
+ @description = "#{@type.to_s} #{@message}".freeze
32
+ end
22
33
  super(@description)
23
34
  end
24
35
  end
25
36
 
26
- def self.parse(xml_doc)
37
+ attr_reader :xsd
38
+ def initialize(xml_doc, parent_xml_doc = nil)
27
39
  unless xml_doc.is_a?(Nokogiri::XML::Document)
28
- raise DocumentError.new("not a Nokogiri::XML::Document (class: #{xml_doc.class.name})!")
40
+ raise DocumentError.new("invalid Nokogiri::XML::Document - #{xml_doc.class.name}")
41
+ 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
+ @document = xml_doc
46
+ ## Determine default/top/root namespace
47
+ target_ns_href = nil
48
+ @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!")
29
53
  end
54
+
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))
58
+
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
71
+
30
72
  ## Build an all-in-one XSD document that imports all of the separate schema locations
31
- xsd_doc = "<?xml version=\"1.0\"?>\n"
32
- xsd_doc << "<xsd:schema targetNamespace=\"http://www.w3.orig/XML/1998/namespace\"\n" \
33
- " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n" \
73
+ @xsd = "<?xml version=\"1.0\"?>\n"
74
+ @xsd << "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n" \
75
+ " targetNamespace=\"#{target_ns_href}\"\n" \
34
76
  " version=\"1.0\">\n"
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
- schemata_by_ns = {}
38
- xml_doc.search('//*[@xsi:schemaLocation]').each do |elem|
39
- elem['xsi:schemaLocation'].scan(/(\S+)\s+(\S+)/).each do |ns_set|
40
- if ns_loc = schemata_by_ns[ns_set.first]
41
- unless ns_loc.eql?(ns_set.last)
42
- raise DocumentError.new("MISMATCHING XMLNS XSI: #{ns_set.first} -> #{ns_loc} VS #{ns_set.last}")
43
- end
44
- else
45
- schemata_by_ns[ns_set.first] = ns_set.last
46
- end
47
- end
48
- end
49
- schemata_by_ns.each do |ns_href, ns_file|
50
- xsd_doc << " <xsd:import namespace=\"#{ns_href}\" schemaLocation=\"#{ns_file}\"/>\n"
77
+
78
+ ## 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?
81
+
82
+ ## Now add imports for the other defined schemaLocations
83
+ schema_locations.each do |ns_href, ns_file|
84
+ @xsd << " <xsd:import namespace=\"#{ns_href}\" schemaLocation=\"#{ns_file}\"/>\n"
51
85
  end
52
- xsd_doc << "</xsd:schema>\n"
53
- Nokogiri::XML::Schema.new(xsd_doc)
86
+ @xsd << "</xsd:schema>\n"
87
+
88
+ ## Create the Schema objects
89
+ @schema = Nokogiri::XML::Schema.new(@xsd)
54
90
  end
55
91
 
56
- def self.validate(xml_doc, xsd = nil)
57
- xml_doc = XML_XSI::parse(xml_doc) unless xml_doc.is_a?(Nokogiri::XML::Document)
92
+ def validate
58
93
  errors = []
59
- unless xsd.nil? || xsd.is_a?(Nokogiri::XML::Schema)
60
- raise DocumentError.new("Invalid XSD - not a Nokogiri::XML::Schema (class: #{xsd.class.name})!")
94
+ @schema.errors.each do |err|
95
+ errors << ValidationError.new(:XSD, err)
61
96
  end
62
- xsd = self.parse(xml_doc) if xsd.nil?
63
- xsd.errors.each do |err|
64
- err_msg = (/ERROR: /.match(err.message)) ? $' : err.message
65
- errors << ValidationError.new(:XSD, err.file, err.line, err.column, err_msg, err)
66
- end
67
- errs = xsd.validate(xml_doc)
97
+ errs = @schema.validate(@document)
68
98
  errs.each do |err|
69
- err_msg = (/ERROR: /.match(err.message)) ? $' : err.message
70
- fname = (err.file.nil?) ? xml_doc.filename : err.file
71
- errors << ValidationError.new(:XML, fname, err.line, err.column, err_msg, err)
99
+ fname = (err.file.nil?) ? @document.filename : err.file
100
+ errors << ValidationError.new(:XML, err, fname)
72
101
  end
73
102
  errors
74
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
128
+ end
129
+ schema_locations
130
+ end
75
131
  end
76
132
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "validate_xml_xsi"
3
- spec.version = "0.2.4"
3
+ spec.version = "0.4.0"
4
4
  spec.authors = ["David Hansen"]
5
5
  spec.email = ["david@hansen4.net"]
6
6
 
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.2.4
4
+ version: 0.4.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-21 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,6 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: 1.13.2
55
- description:
56
54
  email:
57
55
  - david@hansen4.net
58
56
  executables:
@@ -76,7 +74,6 @@ metadata:
76
74
  homepage_uri: https://github.com/d-hansen/validate_xml_xsi
77
75
  source_code_uri: https://github.com/d-hansen/validate_xml_xsi
78
76
  changelog_uri: https://github.com/d-hansen/validate_xml_xsi/blob/master/CHANGELOG.md
79
- post_install_message:
80
77
  rdoc_options: []
81
78
  require_paths:
82
79
  - lib
@@ -91,8 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
88
  - !ruby/object:Gem::Version
92
89
  version: '0'
93
90
  requirements: []
94
- rubygems_version: 3.3.8
95
- signing_key:
91
+ rubygems_version: 4.0.2
96
92
  specification_version: 4
97
93
  summary: Validate XML against it's embedded XSI elements that define the XSD's.
98
94
  test_files: []