lib/xmlable/mixins/elements_storage.rb
module XMLable
module Mixins
#
# ElementsStorage module contains the logic to store XML elements
#
module ElementsStorage
def self.included(base)
base.send(:extend, ClassMethods)
end
#
# Set element from XML Node
#
# @param [Symbol] name tag name
# @param [Nokogiri::XML::Element, nil] el
#
# @api private
#
def __set_element(el, opts = {})
unless el.is_a?(Nokogiri::XML::Element)
el = Nokogiri::XML::Element.new(opts[:tag], @__node)
@__node << el
if opts[:namespace]
el.namespace = el.namespace_scopes.find { |n| n.prefix == opts[:namespace] }
end
end
h = __elements_handlers.for_xml_object(el)
el.instance_variable_set(:@__handler, h)
__initialize_elements_container(h) << h.from_xml_element(el)
end
#
# Initialize elements container
#
# @param [XMLable::Handlers::Element, XMLable::Handlers::Elements, XMLable::Handlers::ElementNone] h
#
# @api private
#
# @return [XMLable::Mixins::Container]
#
def __initialize_elements_container(h)
__elements[h.key] ||= h.container_for_xml_element(__node)
end
#
# Elements which current object holds
#
# @api private
#
# @return [Hash(String => Array<XMLable::Mixins::Object>)]
#
def __elements
@__elements ||= {}
end
#
# Is this object empty?
#
# @api private
#
# @return [Boolean]
#
def __empty?
return false unless super
__elements.values.all?(&:__empty?)
end
def method_missing(name, *args, &blocks)
h = __has_element_handler?(name)
return super unless h
if name.to_s.end_with?('=')
__element_object_set(h, args.first)
else
__element_object_get(h)
end
end
#
# Elements handlers storage
#
# @api private
#
# @return [XMLable::Handlers::Storage]
#
def __elements_handlers
@__elements_handler ||= self.class.__elements_handlers.clone
end
#
# Does this object contain element with given key?
#
# @return [XMLable::Mixins::Object, false]
#
def key?(key)
super || __has_element_handler?(key)
end
#
# Get element object by its key
#
# @param [String] key element key
#
# @return [XMLable::Mixins::Object, nil]
#
def [](key)
h = __has_element_handler?(key)
h ? __element_object_get(h) : super
end
#
# Set element value
#
# @param [String] key element key
# @param [Object] val new value
#
# @return [XMLable::Mixins::Object, nil]
#
def []=(key, val)
h = __has_element_handler?(key)
h ? __element_object_set(h, val) : super
end
#
# Find element handler by its key
#
# @param [Symbol, String] key
#
# @api private
#
# @return [XMLable::Handlers::Element, XMLable::Handlers::ElementNone, nil]
#
def __has_element_handler?(key)
key = key.to_s.gsub(/[=!]$/, '')
__elements_handlers.storage.find { |h| h.method_name == key }
end
#
# Get element object
#
# @param [XMLable::Handlers::Element, XMLable::Handlers::Elements, XMLable::Handlers::ElementNone] h
#
# @api private
#
# @return [XMLable::Mixins::Object]
#
def __element_object_get(h)
unless __elements.key?(h.key)
if h.single?
__set_element(nil, tag: h.tag, namespace: h.namespace_prefix)
else
__initialize_elements_container(h)
end
end
ret = __elements[h.key]
h.single? ? ret.first : ret
end
#
# Set element object value
#
# @param [XMLable::Handlers::Element, XMLable::Handlers::Elements, XMLable::Handlers::ElementNone] h
# @param [Object] val
#
# @api private
#
# @return [XMLable::Mixins::Object]
#
def __element_object_set(h, val)
__element_object_get(h).__overwrite_content(val)
end
#
# Set element object value
#
# @param [XMLable::Handlers::Element, XMLable::Handlers::Elements, XMLable::Handlers::ElementNone] h
# @param [Object] val
#
# @api private
#
def __element_object_initialize(h, val)
obj = __element_object_get(h)
val = { '__content' => val } unless val.respond_to?(:each)
if h.single?
obj.__initialize_with(val)
else
val.map { |v| obj.new(v) }
end
end
module ClassMethods
#
# Elements handlers storage
#
# @api private
#
# @return [XMLable::Handlers::Storage]
#
def __elements_handlers
@__elements_handlers ||=
__nested(:@__elements_handlers) || Handlers::Storage.new(default: Handlers::ElementNone)
end
%i[element elements].each do |m|
define_method m do |*args, &block|
opts = args.last.is_a?(Hash) ? args.pop : {}
if __default_namespace && !opts.key?(:namespace)
opts[:namespace] = __default_namespace
end
h = XMLable::Handlers.const_get(__method__.to_s.capitalize).build(*args, opts, &block)
__elements_handlers << h
end
end
end
end
end
end