diff --git a/blather.gemspec b/blather.gemspec index eb5efed1..11519f93 100644 --- a/blather.gemspec +++ b/blather.gemspec @@ -22,6 +22,7 @@ Gem::Specification.new do |s| s.add_dependency("eventmachine", ["~> 0.12.6"]) s.add_dependency("nokogiri", [">= 1.4.0"]) + s.add_dependency("niceogiri", [">= 0.0.3"]) s.add_dependency("minitest", [">= 1.7.1"]) s.add_dependency("activesupport", [">= 3.0.7"]) diff --git a/lib/blather.rb b/lib/blather.rb index aac87efc..7012c7b2 100644 --- a/lib/blather.rb +++ b/lib/blather.rb @@ -2,7 +2,7 @@ %w[ rubygems eventmachine - nokogiri + niceogiri ipaddr digest/md5 digest/sha1 @@ -13,7 +13,6 @@ blather/core_ext/eventmachine blather/core_ext/ipaddr - blather/core_ext/nokogiri blather/errors blather/errors/sasl_error diff --git a/lib/blather/core_ext/nokogiri.rb b/lib/blather/core_ext/nokogiri.rb deleted file mode 100644 index 3531d93a..00000000 --- a/lib/blather/core_ext/nokogiri.rb +++ /dev/null @@ -1,44 +0,0 @@ -# @private -module Nokogiri -module XML - - # Nokogiri::Node extensions - class Node - # Alias #name to #element_name so we can use #name in an XMPP Stanza context - alias_method :element_name, :name - alias_method :element_name=, :name= - - alias_method :attr_set, :[]= - # Override Nokogiri's attribute setter to add the ability to kill an attribute - # by setting it to nil and to be able to lookup an attribute by symbol - # - # @param [#to_s] name the name of the attribute - # @param [#to_s, nil] value the new value or nil to remove it - def []=(name, value) - name = name.to_s - value.nil? ? remove_attribute(name) : attr_set(name, value.to_s) - end - - alias_method :nokogiri_xpath, :xpath - # Override Nokogiri's #xpath method to add the ability to use symbols for lookup - # and namespace designation - def xpath(*paths) - paths[0] = paths[0].to_s - - if paths.size > 1 && (namespaces = paths.pop).is_a?(Hash) - paths << namespaces.inject({}) { |h,v| h[v[0].to_s] = v[1]; h } - end - - nokogiri_xpath *paths - end - alias_method :find, :xpath - - # Return the first element at a specified xpath - # @see #xpath - def find_first(*paths) - xpath(*paths).first - end - end - -end #XML -end #Blather diff --git a/lib/blather/xmpp_node.rb b/lib/blather/xmpp_node.rb index 74391548..24397a58 100644 --- a/lib/blather/xmpp_node.rb +++ b/lib/blather/xmpp_node.rb @@ -2,15 +2,14 @@ module Blather # Base XML Node # All XML classes subclass XMPPNode it allows the addition of helpers - class XMPPNode < Nokogiri::XML::Node + class XMPPNode < Niceogiri::XML::Node # @private BASE_NAMES = %w[presence message iq].freeze # @private @@registrations = {} - class_inheritable_accessor :registered_ns, - :registered_name + class_inheritable_accessor :registered_ns, :registered_name # Register a new stanza class to a name and/or namespace # @@ -32,8 +31,7 @@ def self.register(name, ns = nil) # @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, ns]] + @@registrations[[name.to_s, ns]] end # Import an XML::Node to the appropriate class @@ -58,43 +56,8 @@ def self.import(node) # @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 - - 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 - - # Helper method to read an attribute - # - # @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 - - # Helper method to write a value to an attribute - # - # @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 - - # Helper method to read the content of a node - # - # @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 + def self.new(name = registered_name, doc = nil) + super name, doc, BASE_NAMES.include?(name.to_s) ? nil : self.registered_ns end # Turn the object into a proper stanza @@ -103,133 +66,6 @@ def read_content(node, to_call = nil) 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 - self.add_namespace nil, namespaces - when Hash - if ns = namespaces.delete(nil) - self.add_namespace nil, ns - end - namespaces.each do |p, n| - ns = self.add_namespace p, n - 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 regardless of namespace - # - # @param [String] name the name of the nodes to remove - def remove_children(name) - xpath("./*[local-name()='#{name}']").remove - end - - # 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 - # - # @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 - else - remove_child node - end - end - - alias_method :copy, :dup - - # 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) - ns = n.namespace_definitions.find { |ns| ns.prefix == c.namespace.prefix } - n.namespace = ns if ns - end - self - end - - # 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 - - # The node as XML - # - # @return [String] XML representation of the node - def inspect - self.to_xml - end - - # Check that a set of fields are equal between nodes - # - # @param [XMPPNode] other the other node to compare against - # @param [*#to_s] fields the set of fields to compare - # @return [Fixnum<-1,0,1>] - def eql?(o, *fields) - o.is_a?(self.class) && fields.all? { |f| self.__send__(f) == o.__send__(f) } - end - - # @private - def ==(o) - eql?(o) - end end # XMPPNode end # Blather diff --git a/spec/blather/xmpp_node_spec.rb b/spec/blather/xmpp_node_spec.rb index f63aa490..319e99e9 100644 --- a/spec/blather/xmpp_node_spec.rb +++ b/spec/blather/xmpp_node_spec.rb @@ -3,28 +3,6 @@ describe Blather::XMPPNode do before { @doc = Nokogiri::XML::Document.new } - it 'generates a new node automatically setting the document' do - n = Blather::XMPPNode.new 'foo' - n.element_name.must_equal 'foo' - n.document.wont_equal @doc - end - - it 'sets the new document root to the node' do - n = Blather::XMPPNode.new 'foo' - n.document.root.must_equal n - end - - it 'does not set the document root if the document is provided' do - n = Blather::XMPPNode.new 'foo', @doc - n.document.root.wont_equal n - end - - it 'generates a new node with the given document' do - n = Blather::XMPPNode.new 'foo', @doc - n.element_name.must_equal 'foo' - n.document.must_equal @doc - end - it 'generates a node based on the registered_name' do foo = Class.new(Blather::XMPPNode) foo.registered_name = 'foo' @@ -51,182 +29,9 @@ class ImportSubClass < Blather::XMPPNode; register 'foo', 'foo:bar'; end Blather::XMPPNode.import(n).must_be_kind_of ImportSubClass end - it 'provides an attribute reader' do - foo = Blather::XMPPNode.new - foo.read_attr(:bar).must_be_nil - foo[:bar] = 'baz' - foo.read_attr(:bar).must_equal 'baz' - end - - it 'provides an attribute reader with converstion' do - foo = Blather::XMPPNode.new - foo.read_attr(:bar, :to_sym).must_be_nil - foo[:bar] = 'baz' - foo.read_attr(:bar, :to_sym).must_equal :baz - end - - it 'provides an attribute writer' do - foo = Blather::XMPPNode.new - foo[:bar].must_be_nil - foo.write_attr(:bar, 'baz') - foo[:bar].must_equal 'baz' - end - - it 'provides a content reader' do - foo = Blather::XMPPNode.new('foo') - foo << (bar = Blather::XMPPNode.new('bar', foo.document)) - bar.content = 'baz' - foo.read_content(:bar).must_equal 'baz' - end - - it 'provides a content reader that converts the value' do - foo = Blather::XMPPNode.new('foo') - foo << (bar = Blather::XMPPNode.new('bar', foo.document)) - bar.content = 'baz' - foo.read_content(:bar, :to_sym).must_equal :baz - end - - it 'provides a content writer' do - foo = Blather::XMPPNode.new('foo') - foo.set_content_for :bar, 'baz' - foo.content_from(:bar).must_equal 'baz' - end - - it 'provides a content writer that removes a child when set to nil' do - foo = Blather::XMPPNode.new('foo') - foo << (bar = Blather::XMPPNode.new('bar', foo.document)) - bar.content = 'baz' - foo.content_from(:bar).must_equal 'baz' - foo.xpath('bar').wont_be_empty - - foo.set_content_for :bar, nil - foo.content_from(:bar).must_be_nil - foo.xpath('bar').must_be_empty - end - it 'can convert itself into a stanza' do class StanzaConvert < Blather::XMPPNode; register 'foo'; end n = Blather::XMPPNode.new('foo') n.to_stanza.must_be_kind_of StanzaConvert end - - it 'provides "attr_accessor" for namespace' do - n = Blather::XMPPNode.new('foo') - n.namespace.must_be_nil - - n.namespace = 'foo:bar' - n.namespace_href.must_equal 'foo:bar' - end - - it 'will remove a child element' do - n = Blather::XMPPNode.new 'foo' - n << Blather::XMPPNode.new('bar', n.document) - n << Blather::XMPPNode.new('bar', n.document) - - n.find(:bar).size.must_equal 2 - n.remove_child 'bar' - n.find(:bar).size.must_equal 1 - end - - it 'will remove a child with a specific xmlns' do - n = Blather::XMPPNode.new 'foo' - n << Blather::XMPPNode.new('bar') - c = Blather::XMPPNode.new('bar') - c.namespace = 'foo:bar' - n << c - - n.find(:bar).size.must_equal 1 - n.find('//xmlns:bar', :xmlns => 'foo:bar').size.must_equal 1 - n.remove_child '//xmlns:bar', :xmlns => 'foo:bar' - n.find(:bar).size.must_equal 1 - n.find('//xmlns:bar', :xmlns => 'foo:bar').size.must_equal 0 - end - - it 'will remove all child elements' do - n = Blather::XMPPNode.new 'foo' - n << Blather::XMPPNode.new('bar') - n << Blather::XMPPNode.new('bar') - - n.find(:bar).size.must_equal 2 - n.remove_children 'bar' - n.find(:bar).size.must_equal 0 - end - - it 'provides a copy mechanism' do - n = Blather::XMPPNode.new 'foo' - n2 = n.copy - n2.object_id.wont_equal n.object_id - n2.element_name.must_equal n.element_name - end - - it 'provides an inherit mechanism' do - n = Blather::XMPPNode.new 'foo' - n2 = Blather::XMPPNode.new 'foo' - n2.content = 'bar' - n2['foo'] = 'bar' - - n.inherit(n2) - n['foo'].must_equal 'bar' - n.content.must_equal 'bar' - n2.to_s.must_equal n.to_s - end - - it 'holds on to namespaces when inheriting content' do - n = parse_stanza('').root - n2 = Blather::XMPPNode.new('message').inherit n - n2.to_s.must_equal n.to_s - end - - it 'provides a mechanism to inherit attrs' do - n = Blather::XMPPNode.new 'foo' - n2 = Blather::XMPPNode.new 'foo' - n2['foo'] = 'bar' - - n.inherit_attrs(n2.attributes) - n['foo'].must_equal 'bar' - end - - it 'has a content_from helper that pulls the content from a child node' do - f = Blather::XMPPNode.new('foo') - f << (b = Blather::XMPPNode.new('bar')) - b.content = 'content' - f.content_from(:bar).must_equal 'content' - end - - it 'returns nil when sent #content_from and a missing node' do - f = Blather::XMPPNode.new('foo') - f.content_from(:bar).must_be_nil - end - - it 'creates a new node and sets content when sent #set_content_for' do - f = Blather::XMPPNode.new('foo') - f.must_respond_to :set_content_for - f.xpath('bar').must_be_empty - f.set_content_for :bar, :baz - f.xpath('bar').wont_be_empty - f.xpath('bar').first.content.must_equal 'baz' - end - - it 'removes a child node when sent #set_content_for with nil' do - f = Blather::XMPPNode.new('foo') - f << (b = Blather::XMPPNode.new('bar')) - f.must_respond_to :set_content_for - f.xpath('bar').wont_be_empty - f.set_content_for :bar, nil - f.xpath('bar').must_be_empty - end - - it 'will change the content of an existing node when sent #set_content_for' do - f = Blather::XMPPNode.new('foo') - f << (b = Blather::XMPPNode.new('bar')) - b.content = 'baz' - f.must_respond_to :set_content_for - f.xpath('bar').wont_be_empty - f.xpath('bar').first.content.must_equal 'baz' - control = f.xpath('bar').first.pointer_id - - f.set_content_for :bar, 'fiz' - f.xpath('bar').first.content.must_equal 'fiz' - f.xpath('bar').first.pointer_id.must_equal control - end end