Skip to content

Commit

Permalink
adding first_element_child, last_element_child, elements, refactoring…
Browse files Browse the repository at this point in the history
… element finder nodes
  • Loading branch information
tenderlove committed Mar 29, 2010
1 parent 6dd8cac commit da33d8e
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 28 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rdoc
Expand Up @@ -13,6 +13,12 @@
* XML::Document#create_entity will create new EntityDecl objects. GH #174
* JRuby FFI implementation no longer uses ObjectSpace._id2ref,
instead using Charles Nutter's rocking Weakling gem.
* Nokogiri::XML::Node#first_element_child fetch the first child node that is
an ELEMENT node.
* Nokogiri::XML::Node#last_element_child fetch the last child node that is
an ELEMENT node.
* Nokogiri::XML::Node#elements fetch all children nodes that are ELEMENT
nodes.

* Bugfixes

Expand Down
93 changes: 83 additions & 10 deletions ext/nokogiri/xml_node.c
Expand Up @@ -398,13 +398,10 @@ static VALUE next_element(VALUE self)
xmlNodePtr node, sibling;
Data_Get_Struct(self, xmlNode, node);

sibling = node->next;
sibling = xmlNextElementSibling(node);
if(!sibling) return Qnil;

while(sibling && sibling->type != XML_ELEMENT_NODE)
sibling = sibling->next;

return sibling ? Nokogiri_wrap_xml_node(Qnil, sibling) : Qnil ;
return Nokogiri_wrap_xml_node(Qnil, sibling);
}

/*
Expand All @@ -418,13 +415,10 @@ static VALUE previous_element(VALUE self)
xmlNodePtr node, sibling;
Data_Get_Struct(self, xmlNode, node);

sibling = node->prev;
sibling = xmlPreviousElementSibling(node);
if(!sibling) return Qnil;

while(sibling && sibling->type != XML_ELEMENT_NODE)
sibling = sibling->prev;

return sibling ? Nokogiri_wrap_xml_node(Qnil, sibling) : Qnil ;
return Nokogiri_wrap_xml_node(Qnil, sibling);
}

/* :nodoc: */
Expand Down Expand Up @@ -463,6 +457,40 @@ static VALUE children(VALUE self)
return node_set;
}

/*
* call-seq:
* element_children
*
* Get the list of children for this node as a NodeSet. All nodes will be
* element nodes.
*
* Example:
*
* @doc.root.element_children.all? { |x| x.element? } # => true
*/
static VALUE element_children(VALUE self)
{
xmlNodePtr node;
Data_Get_Struct(self, xmlNode, node);

xmlNodePtr child = xmlFirstElementChild(node);
xmlNodeSetPtr set = xmlXPathNodeSetCreate(child);

VALUE document = DOC_RUBY_OBJECT(node->doc);

if(!child) return Nokogiri_wrap_xml_node_set(set, document);

child = xmlNextElementSibling(child);
while(NULL != child) {
xmlXPathNodeSetAddUnique(set, child);
child = xmlNextElementSibling(child);
}

VALUE node_set = Nokogiri_wrap_xml_node_set(set, document);

return node_set;
}

/*
* call-seq:
* child
Expand All @@ -480,6 +508,48 @@ static VALUE child(VALUE self)
return Nokogiri_wrap_xml_node(Qnil, child);
}

/*
* call-seq:
* first_element_child
*
* Returns the first child node of this node that is an element.
*
* Example:
*
* @doc.root.first_element_child.element? # => true
*/
static VALUE first_element_child(VALUE self)
{
xmlNodePtr node, child;
Data_Get_Struct(self, xmlNode, node);

child = xmlFirstElementChild(node);
if(!child) return Qnil;

return Nokogiri_wrap_xml_node(Qnil, child);
}

/*
* call-seq:
* last_element_child
*
* Returns the last child node of this node that is an element.
*
* Example:
*
* @doc.root.last_element_child.element? # => true
*/
static VALUE last_element_child(VALUE self)
{
xmlNodePtr node, child;
Data_Get_Struct(self, xmlNode, node);

child = xmlLastElementChild(node);
if(!child) return Qnil;

return Nokogiri_wrap_xml_node(Qnil, child);
}

/*
* call-seq:
* key?(attribute)
Expand Down Expand Up @@ -1140,7 +1210,10 @@ void init_xml_node()
rb_define_method(klass, "node_name=", set_name, 1);
rb_define_method(klass, "parent", get_parent, 0);
rb_define_method(klass, "child", child, 0);
rb_define_method(klass, "first_element_child", first_element_child, 0);
rb_define_method(klass, "last_element_child", last_element_child, 0);
rb_define_method(klass, "children", children, 0);
rb_define_method(klass, "element_children", element_children, 0);
rb_define_method(klass, "next_sibling", next_sibling, 0);
rb_define_method(klass, "previous_sibling", previous_sibling, 0);
rb_define_method(klass, "next_element", next_element, 0);
Expand Down
4 changes: 4 additions & 0 deletions lib/nokogiri/ffi/libxml.rb
Expand Up @@ -113,6 +113,10 @@ module LibXML
attach_function :xmlFreeDoc, [:pointer], :void
attach_function :xmlSetTreeDoc, [:pointer, :pointer], :void
attach_function :xmlNewReference, [:pointer, :string], :pointer
attach_function :xmlFirstElementChild, [:pointer], :pointer
attach_function :xmlLastElementChild, [:pointer], :pointer
attach_function :xmlNextElementSibling, [:pointer], :pointer
attach_function :xmlPreviousElementSibling, [:pointer], :pointer
attach_function :xmlNewNode, [:pointer, :string], :pointer
attach_function :xmlCopyNode, [:pointer, :int], :pointer
attach_function :xmlDocCopyNode, [:pointer, :pointer, :int], :pointer
Expand Down
50 changes: 32 additions & 18 deletions lib/nokogiri/ffi/xml/node.rb
Expand Up @@ -78,27 +78,13 @@ def previous_sibling
end

def next_element
sibling_ptr = cstruct[:next]

while ! sibling_ptr.null?
sibling_cstruct = LibXML::XmlNode.new(sibling_ptr)
break if sibling_cstruct[:type] == ELEMENT_NODE
sibling_ptr = sibling_cstruct[:next]
end

return sibling_ptr.null? ? nil : Node.wrap(sibling_ptr)
sibling_ptr = LibXML.xmlNextElementSibling cstruct
sibling_ptr.null? ? nil : Node.wrap(sibling_ptr)
end

def previous_element
sibling_ptr = cstruct[:prev]

while ! sibling_ptr.null?
sibling_cstruct = LibXML::XmlNode.new(sibling_ptr)
break if sibling_cstruct[:type] == ELEMENT_NODE
sibling_ptr = sibling_cstruct[:prev]
end

return sibling_ptr.null? ? nil : Node.wrap(sibling_ptr)
sibling_ptr = LibXML.xmlPreviousElementSibling cstruct
sibling_ptr.null? ? nil : Node.wrap(sibling_ptr)
end

def replace_node new_node
Expand Down Expand Up @@ -130,10 +116,38 @@ def children
return set
end

def element_children
child = LibXML.xmlFirstElementChild(cstruct)
return NodeSet.new(nil) if child.null?
child = Node.wrap(child)

set = NodeSet.wrap(LibXML.xmlXPathNodeSetCreate(child.cstruct), self.document)
return set unless child

next_sibling = LibXML.xmlNextElementSibling(child.cstruct)
while ! next_sibling.null?
child = Node.wrap(next_sibling)
LibXML.xmlXPathNodeSetAddUnique(set.cstruct, child.cstruct)
next_sibling = LibXML.xmlNextElementSibling(child.cstruct)
end

return set
end

def child
(val = cstruct[:children]).null? ? nil : Node.wrap(val)
end

def first_element_child
element_child = LibXML.xmlFirstElementChild(cstruct)
element_child.null? ? nil : Node.wrap(element_child)
end

def last_element_child
element_child = LibXML.xmlLastElementChild(cstruct)
element_child.null? ? nil : Node.wrap(element_child)
end

def key?(attribute)
! (prop = LibXML.xmlHasProp(cstruct, attribute.to_s)).null?
end
Expand Down
1 change: 1 addition & 0 deletions lib/nokogiri/xml/node.rb
Expand Up @@ -292,6 +292,7 @@ def add_next_sibling node
alias :type :node_type
alias :to_str :text
alias :clone :dup
alias :elements :element_children

####
# Returns a hash containing the node's attributes. The key is
Expand Down
17 changes: 17 additions & 0 deletions test/xml/test_node.rb
Expand Up @@ -10,6 +10,23 @@ def setup
@xml = Nokogiri::XML(File.read(XML_FILE), XML_FILE)
end

def test_first_element_child
node = @xml.root.first_element_child
assert_equal 'employee', node.name
assert node.element?, 'node is an element'
end

def test_element_children
nodes = @xml.root.element_children
assert_equal @xml.root.first_element_child, nodes.first
assert nodes.all? { |node| node.element? }, 'all nodes are elements'
end

def test_last_element_child
nodes = @xml.root.element_children
assert_equal nodes.last, @xml.root.element_children.last
end

def test_bad_xpath
bad_xpath = '//foo['

Expand Down

0 comments on commit da33d8e

Please sign in to comment.