xsd 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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