xsd 1.0.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +124 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE +21 -0
  8. data/Makefile +7 -0
  9. data/README.md +68 -0
  10. data/Rakefile +25 -0
  11. data/lib/xsd/base_object.rb +347 -0
  12. data/lib/xsd/exceptions.rb +12 -0
  13. data/lib/xsd/generator.rb +137 -0
  14. data/lib/xsd/objects/all.rb +22 -0
  15. data/lib/xsd/objects/annotation.rb +19 -0
  16. data/lib/xsd/objects/any.rb +31 -0
  17. data/lib/xsd/objects/any_attribute.rb +30 -0
  18. data/lib/xsd/objects/appinfo.rb +13 -0
  19. data/lib/xsd/objects/attribute.rb +72 -0
  20. data/lib/xsd/objects/attribute_group.rb +18 -0
  21. data/lib/xsd/objects/choice.rb +33 -0
  22. data/lib/xsd/objects/complex_content.rb +25 -0
  23. data/lib/xsd/objects/complex_type.rb +82 -0
  24. data/lib/xsd/objects/documentation.rb +18 -0
  25. data/lib/xsd/objects/element.rb +130 -0
  26. data/lib/xsd/objects/extension.rb +14 -0
  27. data/lib/xsd/objects/facet.rb +12 -0
  28. data/lib/xsd/objects/field.rb +13 -0
  29. data/lib/xsd/objects/group.rb +32 -0
  30. data/lib/xsd/objects/import.rb +67 -0
  31. data/lib/xsd/objects/key.rb +25 -0
  32. data/lib/xsd/objects/keyref.rb +33 -0
  33. data/lib/xsd/objects/list.rb +18 -0
  34. data/lib/xsd/objects/restriction.rb +49 -0
  35. data/lib/xsd/objects/schema.rb +155 -0
  36. data/lib/xsd/objects/selector.rb +15 -0
  37. data/lib/xsd/objects/sequence.rb +33 -0
  38. data/lib/xsd/objects/simple_content.rb +19 -0
  39. data/lib/xsd/objects/simple_type.rb +35 -0
  40. data/lib/xsd/objects/union.rb +23 -0
  41. data/lib/xsd/objects/unique.rb +18 -0
  42. data/lib/xsd/shared/attribute_container.rb +17 -0
  43. data/lib/xsd/shared/based.rb +37 -0
  44. data/lib/xsd/shared/complex_typed.rb +28 -0
  45. data/lib/xsd/shared/element_container.rb +12 -0
  46. data/lib/xsd/shared/min_max_occurs.rb +46 -0
  47. data/lib/xsd/shared/referenced.rb +24 -0
  48. data/lib/xsd/shared/simple_typed.rb +14 -0
  49. data/lib/xsd/validator.rb +90 -0
  50. data/lib/xsd/version.rb +5 -0
  51. data/lib/xsd/xml.rb +111 -0
  52. data/lib/xsd.rb +42 -0
  53. data/xsd.gemspec +45 -0
  54. metadata +239 -0
@@ -0,0 +1,347 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XSD
4
+ # Base object
5
+ class BaseObject
6
+ attr_reader :options
7
+
8
+ # Objects that can not have nested elements
9
+ NO_ELEMENTS_CONTAINER = %i[annotation simpleType attributeGroup attribute
10
+ unique union simpleContent list any anyAttribute key keyref].freeze
11
+
12
+ # Objects that cannot have nested attributes
13
+ NO_ATTRIBUTES_CONTAINER = %i[annotation unique anyAttribute all
14
+ attribute choice sequence group simpleType facet key keyref].freeze
15
+
16
+ # Base XMLSchema namespace
17
+ XML_SCHEMA = 'http://www.w3.org/2001/XMLSchema'
18
+
19
+ class << self
20
+ attr_reader :properties, :children, :links
21
+
22
+ def properties
23
+ @properties ||= {}
24
+ end
25
+
26
+ def links
27
+ @links ||= {}
28
+ end
29
+
30
+ def children
31
+ @children ||= {}
32
+ end
33
+ end
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
+ def initialize(options = {})
44
+ @options = options
45
+ @cache = {}
46
+
47
+ raise Error, "#{self.class}.new expects a hash parameter" unless @options.is_a?(Hash)
48
+ end
49
+
50
+ # Get current XML node
51
+ # @return [Nokogiri::XML::Node]
52
+ def node
53
+ options[:node]
54
+ end
55
+
56
+ # Get current namespaces
57
+ # @return [Hash]
58
+ def namespaces
59
+ node.namespaces || {}
60
+ end
61
+
62
+ # Get child nodes
63
+ # @param [Symbol] name
64
+ # @return [Nokogiri::XML::NodeSet]
65
+ def nodes(name = :*)
66
+ node.xpath("./xs:#{name}", { 'xs' => XML_SCHEMA })
67
+ end
68
+
69
+ # Get schema object for specified namespace prefix
70
+ # @param [String] prefix
71
+ # @return [Schema]
72
+ def schema_for_namespace(prefix)
73
+ if schema.targets_namespace?(prefix)
74
+ schema
75
+ elsif (import = schema.import_by_namespace(prefix))
76
+ import.imported_reader.schema
77
+ else
78
+ raise Error, "Schema not found for namespace '#{prefix}' in '#{schema.id || schema.target_namespace}'"
79
+ end
80
+ end
81
+
82
+ # Get element or attribute by path
83
+ # @return [Element, Attribute, nil]
84
+ def [](*args)
85
+ result = self
86
+
87
+ args.flatten.each do |curname|
88
+ next if result.nil?
89
+
90
+ curname = curname.to_s
91
+
92
+ if curname[0] == '@'
93
+ result = result.all_attributes.find { |attr| attr.name == curname[1..-1] }
94
+ else
95
+ result = result.all_elements.find { |elem| elem.name == curname }
96
+ end
97
+ end
98
+
99
+ result
100
+ end
101
+
102
+ # Search node by name in all available schemas and return its object
103
+ # @param [Symbol] node_name
104
+ # @param [String] name
105
+ # @return [BaseObject, nil]
106
+ def object_by_name(node_name, name)
107
+ # get prefix and local name
108
+ name_prefix = get_prefix(name)
109
+ name_local = strip_prefix(name)
110
+
111
+ # do not search for built-in types
112
+ return nil if schema.namespace_prefix == name_prefix
113
+
114
+ # determine schema for namespace
115
+ search_schema = schema_for_namespace(name_prefix)
116
+
117
+ # find element in target schema
118
+ result = search_schema.node.xpath("./xs:#{node_name}[@name=\"#{name_local}\"]", { 'xs' => XML_SCHEMA }).first
119
+
120
+ result ? search_schema.node_to_object(result) : nil
121
+ end
122
+
123
+ # Get reader object for node
124
+ # @param [Nokogiri::XML::Node]
125
+ # @return [BaseObject]
126
+ def node_to_object(node)
127
+ # check object in cache first
128
+ # TODO: проверить работу!
129
+ return reader.object_cache[node.object_id] if reader.object_cache[node.object_id]
130
+
131
+ klass = XML::CLASS_MAP[node.name]
132
+ raise Error, "Object class not found for '#{node.name}'" unless klass
133
+
134
+ reader.object_cache[node.object_id] = klass.new(options.merge(node: node, schema: schema))
135
+ end
136
+
137
+ # Get xml parent object
138
+ # @return [BaseObject, nil]
139
+ def parent
140
+ node.respond_to?(:parent) && node.parent ? node_to_object(node.parent) : nil
141
+ end
142
+
143
+ # Get current schema object
144
+ # @return [Schema]
145
+ def schema
146
+ options[:schema]
147
+ end
148
+
149
+ # Get child objects
150
+ # @param [Symbol] name
151
+ # @return [Array<BaseObject>]
152
+ def map_children(name)
153
+ nodes(name).map { |node| node_to_object(node) }
154
+ end
155
+
156
+ # Get child object
157
+ # @param [Symbol] name
158
+ # @return [BaseObject, nil]
159
+ def map_child(name)
160
+ map_children(name).first
161
+ end
162
+
163
+ # Strip namespace prefix from node name
164
+ # @param [String, nil] name Name to strip from
165
+ # @return [String, nil]
166
+ def strip_prefix(name)
167
+ name&.include?(':') ? name.split(':').last : name
168
+ end
169
+
170
+ # Get namespace prefix from node name
171
+ # @param [String, nil] name Name to strip from
172
+ # @return [String]
173
+ def get_prefix(name)
174
+ name&.include?(':') ? name.split(':').first : ''
175
+ end
176
+
177
+ # Return element documentation
178
+ # @return [Array<String>]
179
+ def documentation
180
+ documentation_for(node)
181
+ end
182
+
183
+ # Return documentation for specified node
184
+ # @param [Nokogiri::XML::Node] node
185
+ # @return [Array<String>]
186
+ def documentation_for(node)
187
+ node.xpath('./xs:annotation/xs:documentation/text()', { 'xs' => XML_SCHEMA }).map(&:to_s).map(&:strip)
188
+ end
189
+
190
+ # Get all available elements on the current stack level
191
+ # @return [Array<Element>]
192
+ def all_elements(*)
193
+ # exclude element that can not have elements
194
+ return [] if NO_ELEMENTS_CONTAINER.include?(self.class.mapped_name)
195
+
196
+ if is_a?(Referenced) && ref
197
+ reference.all_elements
198
+ else
199
+ # map children recursive
200
+ map_children(:*).map do |obj|
201
+ if obj.is_a?(Element)
202
+ obj
203
+ else
204
+ # get elements considering references
205
+ (obj.is_a?(Referenced) && obj.ref ? obj.reference : obj).all_elements
206
+ end
207
+ end.flatten
208
+ end
209
+ end
210
+
211
+ # Get all available attributes on the current stack level
212
+ # @return [Array<Attribute>]
213
+ def all_attributes(*)
214
+ # exclude element that can not have elements
215
+ return [] if NO_ATTRIBUTES_CONTAINER.include?(self.class.mapped_name)
216
+
217
+ if is_a?(Referenced) && ref
218
+ reference.all_attributes
219
+ else
220
+ # map children recursive
221
+ map_children(:*).map do |obj|
222
+ if obj.is_a?(Attribute)
223
+ obj
224
+ else
225
+ # get attributes considering references
226
+ (obj.is_a?(Referenced) && obj.ref ? obj.reference : obj).all_attributes
227
+ end
228
+ end.flatten
229
+ end
230
+ end
231
+
232
+ # Get reader instance
233
+ # @return [XML]
234
+ def reader
235
+ options[:reader]
236
+ end
237
+
238
+ protected
239
+
240
+ def self.to_underscore(string)
241
+ string.to_s.gsub(/([^A-Z])([A-Z]+)/, '\1_\2').sub(':', '_').downcase.to_sym
242
+ end
243
+
244
+ # Define new object property
245
+ # @param [Symbol] name
246
+ # @param [Symbol] type
247
+ # @param [Hash] options
248
+ def self.property(name, type, options = {}, &block)
249
+ properties[to_underscore(name)] = {
250
+ name: name,
251
+ type: type,
252
+ resolve: block,
253
+ **options
254
+ }
255
+ end
256
+
257
+ # Define new object child
258
+ # @param [Symbol] name
259
+ # @param [Symbol, Array<Symbol>] type
260
+ # @param [Hash] options
261
+ def self.child(name, type, options = {})
262
+ children[to_underscore(name)] = {
263
+ type: type,
264
+ **options
265
+ }
266
+ end
267
+
268
+ # Define new object child
269
+ # @param [Symbol] name
270
+ # @param [Symbol] type
271
+ # @param [Hash] options
272
+ def self.link(name, type, options = {})
273
+ links[to_underscore(name)] = {
274
+ type: type,
275
+ **options
276
+ }
277
+ end
278
+
279
+ # Lookup for properties
280
+ # @param [Symbol] method
281
+ # @param [Array] args
282
+ def method_missing(method, *args)
283
+ # check cache first
284
+ return @cache[method] if @cache[method]
285
+
286
+ # check for property first
287
+ if (property = self.class.properties[method])
288
+ value = property[:resolve] ? property[:resolve].call(self) : node[property[:name].to_s]
289
+ result = if value.nil?
290
+ # if object has reference - search property in referenced object
291
+ node['ref'] ? reference.send(method, *args) : property[:default]
292
+ else
293
+ case property[:type]
294
+ when :integer
295
+ property[:name] == :maxOccurs && value == 'unbounded' ? :unbounded : value.to_i
296
+ when :boolean
297
+ !!value
298
+ else
299
+ value
300
+ end
301
+ end
302
+ return @cache[method] = result
303
+ end
304
+
305
+ # if object has ref it cannot contain any type and children, so proxy call to target object
306
+ if node['ref'] && method != :ref && method != :reference
307
+ return reference.send(method, *args)
308
+ end
309
+
310
+ # then check for linked types
311
+ if (link = self.class.links[method])
312
+ name = link[:property] ? send(link[:property]) : nil
313
+ if name
314
+ return @cache[method] = object_by_name(link[:type], name)
315
+ end
316
+ end
317
+
318
+ # last check for nested objects
319
+ if (child = self.class.children[method])
320
+ result = child[:type].is_a?(Array) ? map_children(child[:type][0]) : map_child(child[:type])
321
+ return @cache[method] = result
322
+ end
323
+
324
+ super
325
+ # api = self.class.properties.keys + self.class.links.keys + self.class.children.keys
326
+ # raise Error, "Tried to access unknown object '#{method}' on '#{self.class.name}'. Available options are: #{api}"
327
+ end
328
+
329
+ # Does object has property/link/child?
330
+ # @param [Symbol] method
331
+ # @param [Array] args
332
+ def respond_to_missing?(method, *args)
333
+ self.class.properties[method] || self.class.links[method] || self.class.children[method] || super
334
+ end
335
+
336
+ # Get mapped element name
337
+ # @return [Symbol]
338
+ def self.mapped_name
339
+ # @mapped_name ||= XML::CLASS_MAP.each { |k, v| return k.to_sym if v == self }
340
+ @mapped_name ||= begin
341
+ name = self.name.split('::').last
342
+ name[0] = name[0].downcase
343
+ name.to_sym
344
+ end
345
+ end
346
+ end
347
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XSD
4
+ class Error < StandardError
5
+ end
6
+
7
+ class ValidationError < Error
8
+ end
9
+
10
+ class ImportError < Error
11
+ end
12
+ end
@@ -0,0 +1,137 @@
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.all_attributes
63
+ if element.complex?
64
+ all_elements = element.all_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
81
+ attributes[attribute.name] = value
82
+ else
83
+ attributes.delete(attribute.name)
84
+ end
85
+ end
86
+
87
+ # generate element
88
+ if element.complex?
89
+ # generate tag recursively
90
+ xml.tag!("#{prefix}:#{element.name}", attributes) do
91
+ all_elements.each do |elem|
92
+ build_element(xml, elem, item, namespaces.dup)
93
+ end
94
+ end
95
+ else
96
+ value = item.is_a?(Hash) ? item['#text'] : item
97
+ xml.tag!("#{prefix}:#{element.name}", attributes, (value == '' ? nil : value))
98
+ end
99
+ end
100
+ end
101
+
102
+ # Get namespace prefix for element
103
+ # @param [Element] element
104
+ # @param [Hash] attributes
105
+ # @param [Hash] namespaces
106
+ # @return [String]
107
+ def get_namespace_prefix(element, attributes, namespaces)
108
+ namespace = (element.referenced? ? element.reference : element).target_namespace
109
+ prefix = namespaces.key(namespace)
110
+ unless prefix
111
+ prefix = "tns#{@namespace_index += 1}"
112
+ namespaces[prefix] = attributes["xmlns:#{prefix}"] = namespace
113
+ end
114
+
115
+ prefix
116
+ end
117
+
118
+ # Find root element with provided lookup
119
+ # @param [String, Array<String>, nil] lookup
120
+ def find_root_element(lookup)
121
+ if lookup
122
+ element = schema[*lookup]
123
+ raise Error, "Cant find start element #{lookup}" unless element.is_a?(Element)
124
+
125
+ element
126
+ else
127
+ raise Error, 'XSD contains more that one root element. Please, specify starting element' if elements.size > 1
128
+
129
+ elements.first
130
+ end
131
+ end
132
+
133
+ def default_builder
134
+ Builder::XmlMarkup.new
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XSD
4
+ # The all element specifies that the child elements can appear in any order and that each child element can
5
+ # occur zero or one time.
6
+ # Parent elements: group, complexType, restriction (both simpleContent and complexContent),
7
+ # extension (both simpleContent and complexContent)
8
+ # https://www.w3schools.com/xml/el_all.asp
9
+ class All < BaseObject
10
+ include ElementContainer
11
+
12
+ # Optional. Specifies the minimum number of times the element can occur. The value can be 0 or 1. Default value is 1
13
+ # @!attribute min_occurs
14
+ # @return [Integer]
15
+ property :minOccurs, :integer, default: 1
16
+
17
+ # Optional. Specifies the maximum number of times the element can occur. The value must be 1.
18
+ # @!attribute max_occurs
19
+ # @return [Integer]
20
+ property :maxOccurs, :integer, default: 1
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XSD
4
+ # The annotation element is a top level element that specifies schema comments. The comments serve as inline
5
+ # documentation.
6
+ # Parent elements: Any element
7
+ # https://www.w3schools.com/xml/el_annotation.asp
8
+ class Annotation < BaseObject
9
+ # Nested appinfos
10
+ # @!attribute appinfos
11
+ # @return [Array<Appinfo>]
12
+ child :appinfos, [:appinfo]
13
+
14
+ # Nested documentations
15
+ # @!attribute documentations
16
+ # @return [Array<Documentation>]
17
+ child :documentations, [:documentation]
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XSD
4
+ # The any element enables the author to extend the XML document with elements not specified by the schema.
5
+ # Parent elements: choice, sequence
6
+ # https://www.w3schools.com/xml/el_any.asp
7
+ class Any < BaseObject
8
+ include MinMaxOccurs
9
+
10
+ # Optional. Specifies the namespaces containing the elements that can be used. Can be set to one of the following:
11
+ # ##any - elements from any namespace is allowed (this is default)
12
+ # ##other - elements from any namespace that is not the namespace of the parent element can be present
13
+ # ##local - elements must come from no namespace
14
+ # ##targetNamespace - elements from the namespace of the parent element can be present
15
+ # List of {URI references of namespaces, ##targetNamespace, ##local} - elements from a space-delimited list of
16
+ # the namespaces can be present
17
+ # @!attribute namespace
18
+ # @return [String]
19
+ property :namespace, :string, default: '##any'
20
+
21
+ # Optional. Specifies how the XML processor should handle validation against the elements specified by this any
22
+ # element. Can be set to one of the following:
23
+ # strict - the XML processor must obtain the schema
24
+ # for the required namespaces and validate the elements (this is default)
25
+ # lax - same as strict but; if the schema cannot be obtained, no errors will occur
26
+ # skip - The XML processor does not attempt to validate any elements from the specified namespaces
27
+ # @!attribute process_contents
28
+ # @return [String, nil]
29
+ property :processContents, :string, default: 'strict'
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XSD
4
+ # The anyAttribute element enables the author to extend the XML document with attributes not specified by the schema.
5
+ # Parent elements: complexType, restriction (both simpleContent and complexContent), extension (both simpleContent
6
+ # and complexContent), attributeGroup
7
+ # https://www.w3schools.com/xml/el_anyattribute.asp
8
+ class AnyAttribute < BaseObject
9
+ # Optional. Specifies the namespaces containing the attributes that can be used. Can be set to one of the following:
10
+ # ##any - attributes from any namespace is allowed (this is default)
11
+ # ##other - attributes from any namespace that is not the namespace of the parent element can be present
12
+ # ##local - attributes must come from no namespace
13
+ # ##targetNamespace - attributes from the namespace of the parent element can be present
14
+ # List of {URI references of namespaces, ##targetNamespace, ##local} - attributes from a space-delimited list
15
+ # of the namespaces can be present
16
+ # @!attribute namespace
17
+ # @return [String]
18
+ property :namespace, :string, default: '##any'
19
+
20
+ # Optional. Specifies how the XML processor should handle validation against the elements specified by this any
21
+ # element. Can be set to one of the following:
22
+ # strict - the XML processor must obtain the schema for the required namespaces and validate the elements (this
23
+ # is default)
24
+ # lax - same as strict but; if the schema cannot be obtained, no errors will occur
25
+ # skip - The XML processor does not attempt to validate any elements from the specified namespaces
26
+ # @!attribute process_contents
27
+ # @return [String, nil]
28
+ property :processContents, :string, default: 'strict'
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XSD
4
+ # The appinfo element specifies information to be used by the application.
5
+ # Parent elements: annotation
6
+ # https://www.w3schools.com/xml/el_appinfo.asp
7
+ class Appinfo < BaseObject
8
+ # Optional. A URI reference that specifies the source of the application information
9
+ # @!attribute source
10
+ # @return [String]
11
+ property :source, :string
12
+ end
13
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XSD
4
+ # The attribute element defines an attribute.
5
+ # Parent elements: attributeGroup, schema, complexType, restriction (both simpleContent and complexContent),
6
+ # extension (both simpleContent and complexContent)
7
+ # https://www.w3schools.com/xml/el_attribute.asp
8
+ class Attribute < BaseObject
9
+ TYPE_PROPERTY = :type
10
+
11
+ include SimpleTyped
12
+ include Referenced
13
+
14
+ # Optional. Specifies the name of the attribute. Name and ref attributes cannot both be present
15
+ # @!attribute name
16
+ # @return [String]
17
+ property :name, :string
18
+
19
+ # Optional. Specifies a default value for the attribute. Default and fixed attributes cannot both be present
20
+ # @!attribute default
21
+ # @return [String, nil]
22
+ property :default, :string
23
+
24
+ # Optional. Specifies a fixed value for the attribute. Default and fixed attributes cannot both be present
25
+ # @!attribute fixed
26
+ # @return [String, nil]
27
+ property :fixed, :string
28
+
29
+ # Optional. Specifies the form for the attribute. The default value is the value of the attributeFormDefault
30
+ # attribute of the element containing the attribute. Can be set to one of the following:
31
+ # qualified - indicates that this attribute must be qualified with the namespace prefix and the no-colon-name
32
+ # (NCName) of the attribute
33
+ # unqualified - indicates that this attribute is not required to be qualified with the namespace prefix and is
34
+ # matched against the (NCName) of the attribute
35
+ # @!attribute form
36
+ # @return [String, nil]
37
+ # TODO: поддержка default значения с вычислением родителя
38
+ property :form, :string
39
+
40
+ # Optional. Specifies a built-in data type or a simple type. The type attribute can only be present when the
41
+ # content does not contain a simpleType element
42
+ # @!attribute type
43
+ # @return [String, nil]
44
+ property :type, :string
45
+
46
+ # Optional. Specifies how the attribute is used. Can be one of the following values:
47
+ # optional - the attribute is optional (this is default)
48
+ # prohibited - the attribute cannot be used
49
+ # required - the attribute is required
50
+ # @!attribute use
51
+ # @return [String]
52
+ property :use, :string, default: 'optional'
53
+
54
+ # Determine if attribute is required
55
+ # @return [Boolean]
56
+ def required?
57
+ use == 'required'
58
+ end
59
+
60
+ # Determine if attribute is optional
61
+ # @return [Boolean]
62
+ def optional?
63
+ use == 'optional'
64
+ end
65
+
66
+ # Determine if attribute is prohibited
67
+ # @return [Boolean]
68
+ def prohibited?
69
+ use == 'prohibited'
70
+ end
71
+ end
72
+ end