Class: Lutaml::Xml::ModelTransform
- Inherits:
-
Model::Transform
- Object
- Model::Transform
- Lutaml::Xml::ModelTransform
- Defined in:
- lib/lutaml/xml/model_transform.rb
Overview
ModelTransform is the XML transform handler for the model layer. It inherits from Lutaml::Model::Transform and implements XML-specific data_to_model and model_to_data methods.
This class bridges the model layer and the XML module:
-
Inherits from Lutaml::Model::Transform (model’s format-agnostic base)
-
Delegates to Lutaml::Xml::Transformation for actual XML serialization
-
Used by model’s serialization pipeline via Transform.for(:xml)
Direct Known Subclasses
Constant Summary collapse
- EMPTY_HASH =
Performance: Frozen empty hash to reduce allocations
{}.freeze
Instance Attribute Summary
Attributes inherited from Model::Transform
#attributes, #context, #lutaml_register
Class Method Summary collapse
-
.collect_element_namespaces(element, path = [], result = nil, visited = nil) ⇒ Hash
Recursively collect namespace declarations from all elements in the tree.
Instance Method Summary collapse
-
#build_input_declaration_plan(root_element) ⇒ DeclarationPlan?
Build a DeclarationPlan from the parsed element tree’s namespace declarations.
-
#collect_element_namespaces ⇒ Object
Instance method delegates to class method.
- #data_to_model(data, _format, options = {}) ⇒ Object
- #model_to_data(model, _format, options = {}) ⇒ Object
Methods inherited from Model::Transform
data_to_model, #initialize, #model_class, model_to_data
Constructor Details
This class inherits a constructor from Lutaml::Model::Transform
Class Method Details
.collect_element_namespaces(element, path = [], result = nil, visited = nil) ⇒ Hash
Recursively collect namespace declarations from all elements in the tree.
Each element may declare namespaces via xmlns attributes. The path array tracks the element names from root to current element. Root has path [], its children have path [“childName”], etc. This matches DeclarationPlan.from_input_with_locations expectations where root path is [] and child paths are built by appending.
Recursively collect namespace declarations from all elements in the tree.
Each element may declare namespaces via xmlns attributes. The path array tracks the element names from root to current element. Root has path [], its children have path [“childName”], etc. This matches DeclarationPlan.from_input_with_locations expectations where root path is [] and child paths are built by appending.
Available as both class method (for lazy plan building from instance context) and instance method (for use within ModelTransform).
139 140 141 142 143 144 145 146 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 |
# File 'lib/lutaml/xml/model_transform.rb', line 139 def self.collect_element_namespaces(element, path = [], result = nil, visited = nil) result ||= {} visited ||= Set.new return result unless element return result if visited.include?(element.object_id) visited.add(element.object_id) # Collect this element's own namespace declarations own_ns = element.own_namespaces if own_ns&.any? input_namespaces = {} own_ns.each do |prefix, ns_data| key = prefix.nil? ? :default : prefix input_namespaces[key] = { uri: ns_data.uri, prefix: prefix, format: prefix.nil? ? :default : :prefix, } end result[path] = input_namespaces unless input_namespaces.empty? end # Recurse into children with path extended by child local name # Use local name (without prefix) since serialization lookup uses # xml_element.name which is the local name without namespace prefix element.children.each do |child| next if child.is_a?(String) # Skip text nodes # Strip namespace prefix if present (e.g., "c:childName" -> "childName") # to match the key format used during serialization lookup full_name = child.name.to_s child_local_name = if full_name.include?(":") full_name.split(":", 2).last else full_name end child_path = path + [child_local_name] collect_element_namespaces(child, child_path, result, visited) end result end |
Instance Method Details
#build_input_declaration_plan(root_element) ⇒ DeclarationPlan?
Build a DeclarationPlan from the parsed element tree’s namespace declarations.
Walks the entire element tree to capture ALL xmlns declarations from input XML, including unused ones (like xmlns:xi for XInclude) and declarations on child elements. The plan preserves WHERE each namespace was declared and its original format/URI.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/lutaml/xml/model_transform.rb', line 101 def build_input_declaration_plan(root_element) return nil unless root_element # Walk the element tree collecting namespaces with their declaration locations namespaces_with_locations = collect_element_namespaces(root_element) return nil if namespaces_with_locations.nil? || namespaces_with_locations.empty? # Get the mapping for namespace resolution xml_mapping = mappings_for(:xml) # Create location-aware DeclarationPlan DeclarationPlan.from_input_with_locations(namespaces_with_locations, xml_mapping) end |
#collect_element_namespaces ⇒ Object
Instance method delegates to class method
187 188 189 |
# File 'lib/lutaml/xml/model_transform.rb', line 187 def collect_element_namespaces(...) self.class.collect_element_namespaces(...) end |
#data_to_model(data, _format, options = {}) ⇒ Object
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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 |
# File 'lib/lutaml/xml/model_transform.rb', line 18 def data_to_model(data, _format, = {}) # Use child's own default register if it has one # This ensures versioned schemas (e.g., MML v2 with lutaml_default_register = :mml_v2) # are instantiated with their native context child_register = Lutaml::Model::Register.resolve_for_child( model_class, lutaml_register ) instance_is_serialize = model_class.include?(::Lutaml::Model::Serialize) if instance_is_serialize instance = model_class.allocate_for_deserialization(child_register) else instance = model_class.new register_accessor_methods_for(instance, child_register) end # Set @__xml_namespace_prefix on root model for doubly-defined namespace support. # This is read during serialization to determine if the root element should use # an explicit prefix from the input XML. # Check the XmlElement's namespace_prefix (not the model's): # - Nil/empty: root uses default format (doubly-defined case) # - Set: root has explicit prefix (mixed content case) root_element = if data.is_a?(::Lutaml::Xml::XmlElement) data else data.root end if root_element && instance_is_serialize root_ns_prefix = if root_element.namespace_prefix_explicit && root_element.namespace_prefix root_element.namespace_prefix else root_element.xml_namespace_prefix end if root_ns_prefix && !root_ns_prefix.empty? instance.xml_namespace_prefix = root_ns_prefix end # Track original namespace URI for namespace alias support. # # When root element's namespace URI differs from the model's canonical URI, # it's an alias that should be preserved during serialization. root_ns_uri = root_element.namespace_uri if root_ns_uri model_ns_class = instance.class.mappings_for(:xml)&.namespace_class if model_ns_class && model_ns_class.uri != root_ns_uri # root_ns_uri differs from canonical - preserve it (alias or other) instance.original_namespace_uri = root_ns_uri end end # Namespace declaration plan for round-trip fidelity. # Only needed for root elements (no lutaml_parent in options). # Three modes: :lazy (default), :eager, :skip. if !.key?(:lutaml_parent) plan_mode = .fetch(:import_declaration_plan, :lazy) case plan_mode when :skip # Skip plan building entirely (fastest) when :eager input_declaration_plan = build_input_declaration_plan(root_element) instance.import_declaration_plan = input_declaration_plan if input_declaration_plan when :lazy # Store element reference for lazy plan building on first to_xml. # No recursive walk during deserialization — plan is built on demand. # Element reference is released after first serialization. instance.pending_plan_root_element = root_element else raise ArgumentError, "import_declaration_plan must be :eager, :lazy, or :skip, got #{plan_mode.inspect}" end end end root_and_parent_assignment(instance, ) apply_xml_mapping(data, instance, , child_register, instance_is_serialize) end |
#model_to_data(model, _format, options = {}) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/lutaml/xml/model_transform.rb', line 191 def model_to_data(model, _format, = {}) # Check if model class has a pre-compiled transformation model_class = model.class if model_class.is_a?(Class) && model_class.include?(::Lutaml::Model::Serialize) transformation = model_class.transformation_for(:xml, lutaml_register) # If transformation exists and is an Xml::Transformation, use it if transformation.is_a?(::Lutaml::Xml::Transformation) return transformation.transform(model, ) end end # Fallback to returning model for classes without transformation model end |