dmolesUC3/xml-mapping_extensions

View on GitHub
lib/xml/mapping_extensions/namespaced.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'uri'

module XML
  module MappingExtensions
    # Extends `XML::Mapping` to add a `namespace` attribute and write the namespace
    # out when saving to XML.
    module Namespaced

      def self.included(base)
        base.extend(ClassMethods)
        base.include(XML::Mapping)
        base.include(InstanceMethods)
      end

      module ClassMethods
        def namespace(ns = nil)
          @namespace = ns if ns
          @namespace
        end
      end

      def namespace
        self.class.namespace
      end

      # Hack to make sure these don't get defined till after `XML::Mapping`'s include
      # hooks have a chance to define their super methods
      module InstanceMethods

        # Overrides `XML::Mapping#fill_into_xml` to set the XML namespace
        def fill_into_xml(xml, options = { mapping: :_default })
          add_namespace(xml)
          super(xml, options)
          return unless namespace
          set_prefix_recursive(namespace.prefix, xml)
        end

        private

        def set_prefix_recursive(prefix, elem)
          return elem unless prefix
          return elem unless elem.namespace == namespace.uri || elem.namespace.to_s.empty?

          set_prefix(prefix, elem)
          elem.each_element { |e| set_prefix_recursive(prefix, e) }
          elem
        end

        def add_namespace(elem)
          return unless namespace
          prefix, uri, schema_location = namespace.prefix, namespace.uri, namespace.schema_location # rubocop:disable Style/ParallelAssignment
          if prefix
            set_prefix(prefix, elem)
            add_schema_location(uri, schema_location, elem.root)
            elem.root.add_namespace(prefix, uri)
          else
            add_schema_location(uri, schema_location, elem)
            elem.add_namespace(uri)
          end
        end

        def set_prefix(prefix, elem)
          # name= with a prefixed name sets namespace by side effect and is the only way to actually output the prefix
          elem.name = "#{prefix}:#{elem.name}" if elem.prefix.to_s.empty?
        end

        def add_schema_location(uri, schema_location, elem)
          return elem unless schema_location

          schema_location_attr = elem.attribute('xsi:schemaLocation')
          all_declarations     = schema_location_attr ? schema_location_attr.value : ''

          declaration = "#{uri} #{schema_location}"
          return elem if all_declarations.include?(declaration)

          all_declarations << " #{declaration}"
          elem.add_attribute('xsi:schemaLocation', all_declarations.strip)

          elem.add_namespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance') unless elem.root && elem.root.attribute('xmlns:xsi')
        end

      end
    end
  end
end