lib/blather/xmpp_node.rb in blather-0.3.4 vs lib/blather/xmpp_node.rb in blather-0.4.0

- old
+ new

@@ -2,28 +2,28 @@ ## # Base XML Node # All XML classes subclass XMPPNode # it allows the addition of helpers - class XMPPNode < XML::Node + class XMPPNode < Nokogiri::XML::Node BASE_NAMES = %w[presence message iq].freeze @@registrations = {} - class_inheritable_accessor :ns, - :name + class_inheritable_accessor :registered_ns, + :registered_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 + self.registered_name = name.to_s + self.registered_ns = ns + @@registrations[[self.registered_name, self.registered_ns]] = self end ## # Find the class to use given the name and namespace of a stanza def self.class_from_registration(name, xmlns) @@ -34,11 +34,11 @@ ## # Looks up the class to use then instantiates an object # of that class and imports all the <tt>node</tt>'s attributes # and children into it. def self.import(node) - klass = class_from_registration(node.element_name, node.namespace) + klass = class_from_registration(node.element_name, (node.namespace.href if node.namespace)) if klass && klass != self klass.import(node) else new(node.element_name).inherit(node) end @@ -53,20 +53,22 @@ # attribute_reader :type # attribute_reader :name, :to_sym => false # end # # n = Node.new - # n.attributes[:type] = 'foo' + # n[:type] = 'foo' # n.type == :foo - # n.attributes[:name] = 'bar' + # n[:name] = 'bar' # n.name == 'bar' def self.attribute_reader(*syms) opts = syms.last.is_a?(Hash) ? syms.pop : {} + convert_str = "val.#{opts[:call]} if val" if opts[:call] 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} + val = self[:#{sym}] + #{convert_str} end END end end @@ -77,17 +79,17 @@ # attribute_writer :type # end # # n = Node.new # n.type = 'foo' - # n.attributes[:type] == 'foo' + # n[: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 + self[:#{sym}] = value end END end end @@ -109,97 +111,156 @@ attribute_reader *syms attribute_writer *syms end ## + # Provides a content reader helper that returns the content of a node + # +method+ is the method to create + # +conversion+ is a method to call on the content before sending it back + # +node+ is the name of the content node (this defaults to the method name) + # + # class Node + # content_attr_reader :body + # content_attr_reader :type, :to_sym + # content_attr_reader :id, :to_i, :identity + # end + # + # n = Node.new 'foo' + # n.to_s == "<foo><body>foobarbaz</body><type>error</type><identity>1000</identity></foo>" + # n.body == 'foobarbaz' + # n.type == :error + # n.id == 1000 + def self.content_attr_reader(method, conversion = nil, node = nil) + node ||= method + conversion = "val.#{conversion} if val.respond_to?(:#{conversion})" if conversion + class_eval(<<-END, __FILE__, __LINE__) + def #{method} + val = content_from :#{node} + #{conversion} + end + END + end + + ## + # Provides a content writer helper that creates or updates the content of a node + # +method+ is the method to create + # +node+ is the name of the node to create (defaults to the method name) + # + # class Node + # content_attr_writer :body + # content_attr_writer :id, :identity + # end + # + # n = Node.new 'foo' + # n.body = 'thebodytext' + # n.id = 'id-text' + # n.to_s == '<foo><body>thebodytext</body><identity>id-text</identity></foo>' + def self.content_attr_writer(method, node = nil) + node ||= method + class_eval(<<-END, __FILE__, __LINE__) + def #{method}=(val) + set_content_for :#{node}, val + end + END + end + + ## + # Provides a quick way of building +content_attr_reader+ and +content_attr_writer+ + # for the same method and node + def self.content_attr_accessor(method, conversion = nil, node = nil) + content_attr_reader method, conversion, node + content_attr_writer method, node + 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 + def self.new(name = nil, doc = nil) + name ||= self.registered_name - super name.to_s, content - self.namespace = self.class.ns unless BASE_NAMES.include?(name.to_s) + node = super name.to_s, (doc || Nokogiri::XML::Document.new) + node.document.root = node unless doc + node.namespace = self.registered_ns unless BASE_NAMES.include?(name.to_s) + node end ## # Quickway of turning itself into a proper object def to_stanza self.class.import self end + alias_method :nokogiri_namespace=, :namespace= def namespace=(namespaces) case namespaces - when XML::Namespace - self.namespaces.namespace = namespaces + when Nokogiri::XML::Namespace + self.nokogiri_namespace = namespaces when String - self.namespaces.namespace = XML::Namespace.new(self, nil, namespaces) + self.add_namespace nil, namespaces when Hash + if ns = namespaces.delete(nil) + self.add_namespace nil, ns + end namespaces.each do |p, n| - self.namespaces.namespace = XML::Namespace.new(self, p, n) + ns = self.add_namespace p, n + self.nokogiri_namespace = ns end end end - def namespace(prefix = nil) - (ns = namespaces.find_by_prefix(prefix)) ? ns.href : nil + def namespace_href + namespace.href if namespace 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) } + child = xpath(name, ns).first + child.remove if child end ## # Remove all children with a given name def remove_children(name) - name = name.to_s - self.find(name).each { |n| n.remove! } + xpath("./*[local-name()='#{name}']").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 + def content_from(name, ns = nil) + child = xpath(name, ns).first + child.content if child end ## - # Create a copy - def copy(deep = true) - copy = self.class.new.inherit(self) - copy.element_name = self.element_name - copy + # Sets the content for the specified node. + # If the node exists it is updated. If not a new node is created + # If the node exists and the content is nil, the node will be removed entirely + def set_content_for(node, content = nil) + if content + child = xpath(node).first + self << (child = XMPPNode.new(node, self.document)) unless child + child.content = content + else + remove_child node + end end + alias_method :copy, :dup + ## # Inherit all of <tt>stanza</tt>'s attributes and children def inherit(stanza) + set_namespace stanza.namespace if stanza.namespace inherit_attrs stanza.attributes - stanza.children.each { |c| self << c.copy(true) } + stanza.children.each { |c| self << c.dup } self end ## # Inherit only <tt>stanza</tt>'s attributes def inherit_attrs(attrs) - attrs.each { |a| attributes[a.name] = a.value } + attrs.each { |name, value| self[name] = 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 \ No newline at end of file +end