lib/lutaml/model/serialize.rb in lutaml-model-0.2.1 vs lib/lutaml/model/serialize.rb in lutaml-model-0.3.0

- old
+ new

@@ -1,16 +1,13 @@ # lib/lutaml/model/serialize.rb -require_relative "json_adapter/standard" -require_relative "json_adapter/multi_json" require_relative "yaml_adapter" require_relative "xml_adapter" -require_relative "toml_adapter/toml_rb_adapter" -require_relative "toml_adapter/tomlib_adapter" require_relative "config" require_relative "type" require_relative "attribute" require_relative "mapping_rule" +require_relative "mapping_hash" require_relative "xml_mapping" require_relative "key_value_mapping" require_relative "json_adapter" module Lutaml @@ -20,18 +17,19 @@ def self.included(base) base.extend(ClassMethods) end - # rubocop:disable Metrics/MethodLength - # rubocop:disable Metrics/BlockLength - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/CyclomaticComplexity - # rubocop:disable Metrics/PerceivedComplexity module ClassMethods attr_accessor :attributes, :mappings + def inherited(subclass) + super + + subclass.instance_variable_set(:@attributes, @attributes.dup) + end + def attribute(name, type, options = {}) self.attributes ||= {} attr = Attribute.new(name, type, options) attributes[name] = attr @@ -48,17 +46,21 @@ define_method(format) do |&block| self.mappings ||= {} klass = format == :xml ? XmlMapping : KeyValueMapping self.mappings[format] = klass.new self.mappings[format].instance_eval(&block) + + if format == :xml && !self.mappings[format].root_element + self.mappings[format].root(to_s) + end end define_method(:"from_#{format}") do |data| adapter = Lutaml::Model::Config.send(:"#{format}_adapter") doc = adapter.parse(data) mapped_attrs = apply_mappings(doc.to_h, format) - apply_content_mapping(doc, mapped_attrs) if format == :xml + # apply_content_mapping(doc, mapped_attrs) if format == :xml new(mapped_attrs) end end def mappings_for(format) @@ -77,11 +79,11 @@ def apply_mappings(doc, format) return apply_xml_mapping(doc) if format == :xml mappings = mappings_for(format).mappings - mappings.each_with_object({}) do |rule, hash| + mappings.each_with_object(Lutaml::Model::MappingHash.new) do |rule, hash| attr = if rule.delegate attributes[rule.delegate].type.attributes[rule.to] else attributes[rule.to] end @@ -93,19 +95,10 @@ elsif doc.key?(rule.name) || doc.key?(rule.name.to_sym) doc[rule.name] || doc[rule.name.to_sym] else attr.default end - # if attr.collection? - # value = (value || []).map do |v| - # attr.type <= Serialize ? attr.type.new(v) : v - # end - # elsif value.is_a?(Hash) && attr.type <= Serialize - # value = attr.type.new(value) - # else - # value = attr.type.cast(value) - # end if attr.collection? value = (value || []).map do |v| attr.type <= Serialize ? attr.type.apply_mappings(v, format) : v end @@ -120,98 +113,121 @@ hash[rule.to] = value end end end - def apply_xml_mapping(doc) + def apply_xml_mapping(doc, caller_class: nil, mixed_content: false) + return unless doc + mappings = mappings_for(:xml).mappings - mappings.each_with_object({}) do |rule, hash| + if doc.is_a?(Array) + raise "May be `collection: true` is" \ + "missing for #{self} in #{caller_class}" + end + + mapping_hash = Lutaml::Model::MappingHash.new + mapping_hash.item_order = doc.item_order + mapping_hash.ordered = mappings_for(:xml).mixed_content? || mixed_content + + mappings.each_with_object(mapping_hash) do |rule, hash| attr = attributes[rule.to] raise "Attribute '#{rule.to}' not found in #{self}" unless attr - value = if rule.name - doc[rule.name.to_s] || doc[rule.name.to_sym] - else + is_content_mapping = rule.name.nil? + value = if is_content_mapping doc["text"] + else + doc[rule.name.to_s] || doc[rule.name.to_sym] end - # if attr.collection? - # value = (value || []).map do |v| - # attr.type <= Serialize ? attr.type.from_hash(v) : v - # end - # elsif value.is_a?(Hash) && attr.type <= Serialize - # value = attr.type.cast(value) - # elsif value.is_a?(Array) - # value = attr.type.cast(value.first["text"]&.first) - # end - if attr.collection? + if value && !value.is_a?(Array) + value = [value] + end + value = (value || []).map do |v| if attr.type <= Serialize - attr.type.apply_xml_mapping(v) - else + attr.type.apply_xml_mapping(v, caller_class: self, mixed_content: rule.mixed_content) + elsif v.is_a?(Hash) v["text"] + else + v end end elsif attr.type <= Serialize - value = attr.type.apply_xml_mapping(value) if value + value = attr.type.apply_xml_mapping(value, caller_class: self, mixed_content: rule.mixed_content) else if value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash value = value["text"] end - value = attr.type.cast(value) + value = attr.type.cast(value) unless is_content_mapping end + hash[rule.to] = value end end - - def apply_content_mapping(doc, mapped_attrs) - content_mapping = mappings_for(:xml).content_mapping - return unless content_mapping - - content = doc.root.children.select(&:text?).map(&:text) - mapped_attrs[content_mapping.to] = content - end end - # rubocop:disable Layout/LineLength + attr_reader :element_order + def initialize(attrs = {}) return unless self.class.attributes + if attrs.is_a?(Lutaml::Model::MappingHash) + @ordered = attrs.ordered? + @element_order = attrs.item_order + end + self.class.attributes.each do |name, attr| - value = if attrs.key?(name) - attrs[name] - elsif attrs.key?(name.to_sym) - attrs[name.to_sym] - elsif attrs.key?(name.to_s) - attrs[name.to_s] - else - attr.default - end + value = attr_value(attrs, name, attr) - value = if attr.collection? - (value || []).map do |v| - if v.is_a?(Hash) - attr.type.new(v) - else - Lutaml::Model::Type.cast( - v, attr.type - ) - end - end - elsif value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash - attr.type.new(value) - else - Lutaml::Model::Type.cast(value, attr.type) - end send(:"#{name}=", ensure_utf8(value)) end end - # rubocop:enable Layout/LineLength + def attr_value(attrs, name, attr_rule) + value = if attrs.key?(name) + attrs[name] + elsif attrs.key?(name.to_sym) + attrs[name.to_sym] + elsif attrs.key?(name.to_s) + attrs[name.to_s] + else + attr_rule.default + end + + if attr_rule.collection? || value.is_a?(Array) + (value || []).map do |v| + if v.is_a?(Hash) + attr_rule.type.new(v) + else + Lutaml::Model::Type.cast( + v, attr_rule.type + ) + end + end + elsif value.is_a?(Hash) && attr_rule.type != Lutaml::Model::Type::Hash + attr_rule.type.new(value) + else + Lutaml::Model::Type.cast(value, attr_rule.type) + end + end + + def ordered? + @ordered + end + + def key_exist?(hash, key) + hash.key?(key) || hash.key?(key.to_sym) || hash.key?(key.to_s) + end + + def key_value(hash, key) + hash[key] || hash[key.to_sym] || hash[key.to_s] + end + # TODO: Make this work # FORMATS.each do |format| # define_method("to_#{format}") do |options = {}| # adapter = Lutaml::Model::Config.send("#{format}_adapter") # representation = if format == :yaml @@ -323,13 +339,8 @@ end.transform_values { |v| ensure_utf8(v) } else value end end - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/BlockLength - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/CyclomaticComplexity - # rubocop:enable Metrics/PerceivedComplexity end end end