xsd 1.0.0

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