Module: Lutaml::Xml::TransformationSupport::RuleApplier

Includes:
ElementBuilder, SkipLogic
Included in:
Lutaml::Xml::Transformation
Defined in:
lib/lutaml/xml/transformation/rule_applier.rb

Overview

Module for applying transformation rules to create XML elements.

Dispatches rule application to specific handlers based on mapping type:

  • Element rules -> apply_element_rule

  • Attribute rules -> apply_attribute_rule

  • Content rules -> apply_content_rule

  • Raw rules -> apply_raw_rule

Instance Method Summary collapse

Methods included from ElementBuilder

#create_element_for_value, #determine_element_namespace

Methods included from ValueSerializer

#serialize_value

Methods included from SkipLogic

#should_skip_delegated_value?, #should_skip_value?

Methods included from Model::RenderPolicy

derived_attribute_for?, #should_skip_delegated_value?, #should_skip_value?

Instance Method Details

#apply_attribute_rule(parent, rule, value, options, model_class, register_id) ⇒ Object

Apply an attribute rule

Parameters:

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

    Parent element

  • rule (CompiledRule)

    The rule

  • value (Object)

    The value

  • options (Hash)

    Options

  • model_class (Class)

    The model class

  • register_id (Symbol, nil)

    The register ID



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/lutaml/xml/transformation/rule_applier.rb', line 121

def apply_attribute_rule(parent, rule, value, options, model_class,
register_id)
  # Handle custom serialization methods
  if rule.has_custom_methods? && rule.custom_methods[:to]
    apply_custom_method(parent, rule, model_class,
                        options[:current_model])
    return
  end

  text = serialize_value(value, rule, model_class, register_id)
  return if text.nil?

  # Determine attribute namespace - type namespace takes precedence
  attr_type = rule.attribute_type
  attr_namespace_class = if attr_type.is_a?(Class) && attr_type <= Lutaml::Model::Type::Value && attr_type.namespace_class
                           attr_type.namespace_class
                         else
                           rule.namespace_class
                         end

  attr = ::Lutaml::Xml::DataModel::XmlAttribute.new(
    rule.serialized_name,
    text,
    attr_namespace_class,
  )

  parent.add_attribute(attr)
end

#apply_content_rule(parent, rule, value, options, model_class, register_id) ⇒ Object

Apply a content mapping rule (map_content directive)

For mixed content, when value is an Array, each item is added as a separate child text node. For regular content, value is set as text_content.

Parameters:

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

    Parent element

  • rule (CompiledRule)

    The rule

  • value (Object)

    The value

  • options (Hash)

    Options

  • model_class (Class)

    The model class

  • register_id (Symbol, nil)

    The register ID



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

def apply_content_rule(parent, rule, value, options, model_class,
register_id)
  # Handle custom serialization methods
  if rule.has_custom_methods? && rule.custom_methods[:to]
    apply_custom_method(parent, rule, model_class,
                        options[:current_model])
    return
  end

  return if value.nil?
  return if Lutaml::Model::Utils.uninitialized?(value)

  xml_mapping = model_class.mappings_for(:xml)
  is_mixed = rule.mixed_content || xml_mapping&.mixed_content? || value.is_a?(Array)

  if value.is_a?(Array) && is_mixed
    apply_mixed_content(parent, value)
  else
    # For regular content, serialize value and set as text_content
    text = serialize_value(value, rule, model_class, register_id)
    parent.text_content = text if text
  end

  parent.cdata = rule.cdata if rule.cdata
end

#apply_element_rule(parent, rule, value, options, model_class, register_id, register) ⇒ Object

Apply an element rule

Parameters:

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

    Parent element

  • rule (CompiledRule)

    The rule

  • value (Object)

    The value

  • options (Hash)

    Options

  • model_class (Class)

    The model class

  • register_id (Symbol, nil)

    The register ID

  • register (Register, nil)

    The register



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

def apply_element_rule(parent, rule, value, options, model_class,
register_id, register)
  # Handle custom serialization methods
  if rule.has_custom_methods? && rule.custom_methods[:to]
    apply_custom_method(parent, rule, model_class,
                        options[:current_model])
    return
  end

  # 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

  # Performance: Only create new options hash if values differ
  # This avoids allocations for unchanged namespace inheritance
  if options[:parent_namespace_class] == parent_ns_class &&
      options[:parent_element_form_default] == parent_element_form_default &&
      options[:parent_element] == parent
    child_options = options
  else
    child_options = options.dup
    child_options[:parent_namespace_class] = parent_ns_class
    child_options[:parent_element_form_default] =
      parent_element_form_default
    child_options[:parent_element] = parent
  end

  apply_element_value(parent, rule, value, child_options, model_class,
                      register_id, register)
end

#apply_raw_rule(parent, _rule, value) ⇒ Object

Apply a raw mapping rule (map_all directive)

Raw content is the entire inner XML as a string, including elements and text. Store it on the parent element so adapters can serialize it as raw XML fragment.

Parameters:

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

    Parent element

  • rule (CompiledRule)

    The rule (unused but kept for API consistency)

  • value (Object)

    The raw XML content as string



195
196
197
198
199
200
# File 'lib/lutaml/xml/transformation/rule_applier.rb', line 195

def apply_raw_rule(parent, _rule, value)
  return unless value
  return if value.to_s.empty?

  parent.raw_content = value.to_s
end

#apply_rule(parent, rule, model_instance, options, model_class, register_id, register) ⇒ Object

Apply a single transformation rule

Parameters:

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

    Parent element

  • rule (CompiledRule)

    The rule to apply

  • model_instance (Object)

    The model instance

  • options (Hash)

    Transformation options

  • model_class (Class)

    The model class

  • register_id (Symbol, nil)

    The register ID

  • register (Register, nil)

    The register



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

def apply_rule(parent, rule, model_instance, options, model_class,
register_id, register)
  # Skip pseudo-rules like root_namespace
  return if rule.option(:mapping_type) == :root_namespace

  # Check if this is a custom-method-only rule
  is_custom_method_only = custom_method_only?(rule)

  # Get attribute value - handle delegation and custom methods
  value = extract_rule_value(rule, model_instance,
                             is_custom_method_only)

  # Handle render options and value_map
  should_skip = if is_custom_method_only
                  false
                elsif rule.option(:delegate_from)
                  delegate_obj = model_instance.public_send(rule.option(:delegate_from))
                  should_skip_delegated_value?(value, rule,
                                               delegate_obj)
                else
                  should_skip_value?(value, rule, model_instance)
                end
  return if should_skip

  # Apply export transformation if present BEFORE any other processing
  value = rule.transform_value(value, :export) if rule.value_transformer

  # Handle based on rule type
  case rule.option(:mapping_type)
  when :element
    apply_element_rule(parent, rule, value, options, model_class,
                       register_id, register)
  when :attribute
    apply_attribute_rule(parent, rule, value, options, model_class,
                         register_id)
  when :content
    apply_content_rule(parent, rule, value, options, model_class,
                       register_id)
  when :raw
    apply_raw_rule(parent, rule, value)
  end
end