lib/xml/mapping_extensions/namespaced.rb
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