Class: Lutaml::Xml::DeclarationPlan
- Inherits:
-
Object
- Object
- Lutaml::Xml::DeclarationPlan
- Defined in:
- lib/lutaml/xml/declaration_plan.rb,
lib/lutaml/xml/declaration_plan/element_node.rb,
lib/lutaml/xml/declaration_plan/attribute_node.rb
Overview
Represents the complete namespace declaration plan for an XML element
DeclarationPlan uses a TREE STRUCTURE that is isomorphic to XmlDataModel, enabling index-based parallel traversal for W3C-compliant attribute prefix handling.
The tree consists of ElementNode objects (containing AttributeNode arrays), structured identically to the XmlDataModel tree to enable position-based matching.
Defined Under Namespace
Classes: AttributeNode, ElementNode
Instance Attribute Summary collapse
-
#children_plans ⇒ Hash
readonly
Children plans (for collection items).
-
#global_prefix_registry ⇒ Hash<String, String>
readonly
Global prefix registry (URI => prefix).
-
#input_formats ⇒ Hash<String, Symbol>
readonly
Input format tracking (URI => :default or :prefix).
-
#input_prefix_formats ⇒ Hash<String, Symbol>
readonly
Key: “#prefix:#uri” for prefixed, “:#uri” for default Value: :default or :prefix.
-
#namespace_classes ⇒ Hash<String, Class>
readonly
Namespace classes by URI (for namespace lookup).
-
#namespace_locations ⇒ Hash<String, Hash>
Format: { “elementName” => { nil => “uri” } or { “prefix” => “uri” } } Used for preserving original namespace URIs during serialization.
-
#original_namespace_uris ⇒ Hash<String, String>
readonly
Used for round-trip fidelity when namespace has uri_aliases.
-
#root_node ⇒ ElementNode
readonly
Root of the element tree containing all decisions.
Class Method Summary collapse
-
.empty ⇒ DeclarationPlan
Create an empty plan (for compatibility).
-
.from_input(input_namespaces, mapping) ⇒ DeclarationPlan
Create DeclarationPlan from parsed XML input namespaces Used during deserialization to capture input format for round-trip preservation.
-
.from_input_with_locations(namespaces_with_locations, mapping) ⇒ DeclarationPlan
Create DeclarationPlan from parsed XML input namespaces WITH location tracking.
Instance Method Summary collapse
-
#[](key) ⇒ Hash?
Backward-compatible Hash-like access for older adapters.
-
#child_plan(name) ⇒ DeclarationPlan?
Get child element plan by attribute name.
-
#collect_ns_classes_recursive(element_node, ns_classes) ⇒ void
Recursively collect namespace classes from element nodes.
-
#find_namespace_by_uri(uri) ⇒ Hash?
Performance: O(1) namespace lookup by URI.
-
#initialize(root_node:, global_prefix_registry: {}, input_formats: {}, namespace_classes: {}, children_plans: {}, input_prefix_formats: {}, original_namespace_uris: {}) ⇒ DeclarationPlan
constructor
Initialize a declaration plan with tree structure.
-
#namespace(key) ⇒ NamespaceDeclaration?
Get a specific namespace declaration by key.
-
#namespace_declared_at_path?(uri, path) ⇒ Boolean
Check if a namespace was declared at a specific path in the input.
-
#namespace_for_class(ns_class) ⇒ NamespaceDeclaration?
Get namespace declaration by namespace class.
-
#namespaces ⇒ Hash<String, NamespaceDeclaration>
Get namespace declarations as a Hash.
-
#namespaces_at_path(path) ⇒ Hash?
Get namespace declarations at a specific element path.
-
#namespaces_with_schema_location ⇒ Array<Class>
Collect all namespace classes in the tree that have schema_location.
-
#xsi_prefix ⇒ String?
Get the XSI prefix from hoisted_declarations.
Constructor Details
#initialize(root_node:, global_prefix_registry: {}, input_formats: {}, namespace_classes: {}, children_plans: {}, input_prefix_formats: {}, original_namespace_uris: {}) ⇒ DeclarationPlan
Initialize a declaration plan with tree structure
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 59 def initialize(root_node:, global_prefix_registry: {}, input_formats: {}, namespace_classes: {}, children_plans: {}, input_prefix_formats: {}, original_namespace_uris: {}) @root_node = root_node @global_prefix_registry = global_prefix_registry @input_formats = input_formats @namespace_classes = namespace_classes @children_plans = children_plans @input_prefix_formats = input_prefix_formats @original_namespace_uris = original_namespace_uris # Performance: Cached lookups @namespaces_cache = nil @uri_to_info_cache = nil end |
Instance Attribute Details
#children_plans ⇒ Hash (readonly)
Returns Children plans (for collection items).
40 41 42 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 40 def children_plans @children_plans end |
#global_prefix_registry ⇒ Hash<String, String> (readonly)
Returns Global prefix registry (URI => prefix).
26 27 28 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 26 def global_prefix_registry @global_prefix_registry end |
#input_formats ⇒ Hash<String, Symbol> (readonly)
Returns Input format tracking (URI => :default or :prefix).
29 30 31 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 29 def input_formats @input_formats end |
#input_prefix_formats ⇒ Hash<String, Symbol> (readonly)
Key: “#prefix:#uri” for prefixed, “:#uri” for default Value: :default or :prefix
34 35 36 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 34 def input_prefix_formats @input_prefix_formats end |
#namespace_classes ⇒ Hash<String, Class> (readonly)
Returns Namespace classes by URI (for namespace lookup).
37 38 39 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 37 def namespace_classes @namespace_classes end |
#namespace_locations ⇒ Hash<String, Hash>
Format: { “elementName” => { nil => “uri” } or { “prefix” => “uri” } } Used for preserving original namespace URIs during serialization.
49 50 51 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 49 def namespace_locations @namespace_locations end |
#original_namespace_uris ⇒ Hash<String, String> (readonly)
Used for round-trip fidelity when namespace has uri_aliases.
44 45 46 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 44 def original_namespace_uris @original_namespace_uris end |
#root_node ⇒ ElementNode (readonly)
Returns Root of the element tree containing all decisions.
23 24 25 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 23 def root_node @root_node end |
Class Method Details
.empty ⇒ DeclarationPlan
Create an empty plan (for compatibility)
132 133 134 135 136 137 138 139 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 132 def self.empty empty_node = ElementNode.new( qualified_name: "", use_prefix: nil, hoisted_declarations: {}, ) new(root_node: empty_node, global_prefix_registry: {}) end |
.from_input(input_namespaces, mapping) ⇒ DeclarationPlan
Create DeclarationPlan from parsed XML input namespaces Used during deserialization to capture input format for round-trip preservation
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 147 def self.from_input(input_namespaces, mapping) # Create minimal tree with just root node capturing input xmlns hoisted = {} # Track input formats input_formats = {} input_prefix_formats = {} # NEW: per-(prefix, URI) format input_namespaces.each_value do |ns_config| prefix = ns_config[:prefix] uri = ns_config[:uri] format = ns_config[:format] || (prefix ? :prefix : :default) # CRITICAL: Hash key based on FORMAT # nil = default namespace (xmlns="...") # "prefix" = prefixed namespace (xmlns:prefix="...") xmlns_key = if format == :default nil else prefix end hoisted[xmlns_key] = uri # Track format used in input for this URI # ONLY for default namespace declarations (xmlns="..."). # For prefixed namespaces (xmlns:prefix="..."), the format is tracked # in input_prefix_formats, not input_formats. input_formats tracks # namespace-level format for elements WITHOUT their own prefix. input_formats[uri] = format if format == :default # NEW: Build per-prefix-URI format key = prefix ? "#{prefix}:#{uri}" : ":#{uri}" input_prefix_formats[key] = format end # Build global prefix registry (only for prefixed namespaces) registry = {} input_namespaces.each_value do |ns_config| if ns_config[:format] == :prefix && ns_config[:prefix] registry[ns_config[:uri]] = ns_config[:prefix] end end root_node = ElementNode.new( qualified_name: mapping.root_element || "", use_prefix: nil, # Will be determined from input_formats hoisted_declarations: hoisted, ) new(root_node: root_node, global_prefix_registry: registry, input_formats: input_formats, input_prefix_formats: input_prefix_formats) end |
.from_input_with_locations(namespaces_with_locations, mapping) ⇒ DeclarationPlan
Create DeclarationPlan from parsed XML input namespaces WITH location tracking
This method preserves WHERE each namespace was declared in the original XML, enabling proper round-trip fidelity for namespace declarations.
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 211 def self.from_input_with_locations(namespaces_with_locations, mapping) # Build hoisted declarations for root (empty path) root_namespaces = namespaces_with_locations[[]] || {} # Track input formats for ALL namespaces (for format preservation) input_formats = {} input_prefix_formats = {} # NEW: per-(prefix, URI) format root_hoisted = {} # Process root namespaces root_namespaces.each_value do |ns_config| prefix = ns_config[:prefix] uri = ns_config[:uri] format = ns_config[:format] || (prefix ? :prefix : :default) xmlns_key = format == :default ? nil : prefix root_hoisted[xmlns_key] = uri # Only track default namespace format in input_formats # Prefixed namespaces are tracked in input_prefix_formats input_formats[uri] = format if format == :default # NEW: Build per-prefix-URI format key = prefix ? "#{prefix}:#{uri}" : ":#{uri}" input_prefix_formats[key] = format end # Build global prefix registry from ALL locations registry = {} namespaces_with_locations.each_value do |ns_hash| ns_hash.each_value do |ns_config| if ns_config[:format] == :prefix && ns_config[:prefix] registry[ns_config[:uri]] = ns_config[:prefix] end # Track format for default namespaces only # Prefixed namespaces are tracked in input_prefix_formats format = ns_config[:format] || (prefix ? :prefix : :default) input_formats[ns_config[:uri]] = format if format == :default # NEW: Build per-prefix-URI format for all locations prefix = ns_config[:prefix] uri = ns_config[:uri] key = prefix ? "#{prefix}:#{uri}" : ":#{uri}" input_prefix_formats[key] = ns_config[:format] || (prefix ? :prefix : :default) end end # Build element node tree with location info root_node = ElementNode.new( qualified_name: mapping.root_element || "", use_prefix: nil, hoisted_declarations: root_hoisted, ) # Store location data for use during serialization # This is the KEY addition - tracking WHERE namespaces were declared location_data = {} namespaces_with_locations.each do |path, ns_hash| next if path.empty? # Root already handled # Convert path array to string key for storage path_key = path.join("/") hoisted = {} ns_hash.each_value do |ns_config| prefix = ns_config[:prefix] uri = ns_config[:uri] format = ns_config[:format] || (prefix ? :prefix : :default) xmlns_key = format == :default ? nil : prefix hoisted[xmlns_key] = uri end location_data[path_key] = hoisted end plan = new(root_node: root_node, global_prefix_registry: registry, input_formats: input_formats, input_prefix_formats: input_prefix_formats) plan.namespace_locations = location_data plan end |
Instance Method Details
#[](key) ⇒ Hash?
Backward-compatible Hash-like access for older adapters
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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 79 def [](key) case key when :namespaces # Convert namespaces to the old Hash format expected by REXML adapter ns_hash = {} return nil unless @namespace_classes @namespace_classes.each do |uri, ns_class| key_str = ns_class.to_key # Determine format and build ns_config hash format = @input_formats[uri] || ( if @root_node.hoisted_declarations.value?(uri) hoisted_key = @root_node.hoisted_declarations.key(uri) hoisted_key.nil? ? :default : :prefix else ns_class.prefix_default ? :prefix : :default end ) prefix_override = nil if format == :prefix hoisted_key = @root_node.hoisted_declarations.key(uri) prefix_override = hoisted_key if hoisted_key end # Build xmlns_declaration string xmlns_decl = if format == :prefix "xmlns:#{prefix_override || ns_class.prefix_default}=\"#{uri}\"" else "xmlns=\"#{uri}\"" end ns_hash[key_str] = { ns_object: ns_class, format: format, declared_at: :here, xmlns_declaration: xmlns_decl, prefix_override: prefix_override, } end ns_hash when :children_plans @children_plans when :type_namespaces # type_namespaces is no longer used in the same way, return empty hash {} end end |
#child_plan(name) ⇒ DeclarationPlan?
Get child element plan by attribute name
431 432 433 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 431 def child_plan(name) @children_plans&.dig(name) end |
#collect_ns_classes_recursive(element_node, ns_classes) ⇒ void
This method returns an undefined value.
Recursively collect namespace classes from element nodes
453 454 455 456 457 458 459 460 461 462 463 464 465 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 453 def collect_ns_classes_recursive(element_node, ns_classes) # Add namespace from element's own namespace_class if element_node.respond_to?(:qualified_name) && @namespace_classes # Try to find namespace class from namespace_classes by matching URI # The ElementNode itself doesn't store ns_class, but we can check if # any of our namespace_classes match the element's context end # Recursively collect from children element_node.element_nodes.each do |child_node| collect_ns_classes_recursive(child_node, ns_classes) end end |
#find_namespace_by_uri(uri) ⇒ Hash?
Performance: O(1) namespace lookup by URI
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 408 def find_namespace_by_uri(uri) return nil unless uri # Build cache on first access unless @uri_to_info_cache @uri_to_info_cache = {} @root_node.hoisted_declarations.each do |xmlns_key, xmlns_uri| @uri_to_info_cache[xmlns_uri] = { prefix: xmlns_key, format: xmlns_key ? :prefix : :default, declared_at: :here, uri: xmlns_uri, } end end @uri_to_info_cache[uri] end |
#namespace(key) ⇒ NamespaceDeclaration?
Get a specific namespace declaration by key
385 386 387 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 385 def namespace(key) namespaces[key] end |
#namespace_declared_at_path?(uri, path) ⇒ Boolean
Check if a namespace was declared at a specific path in the input
314 315 316 317 318 319 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 314 def namespace_declared_at_path?(uri, path) ns_at_path = namespaces_at_path(path) return false unless ns_at_path ns_at_path.value?(uri) end |
#namespace_for_class(ns_class) ⇒ NamespaceDeclaration?
Get namespace declaration by namespace class
393 394 395 396 397 398 399 400 401 402 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 393 def namespace_for_class(ns_class) return nil unless ns_class && @namespace_classes # Find the URI for this namespace class uri = @namespace_classes.key(ns_class) return nil unless uri # Get the namespace using the class's to_key method namespace(ns_class.to_key) end |
#namespaces ⇒ Hash<String, NamespaceDeclaration>
Get namespace declarations as a Hash
Converts hoisted_declarations into NamespaceDeclaration objects for querying and inspection.
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 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 327 def namespaces # Performance: Return cached result if available return @namespaces_cache if @namespaces_cache return {} unless @namespace_classes result = {} @namespace_classes.each do |uri, ns_class| key = ns_class.to_key # Determine format by checking hoisted_declarations # Priority: input_formats (for preservation) > hoisted_declarations (actual format) > default format = @input_formats[uri] prefix_override = nil unless format # Check hoisted_declarations to see what format is actually being used # hoisted_declarations keys: nil = default format, "prefix" = prefix format hoisted_key = @root_node.hoisted_declarations.key(uri) if @root_node.hoisted_declarations.value?(uri) # Namespace IS in hoisted_declarations - check what key it has hoisted_key = @root_node.hoisted_declarations.key(uri) if hoisted_key.nil? # Namespace found with nil key = default format format = :default else # Namespace found with prefix key = prefix format format = :prefix # Set prefix_override to the actual prefix being used prefix_override = hoisted_key end else # Namespace NOT found in hoisted_declarations, use default logic # For root elements, prefer default format unless namespace has prefix_default and no hoisted format = ns_class.prefix_default ? :prefix : :default end end # Check if this format came from input (for from_input? method) from_input = @input_formats.key?(uri) data = NamespaceDeclarationData.new( namespace_class: ns_class, format: format, declared_at: :here, source: from_input ? :input : nil, prefix_override: prefix_override, ) result[key] = NamespaceDeclaration.new(data) end # Performance: Cache the result @namespaces_cache = result end |
#namespaces_at_path(path) ⇒ Hash?
Get namespace declarations at a specific element path
295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 295 def namespaces_at_path(path) return nil unless @namespace_locations # For root path (empty array), return root_node's hoisted declarations # since root namespaces are stored there (not in @namespace_locations # which only has child paths) if path.empty? return root_node.hoisted_declarations end path_key = path.join("/") @namespace_locations[path_key] end |
#namespaces_with_schema_location ⇒ Array<Class>
Collect all namespace classes in the tree that have schema_location
438 439 440 441 442 443 444 445 446 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 438 def namespaces_with_schema_location return [] unless @namespace_classes ns_classes = [] collect_ns_classes_recursive(@root_node, ns_classes) ns_classes.uniq.select do |ns_class| ns_class.respond_to?(:schema_location) && ns_class.schema_location end end |
#xsi_prefix ⇒ String?
Get the XSI prefix from hoisted_declarations
470 471 472 473 474 475 476 477 |
# File 'lib/lutaml/xml/declaration_plan.rb', line 470 def xsi_prefix return nil unless @root_node&.hoisted_declarations @root_node.hoisted_declarations.each do |prefix, uri| return prefix if uri == W3c::XsiNamespace.uri end nil end |