module Blather
##
# Base XML Node
# All XML classes subclass XMPPNode
# it allows the addition of helpers
class XMPPNode < XML::Node
BASE_NAMES = %w[presence message iq].freeze
@@registrations = {}
class_inheritable_accessor :ns,
:name
##
# Lets a subclass register itself
#
# This registers a namespace that is used when looking
# up the class name of the object to instantiate when a new
# stanza is received
def self.register(name, ns = nil)
self.name = name.to_s
self.ns = ns
@@registrations[[self.name, self.ns]] = self
end
##
# Find the class to use given the name and namespace of a stanza
def self.class_from_registration(name, xmlns)
name = name.to_s
@@registrations[[name, xmlns]] || @@registrations[[name, nil]]
end
##
# Looks up the class to use then instantiates an object
# of that class and imports all the node's attributes
# and children into it.
def self.import(node)
klass = class_from_registration(node.element_name, node.namespace)
if klass && klass != self
klass.import(node)
else
new(node.element_name).inherit(node)
end
end
##
# Provides an attribute reader helper. Default behavior is to
# conver the values of the attribute into a symbol. This can
# be turned off by passing :to_sym => false
#
# class Node
# attribute_reader :type
# attribute_reader :name, :to_sym => false
# end
#
# n = Node.new
# n.attributes[:type] = 'foo'
# n.type == :foo
# n.attributes[:name] = 'bar'
# n.name == 'bar'
def self.attribute_reader(*syms)
opts = syms.last.is_a?(Hash) ? syms.pop : {}
syms.flatten.each do |sym|
class_eval(<<-END, __FILE__, __LINE__)
def #{sym}
attributes[:#{sym}]#{".to_sym unless attributes[:#{sym}].blank?" unless opts[:to_sym] == false}
end
END
end
end
##
# Provides an attribute writer helper.
#
# class Node
# attribute_writer :type
# end
#
# n = Node.new
# n.type = 'foo'
# n.attributes[:type] == 'foo'
def self.attribute_writer(*syms)
syms.flatten.each do |sym|
next if sym.is_a?(Hash)
class_eval(<<-END, __FILE__, __LINE__)
def #{sym}=(value)
attributes[:#{sym}] = value
end
END
end
end
##
# Provides an attribute accessor helper combining
# attribute_reader and attribute_writer
#
# class Node
# attribute_accessor :type
# attribute_accessor :name, :to_sym => false
# end
#
# n = Node.new
# n.type = 'foo'
# n.type == :foo
# n.name = 'bar'
# n.name == 'bar'
def self.attribute_accessor(*syms)
attribute_reader *syms
attribute_writer *syms
end
##
# Automatically sets the namespace registered by the subclass
def initialize(name = nil, content = nil)
name ||= self.class.name
content = content.to_s if content
super name.to_s, content
self.namespace = self.class.ns unless BASE_NAMES.include?(name.to_s)
end
##
# Quickway of turning itself into a proper object
def to_stanza
self.class.import self
end
def namespace=(ns)
if ns
ns = {nil => ns} unless ns.is_a?(Hash)
ns.each { |p,n| XML::Namespace.new self, p, n }
end
end
def namespace(prefix = nil)
(ns = namespaces.find_by_prefix(prefix)) ? ns.href : nil
end
##
# Remove a child with the name and (optionally) namespace given
def remove_child(name, ns = nil)
name = name.to_s
self.detect { |n| n.remove! if n.element_name == name && (!ns || n.namespace == ns) }
end
##
# Remove all children with a given name
def remove_children(name)
name = name.to_s
self.find(name).each { |n| n.remove! }
end
##
# Pull the content from a child
def content_from(name)
name = name.to_s
(child = self.detect { |n| n.element_name == name }) ? child.content : nil
end
##
# Create a copy
def copy(deep = true)
copy = self.class.new.inherit(self)
copy.element_name = self.element_name
copy
end
##
# Inherit all of stanza's attributes and children
def inherit(stanza)
inherit_attrs stanza.attributes
stanza.children.each { |c| self << c.copy(true) }
self
end
##
# Inherit only stanza's attributes
def inherit_attrs(attrs)
attrs.each { |a| attributes[a.name] = a.value }
self
end
##
# Turn itself into an XML string and remove all whitespace between nodes
def to_xml
# TODO: Fix this for HTML nodes (and any other that might require whitespace)
to_s.gsub(">\n<", '><')
end
##
# Override #find to work when a node isn't attached to a document
def find(what, nslist = nil)
what = what.to_s
(self.doc ? super(what, nslist) : select { |i| i.element_name == what })
end
end #XMPPNode
end