Module: Lutaml::Xml::TransformationSupport::OrderedApplier

Included in:
Lutaml::Xml::Transformation
Defined in:
lib/lutaml/xml/transformation/ordered_applier.rb

Overview

Module for applying rules in element order for round-trip preservation.

When element_order was captured during parsing (model was deserialized from XML) AND the mapping is marked as ordered, this module ensures the original XML structure is preserved during serialization.

Instance Method Summary collapse

Instance Method Details

#apply_element_rule_single(parent:, rule:, value:, options:) { ... } ⇒ Object

Apply element rule for a single value from a collection

Parameters:

Yields:

  • Block to create element for value



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/lutaml/xml/transformation/ordered_applier.rb', line 83

def apply_element_rule_single(parent:, rule:, value:, options:)
  # Extract parent's namespace info for element_form_default inheritance
  parent_ns_class = parent.namespace_class
  # Only pass element_form_default VALUE if it was explicitly set
  # When not set (defaults to :unqualified), pass nil to avoid incorrect blank namespace treatment
  parent_element_form_default = if parent_ns_class&.element_form_default_set?
                                  parent_ns_class.element_form_default
                                end

  # Merge parent context into options
  child_options = options.merge(
    parent_namespace_class: parent_ns_class,
    parent_element_form_default: parent_element_form_default,
    parent_element: parent,
  )

  element = yield(rule, value, child_options)
  parent.add_child(element) if element
end

#apply_rules_in_order(root, model_instance, options, compiled_rules, model_class, register_id) { ... } ⇒ Object

Apply rules in the order specified by element_order

This ensures round-trip serialization preserves the original XML structure. For mixed content, text nodes from element_order are added directly to preserve the original text interleaving.

Parameters:

  • root (::Lutaml::Xml::DataModel::XmlElement)

    Root element

  • model_instance (Object)

    The model instance

  • options (Hash)

    Transformation options

  • compiled_rules (Array<CompiledRule>)

    The compiled rules

  • model_class (Class)

    The model class

  • register_id (Symbol, nil)

    The register ID

Yields:

  • Block to apply individual rules



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
# File 'lib/lutaml/xml/transformation/ordered_applier.rb', line 25

def apply_rules_in_order(root, model_instance, options, compiled_rules,
model_class, register_id)
  element_order = model_instance.element_order
  mapping = model_class.mappings_for(:xml, register_id)

  # Track index per element type for collection attributes
  element_indices = ::Hash.new(0)

  # Track text node index for content-mapped attribute access.
  text_node_index = 0

  # Track whether we processed any text nodes from element_order
  processed_text_nodes = false

  # Find the content rule to get CDATA flag for mixed content text
  content_rule = compiled_rules.find do |r|
    r.option(:mapping_type) == :content
  end
  root.cdata = true if content_rule&.cdata

  # Pre-check: can we use the content attribute for indexed text access?
  # This requires the content array length to match the text node count
  # in element_order. When they match, mutations to the content array
  # are reflected in serialization. When they don't match (e.g., CDATA
  # creates extra whitespace text nodes), we fall back to element_order.
  text_node_count = element_order.count { |o| o.type == "Text" }
  content_value = content_rule &&             model_instance&.public_send(content_rule.attribute_name)
  use_content_index = content_rule && content_value.is_a?(Array) &&
    content_value.length == text_node_count

  # Iterate through element_order to preserve original sequence
  element_order.each do |object|
    result = process_element_order_item(
      object, root, model_instance, options,
      compiled_rules, mapping, element_indices,
      content_rule, text_node_index, text_node_count,
      use_content_index
    ) do |action, rule, value, xsi_nil_flag|
      yield(action, rule, value, xsi_nil_flag) if block_given?
    end
    text_node_index += 1 if object.type == "Text"
    processed_text_nodes = true if result == :text_node
  end

  # Apply remaining rules that weren't in element_order (attributes only)
  apply_remaining_rules(root, model_instance, options, compiled_rules,
                        mapping, processed_text_nodes) do |action, rule, value|
    yield(action, rule, value) if block_given?
  end
end

#find_rule_for_element(object, compiled_rules) ⇒ CompiledRule?

Find the mapping rule for an element from element_order

Parameters:

  • object (Xml::Element)

    Element from element_order

  • compiled_rules (Array<CompiledRule>)

    The compiled rules

Returns:

  • (CompiledRule, nil)

    The matching rule or nil



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/lutaml/xml/transformation/ordered_applier.rb', line 108

def find_rule_for_element(object, compiled_rules)
  return nil unless object.type == "Element"

  object_ns_uri = object.namespace_uri # nil if old element_order (backward compat)

  compiled_rules.find do |r|
    r.is_a?(::Lutaml::Model::CompiledRule) &&
      r.option(:mapping_type) == :element &&
      matches_element_rule?(r, object.name, object_ns_uri)
  end
end