xsd 2.1.0 → 2.3.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: d3b12830c536c37e72b109727f470d45de29b6c852e62476cd3aa5578dd3b9c7
4
- data.tar.gz: 248b65a3794a6c83a6aca695b0d565d55b971991272fd5f7f0a8a841fb8847ec
3
+ metadata.gz: dce377d21632c6a26b8cf654ec519d7fa7664a6518b136280433a1411580159c
4
+ data.tar.gz: 57ff6cd7eb787f93d1ab51b8ebcc170da4f9726105a584a3a9d69f24711fdd0d
5
5
  SHA512:
6
- metadata.gz: ac15d2c2c163bd70f007fd76b5c007fbc786ed09d5609a2d6dba5aa8836144917a5159a32c1b7f47aa250bd18034d8339f445180fc4cd330745138ec60283e32
7
- data.tar.gz: 7db0b9ac2ba0c97aab16d84db5145b4a8a18c901f3c24c8fd13a73b96a1998083a41cff38c3b0f7098dc53457b693e0ee5d3d941ceb1712f38a54924d32ce544
6
+ metadata.gz: 95c494a1699eee86c90989c1de42ef8f1532ea805273e1f35b2b7f2f6d68cc74f4a056ba1c71b5b3f3e9b7b80d9500b206fc684d4bddf4cb3563dda59d21fc48
7
+ data.tar.gz: d58a3a37ebd8104cbcd58eb416f127bd862f2c9e510bafafc83ffbefb257ce5ef1093d56db8a6ad0682137fbe9de04d67fa8d0e5d2b32f38a56e1b1a3fe0e0cc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.3.0] - 2023-09-06
4
+
5
+ - Fixed reading of SimpleType in List
6
+ - Add support for Restriction without base
7
+ - Support Any generation in simple scenarios
8
+
9
+ ## [2.2.0] - 2023-07-10
10
+
11
+ - Fixed built-in types detection for unprefixed schema namespace
12
+ - Move validation to Schema object
13
+
3
14
  ## [2.1.0] - 2023-07-10
4
15
 
5
16
  - Add support for <xs:include> element
data/Gemfile CHANGED
@@ -12,4 +12,5 @@ group :development do
12
12
  gem 'rubocop-performance', '~> 1.17'
13
13
  gem 'rubocop-rake', '~> 0.6'
14
14
  gem 'rubocop-rspec', '~> 2.10'
15
+ gem 'wasabi', '~> 4.0'
15
16
  end
@@ -32,14 +32,6 @@ module XSD
32
32
  end
33
33
  end
34
34
 
35
- # Optional. Specifies a unique ID for the element
36
- # @!attribute id
37
- # @return String
38
- # property :id, :string
39
- def id
40
- node['id']
41
- end
42
-
43
35
  def initialize(options = {})
44
36
  @options = options
45
37
  @cache = {}
@@ -53,6 +45,20 @@ module XSD
53
45
  options[:node]
54
46
  end
55
47
 
48
+ # Get object string representation
49
+ # @return String
50
+ def inspect
51
+ "#<#{self.class.name} path=#{node.path}>"
52
+ end
53
+
54
+ # Optional. Specifies a unique ID for the element
55
+ # @!attribute id
56
+ # @return String
57
+ # property :id, :string
58
+ def id
59
+ node['id']
60
+ end
61
+
56
62
  # Get current namespaces
57
63
  # @return Hash
58
64
  def namespaces
@@ -62,20 +68,20 @@ module XSD
62
68
  # Get child nodes
63
69
  # @param [Symbol] name
64
70
  # @return Nokogiri::XML::NodeSet
65
- def nodes(name = :*)
66
- node.xpath("./xs:#{name}", { 'xs' => XML_SCHEMA })
71
+ def nodes(name = :*, deep = false)
72
+ node.xpath("./#{deep ? '/' : ''}xs:#{name}", { 'xs' => XML_SCHEMA })
67
73
  end
68
74
 
69
- # Get schema by namespace or namespace prefix
70
- # @param [String, nil] namespace
75
+ # Get schemas by namespace or prefix
76
+ # @param [String, nil] ns_or_prefix
71
77
  # @return Array<Schema>
72
- def schemas_for_namespace(namespace)
73
- if schema.targets_namespace?(namespace)
78
+ def schemas_for_namespace(ns_or_prefix)
79
+ if schema.targets_namespace?(ns_or_prefix)
74
80
  [schema, *schema.includes.map(&:imported_schema)]
75
- elsif (import = schema.import_by_namespace(namespace))
81
+ elsif (import = schema.import_by_namespace(ns_or_prefix))
76
82
  [import.imported_schema]
77
83
  else
78
- raise Error, "Schema not found for namespace '#{namespace}' in '#{schema.id || schema.target_namespace}'"
84
+ raise Error, "Schema not found for namespace '#{ns_or_prefix}' in '#{schema.id || schema.target_namespace}'"
79
85
  end
80
86
  end
81
87
 
@@ -187,7 +193,7 @@ module XSD
187
193
  # @param [Nokogiri::XML::Node] node
188
194
  # @return Array<String>
189
195
  def documentation_for(node)
190
- node.xpath('./xs:annotation/xs:documentation/text()', { 'xs' => XML_SCHEMA }).map(&:to_s).map(&:strip)
196
+ node.xpath('./xs:annotation/xs:documentation/text()', { 'xs' => XML_SCHEMA }).map { |x| x.to_s.strip }
191
197
  end
192
198
 
193
199
  # Get all available elements on the current stack level
@@ -315,6 +321,9 @@ module XSD
315
321
  name = link[:property] ? send(link[:property]) : nil
316
322
  if name
317
323
  return @cache[method] = object_by_name(link[:type], name)
324
+ elsif is_a?(Restriction) && method == :base_simple_type
325
+ # handle restriction without base
326
+ return nil
318
327
  end
319
328
  end
320
329
 
@@ -346,5 +355,12 @@ module XSD
346
355
  name.to_sym
347
356
  end
348
357
  end
358
+
359
+ # Return string if it is not empty, or nil otherwise
360
+ # @param [String, nil] string
361
+ # @return String, nil
362
+ def nil_if_empty(string)
363
+ string&.empty? ? nil : string
364
+ end
349
365
  end
350
366
  end
data/lib/xsd/generator.rb CHANGED
@@ -59,13 +59,13 @@ module XSD
59
59
  # configure namespaces
60
60
  # TODO: попытаться использовать collect_namespaces?
61
61
  attributes = {}
62
- collect_attributes = element.collect_attributes
62
+ all_attributes = element.collect_attributes
63
63
  if element.complex?
64
- collect_elements = element.collect_elements
64
+ all_elements = element.collect_elements
65
65
 
66
66
  # get namespaces for current element and it's children
67
67
  prefix = nil
68
- [*collect_elements, element].each do |elem|
68
+ [*all_elements, element].each do |elem|
69
69
  prefix = get_namespace_prefix(elem, attributes, namespaces)
70
70
  end
71
71
  else
@@ -75,7 +75,7 @@ module XSD
75
75
  # iterate through each item
76
76
  data.each do |item|
77
77
  # prepare attributes
78
- collect_attributes.each do |attribute|
78
+ all_attributes.each do |attribute|
79
79
  value = item["@#{attribute.name}"]
80
80
  if value
81
81
  attributes[attribute.name] = value
@@ -88,13 +88,18 @@ module XSD
88
88
  if element.complex?
89
89
  # generate tag recursively
90
90
  xml.tag!("#{prefix}:#{element.name}", attributes) do
91
- collect_elements.each do |elem|
91
+ all_elements.each do |elem|
92
92
  build_element(xml, elem, item, namespaces.dup)
93
93
  end
94
94
  end
95
95
  else
96
- value = item.is_a?(Hash) ? item['#text'] : item
97
- xml.tag!("#{prefix}:#{element.name}", attributes, (value == '' ? nil : value))
96
+ has_text = item.is_a?(Hash)
97
+ if has_text && element.complex_type&.nodes(:any, true)&.any?
98
+ xml.tag!("#{prefix}:#{element.name}", attributes) { |res| res << item['#text'].to_s }
99
+ else
100
+ value = has_text ? item['#text'] : item
101
+ xml.tag!("#{prefix}:#{element.name}", attributes, (value == '' ? nil : value))
102
+ end
98
103
  end
99
104
  end
100
105
  end
@@ -108,7 +113,7 @@ module XSD
108
113
  namespace = (element.referenced? ? element.reference : element).target_namespace
109
114
  prefix = namespaces.key(namespace)
110
115
  unless prefix
111
- prefix = "tns#{@namespace_index += 1}"
116
+ prefix = "n#{@namespace_index += 1}"
112
117
  namespaces[prefix] = attributes["xmlns:#{prefix}"] = namespace
113
118
  end
114
119
 
@@ -119,7 +124,7 @@ module XSD
119
124
  # @param [String, Array<String>, nil] lookup
120
125
  def find_root_element(lookup)
121
126
  if lookup
122
- element = schema[*lookup]
127
+ element = self[*lookup]
123
128
  raise Error, "Cant find start element #{lookup}" unless element.is_a?(Element)
124
129
 
125
130
  element
@@ -5,7 +5,7 @@ module XSD
5
5
  # Parent elements: simpleType
6
6
  # https://www.w3schools.com/xml/el_list.asp
7
7
  class List < BaseObject
8
- TYPE_PROPERTY = :itemType
8
+ TYPE_PROPERTY = :item_type
9
9
 
10
10
  include SimpleTyped
11
11
 
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tmpdir'
4
+ require 'securerandom'
5
+
3
6
  module XSD
4
7
  # The schema element defines the root element of a schema.
5
8
  # Parent elements: NONE
@@ -108,23 +111,23 @@ module XSD
108
111
  attributes
109
112
  end
110
113
 
111
- # Get target namespace prefix. There may be more than one prefix, but we return only first defined
112
- # @return String
114
+ # Get target namespace prefix
115
+ # @return String, nil
113
116
  def target_namespace_prefix
114
- @target_namespace_prefix ||= namespaces.key(target_namespace)&.sub(/^xmlns:?/, '') || ''
117
+ nil_if_empty(@target_namespace_prefix ||= namespaces.key(target_namespace)&.sub(/^xmlns:?/, '') || '')
115
118
  end
116
119
 
117
120
  # Get schema namespace prefix
118
- # @return String
121
+ # @return String, nil
119
122
  def namespace_prefix
120
- @namespace_prefix ||= namespaces.key(XML_SCHEMA).sub(/^xmlns:?/, '')
123
+ nil_if_empty(@namespace_prefix ||= namespaces.key(XML_SCHEMA).sub(/^xmlns:?/, ''))
121
124
  end
122
125
 
123
126
  # Check if namespace is a target namespace
124
127
  # @param [String, nil] namespace
125
128
  # @return Boolean
126
129
  def targets_namespace?(namespace)
127
- namespace == target_namespace || namespaces[namespace.empty? ? 'xmlns' : "xmlns:#{namespace}"] == target_namespace
130
+ namespace == target_namespace || namespaces[namespace.nil? ? 'xmlns' : "xmlns:#{namespace}"] == target_namespace
128
131
  end
129
132
 
130
133
  # Override map_children on schema to get objects from all loaded schemas
@@ -151,11 +154,121 @@ module XSD
151
154
  end.compact.flatten
152
155
  end
153
156
 
154
- # Get import by namespace
157
+ # Get import by namespace or prefix
158
+ # @param [String, nil] ns_or_prefix
155
159
  # @return Import
156
- def import_by_namespace(ns)
157
- aliases = [ns, namespaces["xmlns:#{(ns || '').gsub(/^xmlns:/, '')}"], reader.namespace_prefixes[ns]].compact
160
+ def import_by_namespace(ns_or_prefix)
161
+ aliases = [
162
+ ns_or_prefix,
163
+ namespaces["xmlns:#{(ns_or_prefix || '').gsub(/^xmlns:/, '')}"]
164
+ ].compact
165
+
158
166
  imports.find { |import| aliases.include?(import.namespace) }
159
167
  end
168
+
169
+ # Validate XML against current schema
170
+ # @param [String, Pathname, Nokogiri::XML::Document] xml
171
+ def validate_xml(xml)
172
+ # validate input
173
+ raise ValidationError unless xml.is_a?(Nokogiri::XML::Document) || xml.is_a?(Pathname) || xml.is_a?(String)
174
+
175
+ begin
176
+ document = xml.is_a?(Nokogiri::XML::Document) ? xml : Nokogiri::XML(xml)
177
+ rescue Nokogiri::XML::SyntaxError => e
178
+ raise ValidationError, e
179
+ end
180
+
181
+ errors = schema_validator.validate(document)
182
+ raise ValidationError, errors.map(&:message).join('; ') if errors.any?
183
+ end
184
+
185
+ # Validate current schema against XMLSchema 1.0
186
+ def validate
187
+ begin
188
+ schema_validator
189
+ rescue Nokogiri::XML::SyntaxError => e
190
+ # TODO: display import map name for imported_xsd
191
+ message = e.message + (e.file ? " in file '#{File.basename(e.file)}'" : '')
192
+ raise ValidationError, message
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ # Get Nokogiri::XML::Schema object to validate against
199
+ # @return Nokogiri::XML::Schema
200
+ def schema_validator
201
+ return @schema_validator if @schema_validator
202
+
203
+ # if !imported_xsd.empty?
204
+ # imports are explicitly provided - put all files in one tmpdir and update import paths appropriately
205
+ # TODO: save file/path map to display in errors
206
+ Dir.mktmpdir('XSD', reader.tmp_dir) do |dir|
207
+ # create primary xsd file
208
+ file = "#{::SecureRandom.urlsafe_base64}.xsd"
209
+
210
+ # create imported xsd files
211
+ recursive_import_xsd(self, file, Set.new) do |f, data|
212
+ File.write("#{dir}/#{f}", data)
213
+ end
214
+
215
+ # read schema from tmp file descriptor
216
+ io = File.open("#{dir}/#{file}")
217
+ @schema_validator = create_xml_schema(io)
218
+ end
219
+ # else
220
+ # @schema_validator = create_xml_schema(schema.node.to_xml)
221
+ # end
222
+
223
+ @schema_validator
224
+ end
225
+
226
+ # Сформировать имена файлов и содержимое XSD схем для корректной валидации
227
+ # @param [Schema] schema
228
+ # @param [String] file
229
+ # @param [Set] processed
230
+ def recursive_import_xsd(schema, file, processed, &block)
231
+ # handle recursion
232
+ return if processed.include?(schema.target_namespace)
233
+
234
+ processed.add(schema.target_namespace)
235
+
236
+ # prepare schema XML with all namespaces included, clone node to avoid mutating original schema
237
+ node = schema.node
238
+ if node.namespaces.size != node.namespace_definitions.size
239
+ prefixes = node.namespace_definitions.map(&:prefix)
240
+ node = schema.node.dup
241
+
242
+ schema.node.namespaces.each do |attr, ns|
243
+ prefix = attr == 'xmlns' ? nil : attr.sub('xmlns:', '')
244
+ # does not work!
245
+ # node.add_namespace_definition(prefix, ns) unless prefixes.include?(prefix)
246
+ node[attr] = ns unless prefixes.include?(prefix)
247
+ end
248
+ end
249
+
250
+ data = node.to_xml
251
+
252
+ schema.imports.each do |import|
253
+ name = "#{::SecureRandom.urlsafe_base64}.xsd"
254
+ location = import.schema_location
255
+ namespace = import.namespace
256
+
257
+ if location
258
+ data = data.sub("schemaLocation=\"#{location}\"", "schemaLocation=\"#{name}\"")
259
+ else
260
+ data = data.sub("namespace=\"#{namespace}\"", "namespace=\"#{namespace}\" schemaLocation=\"#{name}\"")
261
+ end
262
+ recursive_import_xsd(import.imported_schema, name, processed, &block)
263
+ end
264
+
265
+ block.call(file, data)
266
+ end
267
+
268
+ # Create Nokogiri XML Schema instance with preconfigured options
269
+ # @param [IO, String] io
270
+ def create_xml_schema(io)
271
+ Nokogiri::XML::Schema(io, Nokogiri::XML::ParseOptions.new.nononet)
272
+ end
160
273
  end
161
274
  end
data/lib/xsd/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module XSD
4
- VERSION = '2.1.0'
4
+ VERSION = '2.3.0'
5
5
  end
data/lib/xsd/xml.rb CHANGED
@@ -6,9 +6,8 @@ require 'net/http'
6
6
  module XSD
7
7
  class XML
8
8
  include Generator
9
- include Validator
10
9
 
11
- attr_reader :options, :object_cache, :schemas, :namespace_prefixes
10
+ attr_reader :options, :object_cache, :schemas
12
11
 
13
12
  DEFAULT_RESOURCE_RESOLVER = proc do |location, namespace|
14
13
  if location =~ /^https?:/
@@ -75,10 +74,9 @@ module XSD
75
74
  end
76
75
 
77
76
  def initialize(**options)
78
- @options = options
79
- @object_cache = {}
80
- @schemas = []
81
- @namespace_prefixes = {}
77
+ @options = options
78
+ @object_cache = {}
79
+ @schemas = []
82
80
  end
83
81
 
84
82
  def logger
@@ -116,24 +114,16 @@ module XSD
116
114
  new_schema
117
115
  end
118
116
 
119
- # Add prefixes defined outside of processed schemas, for example in WSDL document
120
- # @param [String] prefix
121
- # @param [String] namespace
122
- def add_namespace_prefix(prefix, namespace)
123
- @namespace_prefixes[prefix] = namespace
124
- end
125
-
126
117
  # Get first added (considered primary) schema
127
118
  # @return Schema, nil
128
119
  def schema
129
120
  schemas.first
130
121
  end
131
122
 
132
- # Get schema by namespace or namespace prefix
123
+ # Get schemas by namespace
133
124
  # @param [String, nil] namespace
134
125
  # @return Array<Schema>
135
126
  def schemas_for_namespace(namespace)
136
- namespace = namespace_prefixes[namespace] if namespace_prefixes.key?(namespace)
137
127
  schemas.select { |schema| schema.target_namespace == namespace }
138
128
  end
139
129
 
data/lib/xsd.rb CHANGED
@@ -38,6 +38,5 @@ require 'xsd/objects/any_attribute'
38
38
  require 'xsd/objects/key'
39
39
  require 'xsd/objects/keyref'
40
40
  require 'xsd/generator'
41
- require 'xsd/validator'
42
41
  require 'xsd/exceptions'
43
42
  require 'xsd/xml'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xsd
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - d.arkhipov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-10 00:00:00.000000000 Z
11
+ date: 2023-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: builder
@@ -109,7 +109,6 @@ files:
109
109
  - lib/xsd/shared/min_max_occurs.rb
110
110
  - lib/xsd/shared/referenced.rb
111
111
  - lib/xsd/shared/simple_typed.rb
112
- - lib/xsd/validator.rb
113
112
  - lib/xsd/version.rb
114
113
  - lib/xsd/xml.rb
115
114
  - xsd.gemspec
@@ -135,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
134
  - !ruby/object:Gem::Version
136
135
  version: '0'
137
136
  requirements: []
138
- rubygems_version: 3.1.6
137
+ rubygems_version: 3.4.10
139
138
  signing_key:
140
139
  specification_version: 4
141
140
  summary: The Ruby XSD library is an XML Schema implementation for Ruby.
data/lib/xsd/validator.rb DELETED
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module XSD
4
- module Validator
5
- # Validate XML against XSD
6
- # @param [String, Pathname, Nokogiri::XML::Document] xml
7
- def validate_xml(xml)
8
- # validate input
9
- raise ValidationError unless xml.is_a?(Nokogiri::XML::Document) || xml.is_a?(Pathname) || xml.is_a?(String)
10
-
11
- begin
12
- document = xml.is_a?(Nokogiri::XML::Document) ? xml : Nokogiri::XML(xml)
13
- rescue Nokogiri::XML::SyntaxError => e
14
- raise ValidationError, e
15
- end
16
-
17
- errors = schema_validator.validate(document)
18
- raise ValidationError, errors.map(&:message).join('; ') if errors.any?
19
- end
20
-
21
- # Validate XSD against another XSD (by default uses XMLSchema 1.0)
22
- def validate
23
- begin
24
- schema_validator
25
- rescue Nokogiri::XML::SyntaxError => e
26
- # TODO: display import map name for imported_xsd
27
- message = e.message + (e.file ? " in file '#{File.basename(e.file)}'" : '')
28
- raise ValidationError, message
29
- end
30
- end
31
-
32
- private
33
-
34
- # Get Nokogiri::XML::Schema object to validate against
35
- # @return Nokogiri::XML::Schema
36
- def schema_validator
37
- return @schema_validator if @schema_validator
38
-
39
- if !imported_xsd.empty?
40
- # imports are explicitly provided - put all files in one tmpdir and update import paths appropriately
41
- # TODO: save file/path map to display in errors
42
- Dir.mktmpdir('XSD', tmp_dir) do |dir|
43
- # create primary xsd file
44
- file = "#{SecureRandom.urlsafe_base64}.xsd"
45
-
46
- # create imported xsd files
47
- recursive_import_xsd(self, file, Set.new) do |f, data|
48
- File.write("#{dir}/#{f}", data)
49
- end
50
-
51
- # read schema from tmp file descriptor
52
- io = File.open("#{dir}/#{file}")
53
- @schema_validator = create_xml_schema(io)
54
- end
55
- else
56
- @schema_validator = create_xml_schema(self.xsd)
57
- end
58
-
59
- @schema_validator
60
- end
61
-
62
- # Сформировать имена файлов и содержимое XSD схем для корректной валидации
63
- # @param [XML] reader
64
- # @param [String] file
65
- # @param [Set] processed
66
- def recursive_import_xsd(reader, file, processed, &block)
67
- # handle recursion
68
- namespace = reader.schema.target_namespace
69
- return if processed.include?(namespace)
70
-
71
- processed.add(namespace)
72
-
73
- data = reader.xml
74
-
75
- reader.imports.each do |import|
76
- name = "#{SecureRandom.urlsafe_base64}.xsd"
77
- data = data.sub("schemaLocation=\"#{import.schema_location}\"", "schemaLocation=\"#{name}\"")
78
- recursive_import_xsd(import.imported_reader, name, processed, &block)
79
- end
80
-
81
- block.call(file, data)
82
- end
83
-
84
- # Create Nokogiri XML Schema instance with preconfigured options
85
- # @param [IO] io
86
- def create_xml_schema(io)
87
- Nokogiri::XML::Schema(io, Nokogiri::XML::ParseOptions.new.nononet)
88
- end
89
- end
90
- end