Module: Lutaml::Xml::AttributeNamespaceResolver

Defined in:
lib/lutaml/xml/attribute_namespace_resolver.rb

Overview

AttributeNamespaceResolver - Handle attribute namespace resolution

Extracts common logic for resolving attribute namespaces across all XML adapters. Handles W3C attributeFormDefault semantics and local namespace declarations.

Examples:

Usage in adapter

ns_info = AttributeNamespaceResolver.resolve(
  rule: attribute_rule,
  attribute: attr,
  plan: plan,
  mapper_class: mapper_class,
  register: @register
)
attr_name = ns_info[:qualified_name]
attributes[attr_name] = value

Class Method Summary collapse

Class Method Details

.build_qualified_name(ns_info, mapping_rule_name, attribute_rule) ⇒ String

Build qualified attribute name based on namespace resolution

Handles W3C attributeFormDefault semantics:

  • Same namespace, unqualified form → NO prefix (inherits from element)

  • Different namespace or qualified → use prefix

Parameters:

  • ns_info (Hash)

    namespace info from resolve method

  • mapping_rule_name (String)

    the base attribute name

  • attribute_rule (MappingRule)

    the attribute mapping rule

Returns:

  • (String)

    the qualified attribute name



125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/lutaml/xml/attribute_namespace_resolver.rb', line 125

def self.build_qualified_name(ns_info, mapping_rule_name,
  attribute_rule)
  if ns_info[:unqualified_same_ns]
    # Same namespace, unqualified form → NO prefix
    # Attribute inherits namespace from element's context
    mapping_rule_name
  elsif ns_info[:prefix]
    "#{ns_info[:prefix]}:#{mapping_rule_name}"
  else
    attribute_rule.prefixed_name
  end
end

.resolve(rule:, attribute:, plan:, mapper_class:, register:) ⇒ Hash

Resolve attribute namespace from mapping rule and attribute definition

Handles W3C attributeFormDefault semantics:

  • Unqualified attributes in same namespace as element → NO prefix

  • Qualified attributes → use prefix

Parameters:

  • rule (MappingRule)

    the attribute mapping rule

  • attribute (Attribute)

    the attribute definition

  • plan (DeclarationPlan)

    the namespace declaration plan

  • mapper_class (Class)

    the model class being serialized

  • register (Symbol)

    the model register

Returns:

  • (Hash)

    namespace info with keys:

    • :prefix [String, nil] - namespace prefix to use (nil for no prefix)

    • :uri [String, nil] - namespace URI

    • :unqualified_same_ns [Boolean] - true if unqualified in same namespace as element

    • :needs_local_declaration [Boolean] - true if xmlns declaration needed

    • :local_xmlns_attr [String, nil] - the xmlns attribute name if declaration needed

    • :local_xmlns_uri [String, nil] - the xmlns URI value if declaration needed



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/lutaml/xml/attribute_namespace_resolver.rb', line 39

def self.resolve(rule:, attribute:, plan:, mapper_class:, register:)
  # Get parent namespace class if available
  parent_ns_class = if mapper_class.is_a?(Class) &&
      mapper_class.include?(Lutaml::Model::Serialize)
                      mapper_class.mappings_for(:xml)&.namespace_class
                    end

  # Get attribute form default from parent's schema (namespace class)
  form_default = parent_ns_class&.attribute_form_default || :unqualified

  # Resolve base namespace using MappingRule
  ns_info = rule.resolve_namespace(
    attr: attribute,
    register: register,
    parent_ns_uri: parent_ns_class&.uri,
    parent_ns_class: parent_ns_class,
    form_default: form_default,
  )

  # Determine if attribute's namespace needs local  declaration
  needs_local = false
  local_xmlns_attr = nil
  local_xmlns_uri = nil

  if ns_info[:prefix] && plan&.namespaces
    # Performance: Use O(1) lookup via find_namespace_by_uri
    ns_decl = if ns_info[:uri]
                # Try O(1) lookup first
                ns_info_from_uri = plan.find_namespace_by_uri(ns_info[:uri])
                if ns_info_from_uri
                  # Build a minimal declaration object for local_on_use check
                  Struct.new(:uri, :local_on_use?, :prefix, :ns_object).new(
                    ns_info[:uri],
                    false, # Will be checked via namespace_classes
                    ns_info_from_uri[:prefix],
                    nil,
                  )
                end
              end

    # Fallback to linear search if O(1) lookup didn't find it
    ns_decl ||= if ns_info[:uri]
                  plan.namespaces.values.find do |decl|
                    decl.uri == ns_info[:uri]
                  end
                else
                  plan.namespaces.values.find do |decl|
                    decl.ns_object.prefix_default == ns_info[:prefix] ||
                      decl.prefix == ns_info[:prefix]
                  end
                end

    # Check if namespace is marked for local declaration
    if ns_decl&.local_on_use?
      needs_local = true
      # Handle both default (nil prefix) and prefixed namespaces
      local_xmlns_attr = if ns_info[:prefix]
                           "xmlns:#{ns_info[:prefix]}"
                         else
                           "xmlns"
                         end
      local_xmlns_uri = ns_decl.uri || ns_decl.ns_object.uri
    end
  end

  # Return complete namespace information
  {
    prefix: ns_info[:prefix],
    uri: ns_info[:uri],
    unqualified_same_ns: ns_info[:unqualified_same_ns],
    needs_local_declaration: needs_local,
    local_xmlns_attr: local_xmlns_attr,
    local_xmlns_uri: local_xmlns_uri,
  }
end