xsd 2.4.3 → 2.6.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 +4 -4
- data/CHANGELOG.md +6 -1
- data/README.md +10 -2
- data/lib/xsd/base_object.rb +64 -40
- data/lib/xsd/objects/attribute.rb +13 -2
- data/lib/xsd/objects/attribute_group.rb +3 -2
- data/lib/xsd/objects/complex_type.rb +52 -1
- data/lib/xsd/objects/element.rb +40 -9
- data/lib/xsd/objects/simple_type.rb +23 -2
- data/lib/xsd/shared/named.rb +17 -0
- data/lib/xsd/version.rb +1 -1
- data/lib/xsd/xml.rb +0 -2
- data/lib/xsd.rb +1 -1
- metadata +3 -3
- data/lib/xsd/generator.rb +0 -146
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a5aa03e831e5cf4e88780b18a4ef57217eff54a14e2138669672f33486ba8bd
|
4
|
+
data.tar.gz: b6a9ab392e47192a6a286ffa7dc97c5f624c78d9ab117bdea67e11f801c39363
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a031bc3a3947708a8f6f8e4add4040853f9f8b62cd3465de56421600d9fc2d2b7c45b163efe239c7486dce05bf0427d8f380e119673b644739f736b4598b5276
|
7
|
+
data.tar.gz: ad7bbd0268373292a291935599749dc9937784438b398958acf592db4e1648967106f45c1dd402c82f3cdd534f2fe0e177a1250e91f65872fc3aa9d9a8b416a5
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -28,8 +28,13 @@ require 'xsd'
|
|
28
28
|
# Load ruby-xsd
|
29
29
|
reader = XSD::XML.open('some.xsd')
|
30
30
|
|
31
|
-
# Get
|
31
|
+
# Get element by name with hash lookup
|
32
32
|
element = reader['NewReleaseMessage']
|
33
|
+
|
34
|
+
# Get element by namespace + name with hash lookup
|
35
|
+
element = reader['{http://ddex.net/xml/ern/36}NewReleaseMessage']
|
36
|
+
|
37
|
+
# Get element child elements
|
33
38
|
element.collect_elements.map(&:name) # => ['MessageHeader', 'UpdateIndicator', 'IsBackfill', 'CatalogTransfer', 'WorkList', 'CueSheetList', 'ResourceList', 'CollectionList', 'ReleaseList', 'DealList']
|
34
39
|
|
35
40
|
# Get attributes
|
@@ -38,6 +43,7 @@ attribute = reader['NewReleaseMessage']['@MessageSchemaVersionId']
|
|
38
43
|
# Get attribute information
|
39
44
|
attribute.name # => 'MessageSchemaVersionId'
|
40
45
|
attribute.type # => 'xs:string'
|
46
|
+
attribute.data_type # => 'string'
|
41
47
|
attribute.required? # => true
|
42
48
|
attribute.optional? # => false
|
43
49
|
attribute.prohibited? # => true
|
@@ -48,7 +54,9 @@ element.min_occurs # => 0
|
|
48
54
|
element.max_occurs # => :unbounded
|
49
55
|
element.type # => 'ern:SoundRecording'
|
50
56
|
element.complex_type # => XSD::ComplexType
|
51
|
-
element.
|
57
|
+
element.complex_content? # => true
|
58
|
+
element.simple_content? # => false
|
59
|
+
element.mixed_content? # => false
|
52
60
|
element.multiple_allowed? # => true
|
53
61
|
element.required? # => false
|
54
62
|
```
|
data/lib/xsd/base_object.rb
CHANGED
@@ -34,7 +34,7 @@ module XSD
|
|
34
34
|
|
35
35
|
def initialize(options = {})
|
36
36
|
@options = options
|
37
|
-
@cache
|
37
|
+
@cache = {}
|
38
38
|
|
39
39
|
raise Error, "#{self.class}.new expects a hash parameter" unless @options.is_a?(Hash)
|
40
40
|
end
|
@@ -100,9 +100,9 @@ module XSD
|
|
100
100
|
curname = curname.to_s
|
101
101
|
|
102
102
|
if curname[0] == '@'
|
103
|
-
result = result.collect_attributes.find { |attr| attr
|
103
|
+
result = result.collect_attributes.find { |attr| definition_match?(attr, curname[1..]) }
|
104
104
|
else
|
105
|
-
result = result.collect_elements.find { |elem| elem
|
105
|
+
result = result.collect_elements.find { |elem| definition_match?(elem, curname) }
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
@@ -116,7 +116,7 @@ module XSD
|
|
116
116
|
def object_by_name(node_name, name)
|
117
117
|
# get prefix and local name
|
118
118
|
name_prefix = get_prefix(name)
|
119
|
-
name_local
|
119
|
+
name_local = strip_prefix(name)
|
120
120
|
|
121
121
|
# do not search for built-in types
|
122
122
|
return nil if schema.namespace_prefix == name_prefix
|
@@ -126,7 +126,7 @@ module XSD
|
|
126
126
|
|
127
127
|
# find element in target schema
|
128
128
|
search_schemas.each do |s|
|
129
|
-
node = s.node.
|
129
|
+
node = s.node.at_xpath("./xs:#{node_name}[@name=\"#{name_local}\"]", { 'xs' => XML_SCHEMA })
|
130
130
|
return s.node_to_object(node) if node
|
131
131
|
end
|
132
132
|
|
@@ -138,7 +138,6 @@ module XSD
|
|
138
138
|
# @return BaseObject
|
139
139
|
def node_to_object(node)
|
140
140
|
# check object in cache first
|
141
|
-
# TODO: проверить работу!
|
142
141
|
return reader.object_cache[node.object_id] if reader.object_cache[node.object_id]
|
143
142
|
|
144
143
|
klass = XML::CLASS_MAP[node.name]
|
@@ -203,43 +202,49 @@ module XSD
|
|
203
202
|
# Get all available elements on the current stack level
|
204
203
|
# @return Array<Element>
|
205
204
|
def collect_elements(*)
|
206
|
-
|
207
|
-
return [] if NO_ELEMENTS_CONTAINER.include?(self.class.mapped_name)
|
205
|
+
return @collect_elements if @collect_elements
|
208
206
|
|
209
|
-
if
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
map_children(:*).map do |obj|
|
214
|
-
if obj.is_a?(Element)
|
215
|
-
obj
|
207
|
+
r = if NO_ELEMENTS_CONTAINER.include?(self.class.mapped_name)
|
208
|
+
[]
|
209
|
+
elsif is_a?(Referenced) && ref
|
210
|
+
reference.collect_elements
|
216
211
|
else
|
217
|
-
#
|
218
|
-
(
|
212
|
+
# map children recursive
|
213
|
+
map_children(:*).map do |obj|
|
214
|
+
if obj.is_a?(Element)
|
215
|
+
obj
|
216
|
+
else
|
217
|
+
# get elements considering references
|
218
|
+
(obj.is_a?(Referenced) && obj.ref ? obj.reference : obj).collect_elements
|
219
|
+
end
|
220
|
+
end.flatten
|
219
221
|
end
|
220
|
-
|
221
|
-
|
222
|
+
|
223
|
+
@collect_elements = r
|
222
224
|
end
|
223
225
|
|
224
226
|
# Get all available attributes on the current stack level
|
225
227
|
# @return Array<Attribute>
|
226
228
|
def collect_attributes(*)
|
227
|
-
|
228
|
-
return [] if NO_ATTRIBUTES_CONTAINER.include?(self.class.mapped_name)
|
229
|
+
return @collect_attributes if @collect_attributes
|
229
230
|
|
230
|
-
if
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
map_children(:*).map do |obj|
|
235
|
-
if obj.is_a?(Attribute)
|
236
|
-
obj
|
231
|
+
r = if NO_ATTRIBUTES_CONTAINER.include?(self.class.mapped_name)
|
232
|
+
[]
|
233
|
+
elsif is_a?(Referenced) && ref
|
234
|
+
reference.collect_attributes
|
237
235
|
else
|
238
|
-
#
|
239
|
-
(
|
236
|
+
# map children recursive
|
237
|
+
map_children(:*).map do |obj|
|
238
|
+
if obj.is_a?(Attribute)
|
239
|
+
obj
|
240
|
+
else
|
241
|
+
# get attributes considering references
|
242
|
+
(obj.is_a?(Referenced) && obj.ref ? obj.reference : obj).collect_attributes
|
243
|
+
end
|
244
|
+
end.flatten
|
240
245
|
end
|
241
|
-
|
242
|
-
|
246
|
+
|
247
|
+
@collect_attributes = r
|
243
248
|
end
|
244
249
|
|
245
250
|
# Get reader instance
|
@@ -260,8 +265,8 @@ module XSD
|
|
260
265
|
# @param [Hash] options
|
261
266
|
def self.property(name, type, options = {}, &block)
|
262
267
|
properties[to_underscore(name)] = {
|
263
|
-
name:
|
264
|
-
type:
|
268
|
+
name: name,
|
269
|
+
type: type,
|
265
270
|
resolve: block,
|
266
271
|
**options
|
267
272
|
}
|
@@ -298,7 +303,7 @@ module XSD
|
|
298
303
|
|
299
304
|
# check for property first
|
300
305
|
if (property = self.class.properties[method])
|
301
|
-
value
|
306
|
+
value = property[:resolve] ? property[:resolve].call(self) : node[property[:name].to_s]
|
302
307
|
result = if value.nil?
|
303
308
|
# if object has reference - search property in referenced object
|
304
309
|
node['ref'] ? reference.send(method, *args) : property[:default]
|
@@ -307,7 +312,7 @@ module XSD
|
|
307
312
|
when :integer
|
308
313
|
property[:name] == :maxOccurs && value == 'unbounded' ? :unbounded : value.to_i
|
309
314
|
when :boolean
|
310
|
-
|
315
|
+
value == 'true'
|
311
316
|
else
|
312
317
|
value
|
313
318
|
end
|
@@ -354,10 +359,10 @@ module XSD
|
|
354
359
|
def self.mapped_name
|
355
360
|
# @mapped_name ||= XML::CLASS_MAP.each { |k, v| return k.to_sym if v == self }
|
356
361
|
@mapped_name ||= begin
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
362
|
+
name = self.name.split('::').last
|
363
|
+
name[0] = name[0].downcase
|
364
|
+
name.to_sym
|
365
|
+
end
|
361
366
|
end
|
362
367
|
|
363
368
|
# Return string if it is not empty, or nil otherwise
|
@@ -366,5 +371,24 @@ module XSD
|
|
366
371
|
def nil_if_empty(string)
|
367
372
|
string&.empty? ? nil : string
|
368
373
|
end
|
374
|
+
|
375
|
+
private
|
376
|
+
|
377
|
+
def definition_match?(definition, query)
|
378
|
+
actual_definition = definition.referenced? ? definition.reference : definition
|
379
|
+
|
380
|
+
if query.start_with?('{')
|
381
|
+
parts = query[1..].split('}')
|
382
|
+
raise Error, "Invalid element/attribute query: #{query}" if parts.size != 2
|
383
|
+
|
384
|
+
namespace, name = parts
|
385
|
+
|
386
|
+
return false if namespace != actual_definition.namespace
|
387
|
+
else
|
388
|
+
name = query
|
389
|
+
end
|
390
|
+
|
391
|
+
name == '*' || actual_definition.name == name
|
392
|
+
end
|
369
393
|
end
|
370
394
|
end
|
@@ -10,10 +10,11 @@ module XSD
|
|
10
10
|
|
11
11
|
include SimpleTyped
|
12
12
|
include Referenced
|
13
|
+
include Named
|
13
14
|
|
14
15
|
# Optional. Specifies the name of the attribute. Name and ref attributes cannot both be present
|
15
16
|
# @!attribute name
|
16
|
-
# @return String
|
17
|
+
# @return String, nil
|
17
18
|
property :name, :string
|
18
19
|
|
19
20
|
# Optional. Specifies a default value for the attribute. Default and fixed attributes cannot both be present
|
@@ -34,7 +35,7 @@ module XSD
|
|
34
35
|
# matched against the (NCName) of the attribute
|
35
36
|
# @!attribute form
|
36
37
|
# @return String, nil
|
37
|
-
# TODO:
|
38
|
+
# TODO: support default value from parent
|
38
39
|
property :form, :string
|
39
40
|
|
40
41
|
# Optional. Specifies a built-in data type or a simple type. The type attribute can only be present when the
|
@@ -68,5 +69,15 @@ module XSD
|
|
68
69
|
def prohibited?
|
69
70
|
use == 'prohibited'
|
70
71
|
end
|
72
|
+
|
73
|
+
# Get base data type
|
74
|
+
# @return String, nil
|
75
|
+
def data_type
|
76
|
+
if simple_type
|
77
|
+
simple_type.data_type
|
78
|
+
else
|
79
|
+
strip_prefix(type)
|
80
|
+
end
|
81
|
+
end
|
71
82
|
end
|
72
83
|
end
|
@@ -9,10 +9,11 @@ module XSD
|
|
9
9
|
class AttributeGroup < BaseObject
|
10
10
|
include Referenced
|
11
11
|
include AttributeContainer
|
12
|
+
include Named
|
12
13
|
|
13
|
-
# Optional. Specifies the name of the attribute. Name and ref attributes cannot both be present
|
14
|
+
# Optional. Specifies the name of the attribute group. Name and ref attributes cannot both be present
|
14
15
|
# @!attribute name
|
15
|
-
# @return String
|
16
|
+
# @return String, nil
|
16
17
|
property :name, :string
|
17
18
|
end
|
18
19
|
end
|
@@ -7,8 +7,9 @@ module XSD
|
|
7
7
|
# https://www.w3schools.com/xml/el_complextype.asp
|
8
8
|
class ComplexType < BaseObject
|
9
9
|
include AttributeContainer
|
10
|
+
include Named
|
10
11
|
|
11
|
-
# Optional. Specifies
|
12
|
+
# Optional. Specifies a name for the element
|
12
13
|
# @!attribute name
|
13
14
|
# @return String
|
14
15
|
property :name, :string
|
@@ -78,5 +79,55 @@ module XSD
|
|
78
79
|
def linked?
|
79
80
|
!name.nil?
|
80
81
|
end
|
82
|
+
|
83
|
+
# Determine if this type has mixed content definition
|
84
|
+
# @return Boolean
|
85
|
+
def mixed_content?
|
86
|
+
return true if mixed
|
87
|
+
|
88
|
+
if complex_content
|
89
|
+
return true if complex_content.mixed
|
90
|
+
|
91
|
+
return (complex_content.extension || complex_content.restriction).base_complex_type&.mixed_content?
|
92
|
+
end
|
93
|
+
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
# Determine if this type has complex content definition
|
98
|
+
# @return Boolean
|
99
|
+
def complex_content?
|
100
|
+
if simple_content
|
101
|
+
false
|
102
|
+
elsif complex_content
|
103
|
+
true
|
104
|
+
else
|
105
|
+
group || all || choice || sequence
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Determine if this type has simple content definition
|
110
|
+
# @return Boolean
|
111
|
+
def simple_content?
|
112
|
+
!!simple_content
|
113
|
+
end
|
114
|
+
|
115
|
+
# Get simple content data type
|
116
|
+
# @return String, nil
|
117
|
+
def data_type
|
118
|
+
return nil unless simple_content
|
119
|
+
|
120
|
+
restriction = simple_content.restriction
|
121
|
+
if restriction
|
122
|
+
if restriction.base
|
123
|
+
restriction.base_simple_type&.data_type || strip_prefix(restriction.base)
|
124
|
+
else
|
125
|
+
restriction.simple_type&.data_type
|
126
|
+
end
|
127
|
+
else
|
128
|
+
extension = simple_content.extension
|
129
|
+
extension.base_simple_type&.data_type || strip_prefix(extension.base)
|
130
|
+
end
|
131
|
+
end
|
81
132
|
end
|
82
133
|
end
|
data/lib/xsd/objects/element.rb
CHANGED
@@ -11,10 +11,11 @@ module XSD
|
|
11
11
|
include SimpleTyped
|
12
12
|
include ComplexTyped
|
13
13
|
include Referenced
|
14
|
+
include Named
|
14
15
|
|
15
|
-
# Optional. Specifies
|
16
|
+
# Optional. Specifies a name for the element. This attribute is required if the parent element is the schema element
|
16
17
|
# @!attribute name
|
17
|
-
# @return String
|
18
|
+
# @return String, nil
|
18
19
|
property :name, :string
|
19
20
|
|
20
21
|
# Optional. Specifies either the name of a built-in data type, or the name of a simpleType or complexType element
|
@@ -107,8 +108,32 @@ module XSD
|
|
107
108
|
|
108
109
|
# Determine if element has complex content
|
109
110
|
# @return Boolean
|
110
|
-
def
|
111
|
-
|
111
|
+
def complex_content?
|
112
|
+
# this is not an opposite to simple_content?, element may have neither
|
113
|
+
complex_type&.complex_content? || false
|
114
|
+
end
|
115
|
+
|
116
|
+
# Determine if element has simple content
|
117
|
+
# @return Boolean
|
118
|
+
def simple_content?
|
119
|
+
# this is not an opposite to complex_content?, element may have neither
|
120
|
+
if complex_type
|
121
|
+
complex_type.simple_content?
|
122
|
+
else
|
123
|
+
true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Determine if element has attributes
|
128
|
+
# @return Boolean
|
129
|
+
def attributes?
|
130
|
+
complex_type && collect_attributes.any?
|
131
|
+
end
|
132
|
+
|
133
|
+
# Determine if element has mixed content
|
134
|
+
# @return Boolean
|
135
|
+
def mixed_content?
|
136
|
+
complex_type&.mixed_content? || false
|
112
137
|
end
|
113
138
|
|
114
139
|
# Get elements that can appear instead of this one
|
@@ -117,14 +142,20 @@ module XSD
|
|
117
142
|
# TODO: for now we do not search in parent schemas (that imported current schema)
|
118
143
|
# TODO: refactor for better namespace handling (use xpath with namespaces or correct comparison)
|
119
144
|
schema.collect_elements.select do |element|
|
120
|
-
element.substitution_group
|
145
|
+
strip_prefix(element.substitution_group) == name
|
121
146
|
end
|
122
147
|
end
|
123
148
|
|
124
|
-
# Get
|
125
|
-
# @return String
|
126
|
-
def
|
127
|
-
|
149
|
+
# Get base data type
|
150
|
+
# @return String, nil
|
151
|
+
def data_type
|
152
|
+
if complex_type
|
153
|
+
complex_type.data_type
|
154
|
+
elsif simple_type
|
155
|
+
simple_type.data_type
|
156
|
+
else
|
157
|
+
strip_prefix(type)
|
158
|
+
end
|
128
159
|
end
|
129
160
|
end
|
130
161
|
end
|
@@ -6,9 +6,12 @@ module XSD
|
|
6
6
|
# Parent elements: attribute, element, list, restriction, schema, union
|
7
7
|
# https://www.w3schools.com/xml/el_simpletype.asp
|
8
8
|
class SimpleType < BaseObject
|
9
|
-
|
9
|
+
include Named
|
10
|
+
|
11
|
+
# Specifies a name for the element. This attribute is required if the simpleType element is a child of the
|
12
|
+
# schema element, otherwise it is not allowed
|
10
13
|
# @!attribute name
|
11
|
-
# @return String
|
14
|
+
# @return String, nil
|
12
15
|
property :name, :string
|
13
16
|
|
14
17
|
# Nested restriction
|
@@ -31,5 +34,23 @@ module XSD
|
|
31
34
|
def linked?
|
32
35
|
!name.nil?
|
33
36
|
end
|
37
|
+
|
38
|
+
# Get base data type
|
39
|
+
# @return String, nil
|
40
|
+
def data_type
|
41
|
+
if restriction
|
42
|
+
if restriction.base
|
43
|
+
restriction.base_simple_type&.data_type || strip_prefix(restriction.base)
|
44
|
+
else
|
45
|
+
restriction.simple_type&.data_type
|
46
|
+
end
|
47
|
+
elsif union
|
48
|
+
types = union.types.map { |type| type.is_a?(String) ? strip_prefix(type) : type.data_type }.uniq
|
49
|
+
types.size == 1 ? types.first : types
|
50
|
+
else
|
51
|
+
# list is always a sting
|
52
|
+
'string'
|
53
|
+
end
|
54
|
+
end
|
34
55
|
end
|
35
56
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module XSD
|
4
|
+
module Named
|
5
|
+
# Get definition namespace
|
6
|
+
# @return String
|
7
|
+
def namespace
|
8
|
+
schema.target_namespace
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get absolute definition name
|
12
|
+
# @return String
|
13
|
+
def absolute_name
|
14
|
+
name ? "{#{namespace}}#{name}" : nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/xsd/version.rb
CHANGED
data/lib/xsd/xml.rb
CHANGED
data/lib/xsd.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'xsd/base_object'
|
4
|
+
require 'xsd/shared/named'
|
4
5
|
require 'xsd/shared/min_max_occurs'
|
5
6
|
require 'xsd/shared/referenced'
|
6
7
|
require 'xsd/shared/attribute_container'
|
@@ -37,6 +38,5 @@ require 'xsd/objects/appinfo'
|
|
37
38
|
require 'xsd/objects/any_attribute'
|
38
39
|
require 'xsd/objects/key'
|
39
40
|
require 'xsd/objects/keyref'
|
40
|
-
require 'xsd/generator'
|
41
41
|
require 'xsd/exceptions'
|
42
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.
|
4
|
+
version: 2.6.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: 2024-04-
|
11
|
+
date: 2024-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: builder
|
@@ -72,7 +72,6 @@ files:
|
|
72
72
|
- lib/xsd.rb
|
73
73
|
- lib/xsd/base_object.rb
|
74
74
|
- lib/xsd/exceptions.rb
|
75
|
-
- lib/xsd/generator.rb
|
76
75
|
- lib/xsd/objects/all.rb
|
77
76
|
- lib/xsd/objects/annotation.rb
|
78
77
|
- lib/xsd/objects/any.rb
|
@@ -107,6 +106,7 @@ files:
|
|
107
106
|
- lib/xsd/shared/complex_typed.rb
|
108
107
|
- lib/xsd/shared/element_container.rb
|
109
108
|
- lib/xsd/shared/min_max_occurs.rb
|
109
|
+
- lib/xsd/shared/named.rb
|
110
110
|
- lib/xsd/shared/referenced.rb
|
111
111
|
- lib/xsd/shared/simple_typed.rb
|
112
112
|
- lib/xsd/version.rb
|
data/lib/xsd/generator.rb
DELETED
@@ -1,146 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'builder'
|
4
|
-
|
5
|
-
module XSD
|
6
|
-
module Generator
|
7
|
-
# Generate XML from provided data
|
8
|
-
# @param [Hash] data
|
9
|
-
# @param [String, Array<String>] element
|
10
|
-
# @param [Builder::XmlMarkup] builder
|
11
|
-
# @return Builder::XmlMarkup
|
12
|
-
def generate(data, element = nil, builder = nil)
|
13
|
-
# find root element
|
14
|
-
root = find_root_element(element)
|
15
|
-
|
16
|
-
# create builder
|
17
|
-
builder ||= default_builder
|
18
|
-
|
19
|
-
# build element tree
|
20
|
-
@namespace_index = 0
|
21
|
-
build_element(builder, root, data)
|
22
|
-
|
23
|
-
builder
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
# Build element tree
|
29
|
-
# @param [Builder::XmlMarkup] xml
|
30
|
-
# @param [Element] element
|
31
|
-
# @param [Hash] data
|
32
|
-
# @param [Hash] namespaces
|
33
|
-
def build_element(xml, element, data, namespaces = {})
|
34
|
-
# get item data
|
35
|
-
elements = (element.abstract ? [] : [element]) + element.substitution_elements
|
36
|
-
provided_element = elements.find { |elem| !data[elem.name].nil? }
|
37
|
-
|
38
|
-
if element.required? && provided_element.nil?
|
39
|
-
raise Error, "Element #{element.name} is required, but no data in provided for it"
|
40
|
-
end
|
41
|
-
return unless provided_element
|
42
|
-
|
43
|
-
element = provided_element
|
44
|
-
data = data[element.name]
|
45
|
-
|
46
|
-
# handle repeated items
|
47
|
-
if element.multiple_allowed?
|
48
|
-
unless data.is_a?(Array)
|
49
|
-
raise Error, "Element #{element.name} is allowed to occur multiple times, but non-array is provided"
|
50
|
-
end
|
51
|
-
else
|
52
|
-
if data.is_a?(Array)
|
53
|
-
raise Error, "Element #{element.name} is not allowed to occur multiple times, but an array is provided"
|
54
|
-
end
|
55
|
-
|
56
|
-
data = [data]
|
57
|
-
end
|
58
|
-
|
59
|
-
# configure namespaces
|
60
|
-
# TODO: попытаться использовать collect_namespaces?
|
61
|
-
attributes = {}
|
62
|
-
all_attributes = element.collect_attributes
|
63
|
-
if element.complex?
|
64
|
-
all_elements = element.collect_elements
|
65
|
-
|
66
|
-
# get namespaces for current element and it's children
|
67
|
-
prefix = nil
|
68
|
-
[*all_elements, element].each do |elem|
|
69
|
-
prefix = get_namespace_prefix(elem, attributes, namespaces)
|
70
|
-
end
|
71
|
-
else
|
72
|
-
prefix = get_namespace_prefix(element, attributes, namespaces)
|
73
|
-
end
|
74
|
-
|
75
|
-
# iterate through each item
|
76
|
-
data.each do |item|
|
77
|
-
# prepare attributes
|
78
|
-
all_attributes.each do |attribute|
|
79
|
-
value = item["@#{attribute.name}"]
|
80
|
-
if !value.nil?
|
81
|
-
attributes[attribute.name] = value
|
82
|
-
elsif attribute.required?
|
83
|
-
raise Error, "Attribute #{attribute.name} is required, but no data in provided for it" if attribute.fixed.nil?
|
84
|
-
|
85
|
-
attributes[attribute.name] = attribute.fixed
|
86
|
-
else
|
87
|
-
attributes.delete(attribute.name)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# generate element
|
92
|
-
if element.complex?
|
93
|
-
# generate tag recursively
|
94
|
-
xml.tag!("#{prefix}:#{element.name}", attributes) do
|
95
|
-
all_elements.each do |elem|
|
96
|
-
build_element(xml, elem, item, namespaces.dup)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
else
|
100
|
-
has_text = item.is_a?(Hash)
|
101
|
-
if has_text && element.complex_type&.nodes(:any, true)&.any?
|
102
|
-
xml.tag!("#{prefix}:#{element.name}", attributes) { |res| res << item['#text'].to_s }
|
103
|
-
else
|
104
|
-
value = has_text ? item['#text'] : item
|
105
|
-
xml.tag!("#{prefix}:#{element.name}", attributes, (value == '' ? nil : value))
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# Get namespace prefix for element
|
112
|
-
# @param [Element] element
|
113
|
-
# @param [Hash] attributes
|
114
|
-
# @param [Hash] namespaces
|
115
|
-
# @return String
|
116
|
-
def get_namespace_prefix(element, attributes, namespaces)
|
117
|
-
namespace = (element.referenced? ? element.reference : element).target_namespace
|
118
|
-
prefix = namespaces.key(namespace)
|
119
|
-
unless prefix
|
120
|
-
prefix = "n#{@namespace_index += 1}"
|
121
|
-
namespaces[prefix] = attributes["xmlns:#{prefix}"] = namespace
|
122
|
-
end
|
123
|
-
|
124
|
-
prefix
|
125
|
-
end
|
126
|
-
|
127
|
-
# Find root element with provided lookup
|
128
|
-
# @param [String, Array<String>, nil] lookup
|
129
|
-
def find_root_element(lookup)
|
130
|
-
if lookup
|
131
|
-
element = self[*lookup]
|
132
|
-
raise Error, "Cant find start element #{lookup}" unless element.is_a?(Element)
|
133
|
-
|
134
|
-
element
|
135
|
-
else
|
136
|
-
raise Error, 'XSD contains more that one root element. Please, specify starting element' if elements.size > 1
|
137
|
-
|
138
|
-
elements.first
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def default_builder
|
143
|
-
Builder::XmlMarkup.new
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|