class Lutaml::Model::XmlTransform
def apply_xml_mapping(doc, instance, options = {})
def apply_xml_mapping(doc, instance, options = {}) options = prepare_options(options) instance.encoding = options[:encoding] return instance unless doc mappings = options[:mappings] || mappings_for(:xml).mappings validate_document!(doc, options) set_instance_ordering(instance, doc, options) set_schema_location(instance, doc) defaults_used = [] validate_sequence!(doc.root.order) mappings.each do |rule| raise "Attribute '#{rule.to}' not found in #{context}" unless valid_rule?(rule) attr = attribute_for_rule(rule) next if attr&.derived? new_opts = options.dup if rule.namespace_set? new_opts[:default_namespace] = rule.namespace end value = if rule.raw_mapping? doc.root.inner_xml elsif rule.content_mapping? rule.cdata ? doc.cdata : doc.text else val = value_for_rule(doc, rule, new_opts, instance) if (Utils.uninitialized?(val) || val.nil?) && (instance.using_default?(rule.to) || rule.render_default) defaults_used << rule.to attr&.default || rule.to_value_for(instance) else val end end value = apply_value_map(value, rule.value_map(:from, new_opts), attr) value = normalize_xml_value(value, rule, attr, new_opts) rule.deserialize(instance, value, attributes, context) end defaults_used.each do |attr_name| instance.using_default_for(attr_name) end instance end
def cast_value?(attr, rule)
def cast_value?(attr, rule) attr && !rule.raw_mapping? && !rule.content_mapping? && !rule.custom_methods[:from] end
def data_to_model(data, _format, options = {})
def data_to_model(data, _format, options = {}) instance = model_class.new apply_xml_mapping(data, instance, options) end
def ensure_utf8(value)
def ensure_utf8(value) case value when String value.encode("UTF-8", invalid: :replace, undef: :replace, replace: "") when Array value.map { |v| ensure_utf8(v) } when Hash value.transform_keys do |k| ensure_utf8(k) end.transform_values do |v| ensure_utf8(v) end else value end end
def handle_cdata(children)
def handle_cdata(children) values = children.map do |child| child.cdata_children&.map(&:text) end.flatten children.count > 1 ? values : values.first end
def inner_xml_of(node)
def inner_xml_of(node) case node when Xml::XmlElement node.inner_xml else node.children.map(&:to_xml).join end end
def model_to_data(model, _format, _options = {})
def model_to_data(model, _format, _options = {}) model end
def normalize_xml_value(value, rule, attr, options = {})
def normalize_xml_value(value, rule, attr, options = {}) value = [value].compact if attr&.collection? && !value.is_a?(Array) && !value.nil? return value unless cast_value?(attr, rule) options.merge(caller_class: self, mixed_content: rule.mixed_content) attr.cast( value, :xml, options, ) end
def normalized_value_for_attr(values, attr)
def normalized_value_for_attr(values, attr) # for xml collection: true cases like # <store><items /></store> # <store><items xsi:nil="true"/></store> # <store><items></items></store> # # these are considered empty collection return [] if attr&.collection? && [[nil], [""]].include?(values) return values if attr&.collection? values.is_a?(Array) ? values.first : values end
def prepare_options(options)
def prepare_options(options) opts = Utils.deep_dup(options) opts[:default_namespace] ||= mappings_for(:xml)&.namespace_uri opts end
def set_instance_ordering(instance, doc, options)
def set_instance_ordering(instance, doc, options) return unless instance.respond_to?(:ordered=) instance.element_order = doc.root.order instance.ordered = mappings_for(:xml).ordered? || options[:ordered] instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content] end
def set_schema_location(instance, doc)
def set_schema_location(instance, doc) schema_location = doc.attributes.values.find do |a| a.unprefixed_name == "schemaLocation" end return if schema_location.nil? instance.schema_location = Lutaml::Model::SchemaLocation.new( schema_location: schema_location.value, prefix: schema_location.namespace_prefix, namespace: schema_location.namespace, ) end
def text_hash?(attr, value)
def text_hash?(attr, value) return false unless value.is_a?(Hash) return value.one? && value.text? unless attr !(attr.type <= Serialize) && attr.type != Lutaml::Model::Type::Hash end
def validate_document!(doc, options)
def validate_document!(doc, options) return unless doc.is_a?(Array) raise Lutaml::Model::CollectionTrueMissingError( context, options[:caller_class], ) end
def validate_sequence!(element_order)
def validate_sequence!(element_order) mapping_sequence = mappings_for(:xml).element_sequence current_order = element_order.filter_map(&:element_tag) mapping_sequence.each do |mapping| mapping.validate_content!(current_order) end end
def value_for_rule(doc, rule, options, instance)
def value_for_rule(doc, rule, options, instance) rule_names = rule.namespaced_names(options[:default_namespace]) if rule.attribute? doc.root.find_attribute_value(rule_names) else attr = attribute_for_rule(rule) children = doc.children.select do |child| rule_names.include?(child.namespaced_name) && !child.text? end if rule.has_custom_method_for_deserialization? || attr.type == Lutaml::Model::Type::Hash return_child = attr.type == Lutaml::Model::Type::Hash || !attr.collection? if attr return return_child ? children.first : children end return handle_cdata(children) if rule.cdata values = [] if Utils.present?(children) instance.value_set_for(attr.name) else children = nil values = Lutaml::Model::UninitializedClass.instance end children&.each do |child| if !rule.has_custom_method_for_deserialization? && attr.type <= Serialize cast_options = options.except(:mappings) cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic values << attr.cast(child, :xml, cast_options) elsif attr.raw? values << inner_xml_of(child) else return nil if rule.render_nil_as_nil? && child.nil_element? text = child.nil_element? ? nil : (child&.text&.+ child&.cdata) values << text end end normalized_value_for_attr(values, attr) end end