Class: XSD::BaseObject

Inherits:
Object
  • Object
show all
Defined in:
lib/xsd/base_object.rb

Overview

Base object

Constant Summary collapse

NO_ELEMENTS_CONTAINER =

Objects that can not have nested elements

%i[annotation simpleType attributeGroup attribute
unique union simpleContent list any anyAttribute key keyref].freeze
NO_ATTRIBUTES_CONTAINER =

Objects that cannot have nested attributes

%i[annotation unique anyAttribute all
attribute choice sequence group simpleType facet key keyref].freeze
XML_SCHEMA =

Base XMLSchema namespace

'http://www.w3.org/2001/XMLSchema'

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ BaseObject

Returns a new instance of BaseObject.

Raises:



35
36
37
38
39
40
# File 'lib/xsd/base_object.rb', line 35

def initialize(options = {})
  @options = options
  @cache = {}

  raise Error, "#{self.class}.new expects a hash parameter" unless @options.is_a?(Hash)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object (protected)

Lookup for properties

Parameters:

  • method (Symbol)
  • args (Array)


309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/xsd/base_object.rb', line 309

def method_missing(method, *args)
  # check cache first
  return @cache[method] if @cache[method]

  # check for property first
  if (property = self.class.properties[method])
    value = property[:resolve] ? property[:resolve].call(self) : node[property[:name].to_s]
    r = if value.nil?
          if node['ref']
            # if object has reference - search property in referenced object
            reference.send(method, *args)
          else
            property[:default].is_a?(Proc) ? instance_eval(&property[:default]) : property[:default]
          end
        else
          case property[:type]
          when :integer
            property[:name] == :maxOccurs && value == 'unbounded' ? :unbounded : value.to_i
          when :boolean
            value == 'true'
          else
            value
          end
        end
    return @cache[method] = r
  end

  # if object has ref it cannot contain any type and children, so proxy call to target object
  if node['ref'] && method != :ref && method != :reference
    return reference.send(method, *args)
  end

  # then check for linked types
  if (link = self.class.links[method])
    name = link[:property] ? send(link[:property]) : nil
    if name
      return @cache[method] = object_by_name(link[:type], name)
    elsif is_a?(Restriction) && %i[base_simple_type base_complex_type].include?(method)
      # handle restriction without base
      return nil
    end
  end

  # last check for nested objects
  if (child = self.class.children[method])
    result = child[:type].is_a?(Array) ? map_children(child[:type][0]) : map_child(child[:type])
    return @cache[method] = result
  end

  super
  # api = self.class.properties.keys + self.class.links.keys + self.class.children.keys
  # raise Error, "Tried to access unknown object '#{method}' on '#{self.class.name}'. Available options are: #{api}"
end

Class Attribute Details

.childrenObject (readonly)

Returns the value of attribute children.



20
21
22
# File 'lib/xsd/base_object.rb', line 20

def children
  @children
end

Returns the value of attribute links.



20
21
22
# File 'lib/xsd/base_object.rb', line 20

def links
  @links
end

.propertiesObject (readonly)

Returns the value of attribute properties.



20
21
22
# File 'lib/xsd/base_object.rb', line 20

def properties
  @properties
end

Instance Attribute Details

#idObject

Optional. Specifies a unique ID for the element property :id, :string

Returns:

  • String



58
59
60
# File 'lib/xsd/base_object.rb', line 58

def id
  node['id']
end

#optionsObject (readonly)

Returns the value of attribute options.



6
7
8
# File 'lib/xsd/base_object.rb', line 6

def options
  @options
end

Instance Method Details

#[](*args) ⇒ Object

Get element or attribute by path

Returns:

  • Element, Attribute, nil



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/xsd/base_object.rb', line 94

def [](*args)
  result = self

  args.flatten.each do |curname|
    next if result.nil?

    curname = curname.to_s

    if curname[0] == '@'
      result = result.collect_attributes.find { |attr| definition_match?(attr, curname[1..]) }
    else
      result = result.collect_elements.find { |elem| definition_match?(elem, curname) }
    end
  end

  result
end

#collect_attributesObject

Get all available attributes on the current stack level



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/xsd/base_object.rb', line 237

def collect_attributes(*)
  return @collect_attributes if @collect_attributes

  r = if NO_ATTRIBUTES_CONTAINER.include?(self.class.mapped_name)
        []
      elsif is_a?(Referenced) && ref
        reference.collect_attributes
      else
        # map children recursive
        map_children(:*).map do |obj|
          if obj.is_a?(Attribute) || obj.is_a?(AnyAttribute)
            obj
          else
            # get attributes considering references
            (obj.is_a?(Referenced) && obj.ref ? obj.reference : obj).collect_attributes
          end
        end.flatten
      end

  @collect_attributes = r
end

#collect_elementsObject

Get all available elements on the current stack level



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/xsd/base_object.rb', line 213

def collect_elements(*)
  return @collect_elements if @collect_elements

  r = if NO_ELEMENTS_CONTAINER.include?(self.class.mapped_name)
        []
      elsif is_a?(Referenced) && ref
        reference.collect_elements
      else
        # map children recursive
        map_children(:*).map do |obj|
          if obj.is_a?(Element) || obj.is_a?(Any)
            obj
          else
            # get elements considering references
            (obj.is_a?(Referenced) && obj.ref ? obj.reference : obj).collect_elements
          end
        end.flatten
      end

  @collect_elements = r
end

#documentationObject

Return element documentation



191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/xsd/base_object.rb', line 191

def documentation
  @documentation ||= begin
    docs = documentation_for(node)
    if docs.any?
      docs
    elsif is_a?(Referenced) && referenced?
      documentation_for(reference.node)
    else
      []
    end
  end
end

#documentation_for(node) ⇒ Object

Return documentation for specified node

Parameters:

  • node (Nokogiri::XML::Node)


207
208
209
# File 'lib/xsd/base_object.rb', line 207

def documentation_for(node)
  node.xpath('./xs:annotation/xs:documentation/text()', { 'xs' => XML_SCHEMA }).map { |x| x.text.strip }
end

#get_prefix(name) ⇒ Object

Get namespace prefix from node name

Parameters:

  • name (String, nil)

    Name to strip from

Returns:

  • String, nil



185
186
187
# File 'lib/xsd/base_object.rb', line 185

def get_prefix(name)
  name&.include?(':') ? name.split(':').first : nil
end

#inspectObject

Get object string representation

Returns:

  • String



50
51
52
# File 'lib/xsd/base_object.rb', line 50

def inspect
  "#<#{self.class.name} path=#{node.path}>"
end

#map_child(name) ⇒ Object

Get child object

Parameters:

  • name (Symbol)

Returns:

  • BaseObject, nil



171
172
173
# File 'lib/xsd/base_object.rb', line 171

def map_child(name)
  map_children(name).first
end

#map_children(name) ⇒ Object

Get child objects

Parameters:

  • name (Symbol)


164
165
166
# File 'lib/xsd/base_object.rb', line 164

def map_children(name)
  nodes(name).map { |node| node_to_object(node) }
end

#namespacesObject

Get current namespaces

Returns:

  • Hash



64
65
66
# File 'lib/xsd/base_object.rb', line 64

def namespaces
  node.namespaces || {}
end

#nodeObject

Get current XML node

Returns:

  • Nokogiri::XML::Node



44
45
46
# File 'lib/xsd/base_object.rb', line 44

def node
  options[:node]
end

#node_to_object(node) ⇒ Object

Get reader object for node

Parameters:

  • (Nokogiri::XML::Node)

Returns:

  • BaseObject

Raises:



139
140
141
142
143
144
145
146
147
# File 'lib/xsd/base_object.rb', line 139

def node_to_object(node)
  # check object in cache first
  return reader.object_cache[node.object_id] if reader.object_cache[node.object_id]

  klass = XML::CLASS_MAP[node.name]
  raise Error, "Object class not found for '#{node.name}'" unless klass

  reader.object_cache[node.object_id] = klass.new(options.merge(node: node, schema: schema))
end

#nodes(name = :*, deep = false) ⇒ Object

Get child nodes

Parameters:

  • name (Symbol) (defaults to: :*)

Returns:

  • Nokogiri::XML::NodeSet



71
72
73
# File 'lib/xsd/base_object.rb', line 71

def nodes(name = :*, deep = false)
  node.xpath("./#{deep ? '/' : ''}xs:#{name}", { 'xs' => XML_SCHEMA })
end

#object_by_name(node_name, name) ⇒ Object

Search node by name in all available schemas and return its object

Parameters:

  • node_name (Symbol)
  • name (String)

Returns:

  • BaseObject, nil



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/xsd/base_object.rb', line 116

def object_by_name(node_name, name)
  # get prefix and local name
  name_prefix = get_prefix(name)
  name_local = strip_prefix(name)

  # do not search for built-in types
  return nil if schema.namespace_prefix == name_prefix

  # determine schema for namespace
  search_schemas = schemas_for_namespace(name_prefix)

  # find element in target schema
  search_schemas.each do |s|
    node = s.node.at_xpath("./xs:#{node_name}[@name=\"#{name_local}\"]", { 'xs' => XML_SCHEMA })
    return s.node_to_object(node) if node
  end

  nil
end

#parentObject

Get xml parent object

Returns:

  • BaseObject, nil



151
152
153
# File 'lib/xsd/base_object.rb', line 151

def parent
  node.respond_to?(:parent) && node.parent ? node_to_object(node.parent) : nil
end

#readerObject

Get reader instance

Returns:

  • XML



261
262
263
# File 'lib/xsd/base_object.rb', line 261

def reader
  options[:reader]
end

#schemaObject

Get current schema object

Returns:

  • Schema



157
158
159
# File 'lib/xsd/base_object.rb', line 157

def schema
  options[:schema]
end

#schemas_for_namespace(ns_or_prefix) ⇒ Object

Get schemas by namespace or prefix

Parameters:

  • ns_or_prefix (String, nil)


78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/xsd/base_object.rb', line 78

def schemas_for_namespace(ns_or_prefix)
  # resolve namespace for current node if prefix was provided
  prefix = node.namespaces[['xmlns', (ns_or_prefix == '' ? nil : ns_or_prefix)].compact.join(':')]
  ns = prefix || ns_or_prefix

  if schema.targets_namespace?(ns)
    [schema, *schema.includes.map(&:imported_schema)]
  elsif (import = schema.import_by_namespace(ns))
    [import.imported_schema]
  else
    raise Error, "Schema not found for namespace '#{ns}' in '#{schema.id || schema.target_namespace}'"
  end
end

#strip_prefix(name) ⇒ Object

Strip namespace prefix from node name

Parameters:

  • name (String, nil)

    Name to strip from

Returns:

  • String, nil



178
179
180
# File 'lib/xsd/base_object.rb', line 178

def strip_prefix(name)
  name&.include?(':') ? name.split(':').last : name
end