lib/blather/xmpp_node.rb in blather-0.4.7 vs lib/blather/xmpp_node.rb in blather-0.4.8

- old
+ new

@@ -1,196 +1,124 @@ module Blather - ## # Base XML Node - # All XML classes subclass XMPPNode - # it allows the addition of helpers + # All XML classes subclass XMPPNode it allows the addition of helpers class XMPPNode < Nokogiri::XML::Node + # @private BASE_NAMES = %w[presence message iq].freeze + # @private @@registrations = {} class_inheritable_accessor :registered_ns, :registered_name - ## - # Lets a subclass register itself + # Register a new stanza class to a name and/or namespace # # This registers a namespace that is used when looking # up the class name of the object to instantiate when a new # stanza is received + # + # @param [#to_s] name the name of the node + # @param [String, nil] ns the namespace the node belongs to def self.register(name, ns = nil) 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) + # + # @param [#to_s] name the name to lookup + # @param [String, nil] xmlns the namespace the node belongs to + # @return [Class, nil] the class appropriate for the name/ns combination + def self.class_from_registration(name, ns = nil) name = name.to_s - @@registrations[[name, xmlns]] || @@registrations[[name, nil]] + @@registrations[[name, ns]] || @@registrations[[name, nil]] end - ## - # 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. + # Import an XML::Node to the appropriate class + # + # Looks up the class the node should be then creates it based on the + # elements of the XML::Node + # @param [XML::Node] node the node to import + # @return the appropriate object based on the node name and namespace def self.import(node) - klass = class_from_registration(node.element_name, (node.namespace.href if node.namespace)) + ns = (node.namespace.href if node.namespace) + klass = class_from_registration(node.element_name, ns) 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 <tt>:to_sym => false</tt> + # Create a new Node object # - # class Node - # attribute_reader :type - # attribute_reader :name, :to_sym => false - # end - # - # n = Node.new - # n[:type] = 'foo' - # n.type == :foo - # 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} - val = self[:#{sym}] - #{convert_str} - end - END - end - end + # @param [String, nil] name the element name + # @param [XML::Document, nil] doc the document to attach the node to. If + # not provided one will be created + # @return a new object with the registered name and namespace + def self.new(name = nil, doc = nil) + name ||= self.registered_name - ## - # Provides an attribute writer helper. - # - # class Node - # attribute_writer :type - # end - # - # n = Node.new - # n.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) - self[:#{sym}] = value - end - END - end + 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 - ## - # Provides an attribute accessor helper combining - # <tt>attribute_reader</tt> and <tt>attribute_writer</tt> + # Helper method to read an attribute # - # 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 + # @param [#to_sym] attr_name the name of the attribute + # @param [String, Symbol, nil] to_call the name of the method to call on + # the returned value + # @return nil or the value + def read_attr(attr_name, to_call = nil) + val = self[attr_name.to_sym] + val && to_call ? val.__send__(to_call) : val 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) + # Helper method to write a value to an attribute # - # 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 + # @param [#to_sym] attr_name the name of the attribute + # @param [#to_s] value the value to set the attribute to + def write_attr(attr_name, value) + self[attr_name.to_sym] = value 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) + # Helper method to read the content of a node # - # 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 + # @param [#to_sym] node the name of the node + # @param [String, Symbol, nil] to_call the name of the method to call on + # the returned value + # @return nil or the value + def read_content(node, to_call = nil) + val = content_from node.to_sym + val && to_call ? val.__send__(to_call) : val 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 self.new(name = nil, doc = nil) - name ||= self.registered_name - - 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 + # Turn the object into a proper stanza + # + # @return a stanza object def to_stanza self.class.import self end + # @private alias_method :nokogiri_namespace=, :namespace= + # Attach a namespace to the node + # + # @overload namespace=(ns) + # Attach an already created XML::Namespace + # @param [XML::Namespace] ns the namespace object + # @overload namespace=(ns) + # Create a new namespace and attach it + # @param [String] ns the namespace uri + # @overload namespace=(namespaces) + # Createa and add new namespaces from a hash + # @param [Hash] namespaces a hash of prefix => uri pairs def namespace=(namespaces) case namespaces when Nokogiri::XML::Namespace self.nokogiri_namespace = namespaces when String @@ -204,38 +132,50 @@ self.nokogiri_namespace = ns end end end + # Helper method to get the node's namespace + # + # @return [XML::Namespace, nil] The node's namespace object if it exists def namespace_href namespace.href if namespace end - ## # Remove a child with the name and (optionally) namespace given + # + # @param [String] name the name or xpath of the node to remove + # @param [String, nil] ns the namespace the node is in def remove_child(name, ns = nil) child = xpath(name, ns).first child.remove if child end - ## - # Remove all children with a given name + # Remove all children with a given name regardless of namespace + # + # @param [String] name the name of the nodes to remove def remove_children(name) xpath("./*[local-name()='#{name}']").remove end - ## - # Pull the content from a child + # The content of the named node + # + # @param [String] name the name or xpath of the node + # @param [String, nil] ns the namespace the node is in + # @return [String, nil] the content of the node def content_from(name, ns = nil) child = xpath(name, ns).first child.content if child end - ## # 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 + # If the node exists and the content is nil, the node will be removed + # entirely + # + # @param [String] node the name of the node to update/create + # @param [String, nil] content the content to set within the node 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 @@ -244,12 +184,14 @@ end end alias_method :copy, :dup - ## - # Inherit all of <tt>stanza</tt>'s attributes and children + # Inherit the attributes and children of an XML::Node + # + # @param [XML::Node] stanza the node to inherit + # @return [self] def inherit(stanza) set_namespace stanza.namespace if stanza.namespace inherit_attrs stanza.attributes stanza.children.each do |c| self << (n = c.dup) @@ -257,14 +199,20 @@ n.namespace = ns if ns end self end - ## - # Inherit only <tt>stanza</tt>'s attributes + # Inherit a set of attributes + # + # @param [Hash] attrs a hash of attributes to set on the node + # @return [self] def inherit_attrs(attrs) attrs.each { |name, value| self[name] = value } self end - end #XMPPNode -end + def inspect + self.to_xml + end + end # XMPPNode + +end # Blather