Class: WSDL::Schema::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/wsdl/schema/node.rb

Overview

Unified representation of all XSD schema nodes.

Instead of a deep class hierarchy where most classes are empty, this single class represents all XSD constructs and uses the #kind attribute to determine behavior.

rubocop:disable Metrics/ClassLength

Examples:

Basic usage

node = Schema::Node.new(nokogiri_node, collection, context)
node.kind        # => :complexType
node.name        # => "UserType"
node.elements    # => [Node, Node, ...]

Pattern matching (Ruby 3.0+)

case node
in { kind: :element, name: }
  puts "Element: #{name}"
in { kind: :complexType }
  puts "Complex type with #{node.elements.count} elements"
end

Constant Summary collapse

ELEMENT_TERMINATORS =

Node kinds that terminate element collection (contain no child elements).

Set[
  :attribute,
  :annotation,
  :simpleContent
].freeze
ATTRIBUTE_TERMINATORS =

Node kinds that terminate attribute collection.

Set[
  :annotation
].freeze
ELEMENT_KINDS =

Node kinds representing actual schema elements.

Set[:element, :any].freeze
ATTRIBUTE_KINDS =

Node kinds representing attributes.

Set[:attribute].freeze
INLINE_TYPE_KINDS =

Node kinds that can have inline type definitions.

Set[:complexType, :simpleType].freeze

Instance Attribute Summary collapse

Common Attributes collapse

Children & Traversal collapse

Type Resolution collapse

xs:any Wildcard Support collapse

Cardinality collapse

Pattern Matching collapse

Instance Method Summary collapse

Constructor Details

#initialize(xml_node, collection, context = {}) ⇒ Node

Creates a new Node from an XSD element.

Parameters:

  • xml_node (Nokogiri::XML::Node)

    the XSD element node

  • collection (Collection)

    the schema collection for resolving refs

  • context (Hash) (defaults to: {})

    schema context

Options Hash (context):

  • :target_namespace (String)

    the target namespace URI

  • :element_form_default (String)

    'qualified' or 'unqualified'

  • :schema_namespaces (Hash)

    cached namespace declarations from the parent schema



60
61
62
63
64
65
66
# File 'lib/wsdl/schema/node.rb', line 60

def initialize(xml_node, collection, context = {})
  @xml_node = xml_node
  @collection = collection
  @context = context

  @kind = xml_node.name.to_sym
end

Instance Attribute Details

#kindSymbol (readonly)

Returns the XSD element type (:element, :complexType, :sequence, etc.).

Returns:

  • (Symbol)

    the XSD element type (:element, :complexType, :sequence, etc.)



69
70
71
# File 'lib/wsdl/schema/node.rb', line 69

def kind
  @kind
end

#xml_nodeNokogiri::XML::Node? (readonly)

Returns the underlying XML node (nil after #release_dom_references!).

Returns:



73
74
75
# File 'lib/wsdl/schema/node.rb', line 73

def xml_node
  @xml_node
end

Instance Method Details

#[](attr_name) ⇒ String?

Accesses any attribute from the underlying node.

Parameters:

  • attr_name (String)

    the attribute name

Returns:

  • (String, nil)

    the attribute value



168
169
170
# File 'lib/wsdl/schema/node.rb', line 168

def [](attr_name)
  @xml_node[attr_name]
end

#any?Boolean

Returns true if this is an xs:any wildcard.

Returns:

  • (Boolean)

    true if this is an xs:any wildcard



326
327
328
# File 'lib/wsdl/schema/node.rb', line 326

def any?
  kind == :any
end

#attributes(memo = [], limits: nil) ⇒ Array<Node>

Collects all attribute definitions from this node and descendants.

Traverses the type hierarchy to find all xs:attribute definitions. Handles attributeGroup references. Skips unresolvable references and continues collecting.

Parameters:

  • memo (Array<Node>) (defaults to: [])

    accumulator for recursive traversal

  • limits (Limits, nil) (defaults to: nil)

    resource limits for validation

Returns:

  • (Array<Node>)

    all attribute definitions found

Raises:



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/wsdl/schema/node.rb', line 255

def attributes(memo = [], limits: nil)
  return memo if ATTRIBUTE_TERMINATORS.include?(kind)
  return resolve_attribute_group_ref(memo, limits:) if kind == :attributeGroup && ref

  if @cached_attributes
    memo.concat(@cached_attributes)
    validate_attribute_count!(memo.size, limits)
    return memo
  end

  before = memo.size
  memo = collect_child_attributes(memo, limits:)
  @cached_attributes = memo[before..].freeze if kind == :complexType
  memo
end

#baseString?

Returns the base type for restrictions/extensions.

Returns:

  • (String, nil)

    the base type for restrictions/extensions



115
116
117
118
119
# File 'lib/wsdl/schema/node.rb', line 115

def base
  return @base if defined?(@base)

  @base = @xml_node['base']
end

#childrenArray<Node>

Returns the parsed child nodes.

Returns:

  • (Array<Node>)

    the child nodes



197
198
199
# File 'lib/wsdl/schema/node.rb', line 197

def children
  @children ||= @xml_node.element_children.map { |n| Node.new(n, @collection, @context) }
end

#deconstruct_keys(_keys) ⇒ Hash

Supports Ruby pattern matching.

Examples:

case node
in { kind: :element, name: "User" }
  # matches element named "User"
end

Parameters:

  • _keys (Array<Symbol>, nil)

    keys to extract (unused, returns all keys)

Returns:

  • (Hash)

    deconstructed key-value pairs



401
402
403
404
405
406
407
408
409
# File 'lib/wsdl/schema/node.rb', line 401

def deconstruct_keys(_keys)
  {
    kind:,
    name:,
    type:,
    ref:,
    namespace:
  }
end

#defaultString?

Returns the default value.

Returns:

  • (String, nil)

    the default value



129
130
131
132
133
# File 'lib/wsdl/schema/node.rb', line 129

def default
  return @default if defined?(@default)

  @default = @xml_node['default']
end

#elements(memo = [], limits: nil) ⇒ Array<Node>

Collects all element definitions from this node and descendants.

Traverses the type hierarchy to find all xs:element definitions, which represent the actual content model of complex types. Handles extension base type inheritance. Skips unresolvable references and continues collecting.

Parameters:

  • memo (Array<Node>) (defaults to: [])

    accumulator for recursive traversal

  • limits (Limits, nil) (defaults to: nil)

    resource limits for validation

Returns:

  • (Array<Node>)

    all element definitions found

Raises:



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/wsdl/schema/node.rb', line 228

def elements(memo = [], limits: nil)
  return memo if ELEMENT_TERMINATORS.include?(kind)
  return resolve_group_ref(memo, limits:) if kind == :group && ref

  if @cached_elements
    memo.concat(@cached_elements)
    validate_element_count!(memo.size, limits)
    return memo
  end

  before = memo.size
  include_base_type_elements(memo, limits:) if kind == :extension
  collect_child_elements(memo, limits:)
  @cached_elements = memo[before..].freeze if kind == :complexType
  memo
end

#empty?Boolean

Returns whether this node has no meaningful content.

A node is considered empty if it has no children, or if its only children are empty compositors (sequence, all, choice with no elements).

Returns:

  • (Boolean)

    true if the node has no meaningful content



207
208
209
210
211
212
213
214
215
# File 'lib/wsdl/schema/node.rb', line 207

def empty?
  return true if children.empty?

  # Check if all children are empty compositors
  compositor_kinds = %i[sequence all choice]
  children.all? do |child|
    compositor_kinds.include?(child.kind) && child.children.empty?
  end
end

#fixedString?

Returns the fixed value.

Returns:

  • (String, nil)

    the fixed value



136
137
138
139
140
# File 'lib/wsdl/schema/node.rb', line 136

def fixed
  return @fixed if defined?(@fixed)

  @fixed = @xml_node['fixed']
end

#formString

Returns the element form (qualified or unqualified).

Returns:

  • (String)

    'qualified' or 'unqualified'



157
158
159
160
161
162
# File 'lib/wsdl/schema/node.rb', line 157

def form
  return @form if defined?(@form)

  @form = @xml_node['form'] ||
          (@context[:element_form_default] == 'qualified' ? 'qualified' : 'unqualified')
end

#inline_typeNode?

Returns the inline type definition if present.

An inline type is a complex or simple type defined directly within the element rather than referenced by name. Skips annotation elements.

Returns:

  • (Node, nil)

    the inline type, or nil if none



281
282
283
# File 'lib/wsdl/schema/node.rb', line 281

def inline_type
  children.find { |c| INLINE_TYPE_KINDS.include?(c.kind) }
end

#inspectString

Returns a string representation for debugging.

Returns:

  • (String)

    formatted debug string



416
417
418
419
# File 'lib/wsdl/schema/node.rb', line 416

def inspect
  attrs = @xml_node.attribute_nodes.map { |a| "#{a.name}=#{a.value.inspect}" }.join(' ')
  "#<Schema::Node:#{kind} #{attrs}>"
end

#list_item_typeString?

Returns the item type from a list derivation.

Returns:

  • (String, nil)

    the itemType attribute value



296
297
298
299
# File 'lib/wsdl/schema/node.rb', line 296

def list_item_type
  list = children.find { |c| c.kind == :list }
  list&.[]('itemType')
end

#max_occursString

Returns the maxOccurs value ('1', 'unbounded', etc.).

Returns:

  • (String)

    the maxOccurs value ('1', 'unbounded', etc.)



353
354
355
356
357
# File 'lib/wsdl/schema/node.rb', line 353

def max_occurs
  return @max_occurs if defined?(@max_occurs)

  @max_occurs = @xml_node['maxOccurs'] || '1'
end

#min_occursString

Returns the minOccurs value ('0', '1', etc.).

Returns:

  • (String)

    the minOccurs value ('0', '1', etc.)



360
361
362
363
364
# File 'lib/wsdl/schema/node.rb', line 360

def min_occurs
  return @min_occurs if defined?(@min_occurs)

  @min_occurs = @xml_node['minOccurs'] || '1'
end

#multiple?Boolean

Returns whether this element can appear multiple times.

Returns:

  • (Boolean)

    true if maxOccurs is unbounded or > 1



369
370
371
# File 'lib/wsdl/schema/node.rb', line 369

def multiple?
  max_occurs == 'unbounded' || max_occurs.to_i > 1
end

#nameString?

Returns the local name of this node.

Returns:

  • (String, nil)

    the local name of this node



94
95
96
97
98
# File 'lib/wsdl/schema/node.rb', line 94

def name
  return @name if defined?(@name)

  @name = @xml_node['name']
end

#namespaceString?

Returns the target namespace URI.

Returns:

  • (String, nil)

    the target namespace URI



150
151
152
# File 'lib/wsdl/schema/node.rb', line 150

def namespace
  @context[:target_namespace]
end

#namespace_constraintString

Returns the namespace constraint for wildcards.

Returns:

  • (String)

    '##any', '##other', '##local', '##targetNamespace', or a URI



333
334
335
336
337
# File 'lib/wsdl/schema/node.rb', line 333

def namespace_constraint
  return @namespace_constraint if defined?(@namespace_constraint)

  @namespace_constraint = @xml_node['namespace'] || '##any'
end

#namespacesHash{String => String}

Returns XML namespace declarations in scope.

Computed lazily because many intermediate schema nodes (sequence, all, choice, etc.) never have their namespaces accessed externally. Reuses the cached schema-level namespaces when the node has no local namespace declarations, avoiding expensive Nokogiri traversal.

Returns:

  • (Hash{String => String})

    XML namespace declarations in scope



83
84
85
86
87
88
89
# File 'lib/wsdl/schema/node.rb', line 83

def namespaces
  @namespaces ||= if @context[:schema_namespaces] && @xml_node.namespace_definitions.empty?
    @context[:schema_namespaces]
  else
    @xml_node.namespaces.freeze
  end
end

#nillable?Boolean

Returns whether this element allows nil values (xsi:nil="true").

Returns:

  • (Boolean)

    whether this element allows nil values (xsi:nil="true")



143
144
145
146
147
# File 'lib/wsdl/schema/node.rb', line 143

def nillable?
  return @nillable if defined?(@nillable)

  @nillable = @xml_node['nillable'] == 'true'
end

#optional?Boolean

Returns whether this element is optional.

Returns:

  • (Boolean)

    true if minOccurs is 0



376
377
378
# File 'lib/wsdl/schema/node.rb', line 376

def optional?
  min_occurs == '0'
end

#process_contentsString

Returns how wildcard content should be validated.

Returns:

  • (String)

    'strict', 'lax', or 'skip'



342
343
344
345
346
# File 'lib/wsdl/schema/node.rb', line 342

def process_contents
  return @process_contents if defined?(@process_contents)

  @process_contents = @xml_node['processContents'] || 'strict'
end

#refString?

Returns the qualified element/attribute reference.

Returns:

  • (String, nil)

    the qualified element/attribute reference



108
109
110
111
112
# File 'lib/wsdl/schema/node.rb', line 108

def ref
  return @ref if defined?(@ref)

  @ref = @xml_node['ref']
end

#release_dom_references!void

This method returns an undefined value.

Releases all references to the Nokogiri DOM.

After Definition::Builder extracts data into the frozen Definition IR, the DOM nodes are no longer needed. Calling this method nils out the Nokogiri reference and any cached child/element/attribute arrays so the GC can reclaim the DOM while the Collection is still reachable.

Safe to call multiple times. Preserves #kind and any already-extracted scalar attributes (+@name+, @type, etc.) since those are plain Ruby strings.



185
186
187
188
189
190
# File 'lib/wsdl/schema/node.rb', line 185

def release_dom_references!
  @xml_node = nil
  @children = nil
  @cached_elements = nil
  @cached_attributes = nil
end

#required?Boolean

Returns whether this element is required.

Returns:

  • (Boolean)

    true if minOccurs is not 0



383
384
385
# File 'lib/wsdl/schema/node.rb', line 383

def required?
  !optional?
end

#restriction_baseString?

Returns the base type from a restriction child.

Returns:

  • (String, nil)

    the base type name



288
289
290
291
# File 'lib/wsdl/schema/node.rb', line 288

def restriction_base
  restriction = children.find { |c| c.kind == :restriction }
  restriction&.base
end

#typeString?

Returns the qualified type reference.

Returns:

  • (String, nil)

    the qualified type reference



101
102
103
104
105
# File 'lib/wsdl/schema/node.rb', line 101

def type
  return @type if defined?(@type)

  @type = @xml_node['type']
end

#type_idString?

Returns a unique identifier for this type.

Used to detect recursive type definitions during element building.

Returns:

  • (String, nil)

    the type ID in "namespace:name" format



314
315
316
317
318
319
# File 'lib/wsdl/schema/node.rb', line 314

def type_id
  return nil unless %i[complexType element].include?(kind)
  return nil unless name

  "#{namespace}:#{name}"
end

#union_member_typesString?

Returns the member types from a union derivation.

Returns:

  • (String, nil)

    the memberTypes attribute value



304
305
306
307
# File 'lib/wsdl/schema/node.rb', line 304

def union_member_types
  union = children.find { |c| c.kind == :union }
  union&.[]('memberTypes')
end

#useString

Returns the use constraint ('optional' or 'required').

Returns:

  • (String)

    the use constraint ('optional' or 'required')



122
123
124
125
126
# File 'lib/wsdl/schema/node.rb', line 122

def use
  return @use if defined?(@use)

  @use = @xml_node['use'] || 'optional'
end