ext/nokogiri/xml_node_set.c
#include <nokogiri.h>
VALUE cNokogiriXmlNodeSet ;
static ID decorate ;
static void
Check_Node_Set_Node_Type(VALUE node)
{
if (!(rb_obj_is_kind_of(node, cNokogiriXmlNode) ||
rb_obj_is_kind_of(node, cNokogiriXmlNamespace))) {
rb_raise(rb_eArgError, "node must be a Nokogiri::XML::Node or Nokogiri::XML::Namespace");
}
}
static
VALUE
ruby_object_get(xmlNodePtr c_node)
{
/* see xmlElementType in libxml2 tree.h */
switch (c_node->type) {
case XML_NAMESPACE_DECL:
/* _private is later in the namespace struct */
return (VALUE)(((xmlNsPtr)c_node)->_private);
case XML_DOCUMENT_NODE:
case XML_HTML_DOCUMENT_NODE:
/* in documents we use _private to store a tuple */
if (DOC_RUBY_OBJECT_TEST(((xmlDocPtr)c_node))) {
return DOC_RUBY_OBJECT((xmlDocPtr)c_node);
}
return (VALUE)NULL;
default:
return (VALUE)(c_node->_private);
}
}
static void
xml_node_set_mark(void *data)
{
xmlNodeSetPtr node_set = data;
VALUE rb_node;
int jnode;
for (jnode = 0; jnode < node_set->nodeNr; jnode++) {
rb_node = ruby_object_get(node_set->nodeTab[jnode]);
if (rb_node) {
rb_gc_mark(rb_node);
}
}
}
static void
xml_node_set_deallocate(void *data)
{
xmlNodeSetPtr node_set = data;
/*
* For reasons outlined in xml_namespace.c, here we reproduce xmlXPathFreeNodeSet() except for the
* offending call to xmlXPathNodeSetFreeNs().
*/
if (node_set->nodeTab != NULL) {
xmlFree(node_set->nodeTab);
}
xmlFree(node_set);
}
static const rb_data_type_t xml_node_set_type = {
.wrap_struct_name = "xmlNodeSet",
.function = {
.dmark = xml_node_set_mark,
.dfree = xml_node_set_deallocate,
},
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
};
static VALUE
xml_node_set_allocate(VALUE klass)
{
return TypedData_Wrap_Struct(klass, &xml_node_set_type, xmlXPathNodeSetCreate(NULL));
}
/* :nodoc: */
static VALUE
rb_xml_node_set_initialize_copy(VALUE rb_self, VALUE rb_other)
{
xmlNodeSetPtr c_self, c_other;
VALUE rb_document;
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
TypedData_Get_Struct(rb_other, xmlNodeSet, &xml_node_set_type, c_other);
xmlXPathNodeSetMerge(c_self, c_other);
rb_document = rb_iv_get(rb_other, "@document");
if (!NIL_P(rb_document)) {
rb_iv_set(rb_self, "@document", rb_document);
rb_funcall(rb_document, decorate, 1, rb_self);
}
return rb_self;
}
static void
xpath_node_set_del(xmlNodeSetPtr cur, xmlNodePtr val)
{
/*
* For reasons outlined in xml_namespace.c, here we reproduce xmlXPathNodeSetDel() except for the
* offending call to xmlXPathNodeSetFreeNs().
*/
int i;
if (cur == NULL) { return; }
if (val == NULL) { return; }
/*
* find node in nodeTab
*/
for (i = 0; i < cur->nodeNr; i++)
if (cur->nodeTab[i] == val) { break; }
if (i >= cur->nodeNr) { /* not found */
return;
}
cur->nodeNr--;
for (; i < cur->nodeNr; i++) {
cur->nodeTab[i] = cur->nodeTab[i + 1];
}
cur->nodeTab[cur->nodeNr] = NULL;
}
/*
* call-seq:
* length
*
* Get the length of the node set
*/
static VALUE
length(VALUE rb_self)
{
xmlNodeSetPtr c_self;
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
return c_self ? INT2NUM(c_self->nodeNr) : INT2NUM(0);
}
/*
* call-seq:
* push(node)
*
* Append +node+ to the NodeSet.
*/
static VALUE
push(VALUE rb_self, VALUE rb_node)
{
xmlNodeSetPtr c_self;
xmlNodePtr node;
Check_Node_Set_Node_Type(rb_node);
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
Noko_Node_Get_Struct(rb_node, xmlNode, node);
xmlXPathNodeSetAdd(c_self, node);
return rb_self;
}
/*
* call-seq:
* delete(node)
*
* Delete +node+ from the Nodeset, if it is a member. Returns the deleted node
* if found, otherwise returns nil.
*/
static VALUE
delete (VALUE rb_self, VALUE rb_node)
{
xmlNodeSetPtr c_self;
xmlNodePtr node;
Check_Node_Set_Node_Type(rb_node);
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
Noko_Node_Get_Struct(rb_node, xmlNode, node);
if (xmlXPathNodeSetContains(c_self, node)) {
xpath_node_set_del(c_self, node);
return rb_node;
}
return Qnil ;
}
/*
* call-seq:
* &(node_set)
*
* Set Intersection — Returns a new NodeSet containing nodes common to the two NodeSets.
*/
static VALUE
intersection(VALUE rb_self, VALUE rb_other)
{
xmlNodeSetPtr c_self, c_other ;
xmlNodeSetPtr intersection;
if (!rb_obj_is_kind_of(rb_other, cNokogiriXmlNodeSet)) {
rb_raise(rb_eArgError, "node_set must be a Nokogiri::XML::NodeSet");
}
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
TypedData_Get_Struct(rb_other, xmlNodeSet, &xml_node_set_type, c_other);
intersection = xmlXPathIntersection(c_self, c_other);
return noko_xml_node_set_wrap(intersection, rb_iv_get(rb_self, "@document"));
}
/*
* call-seq:
* include?(node)
*
* Returns true if any member of node set equals +node+.
*/
static VALUE
include_eh(VALUE rb_self, VALUE rb_node)
{
xmlNodeSetPtr c_self;
xmlNodePtr node;
Check_Node_Set_Node_Type(rb_node);
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
Noko_Node_Get_Struct(rb_node, xmlNode, node);
return (xmlXPathNodeSetContains(c_self, node) ? Qtrue : Qfalse);
}
/*
* call-seq:
* |(node_set)
*
* Returns a new set built by merging the set and the elements of the given
* set.
*/
static VALUE
rb_xml_node_set_union(VALUE rb_self, VALUE rb_other)
{
xmlNodeSetPtr c_self, c_other;
xmlNodeSetPtr c_new_node_set;
if (!rb_obj_is_kind_of(rb_other, cNokogiriXmlNodeSet)) {
rb_raise(rb_eArgError, "node_set must be a Nokogiri::XML::NodeSet");
}
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
TypedData_Get_Struct(rb_other, xmlNodeSet, &xml_node_set_type, c_other);
c_new_node_set = xmlXPathNodeSetMerge(NULL, c_self);
c_new_node_set = xmlXPathNodeSetMerge(c_new_node_set, c_other);
return noko_xml_node_set_wrap(c_new_node_set, rb_iv_get(rb_self, "@document"));
}
/*
* call-seq:
* -(node_set)
*
* Difference - returns a new NodeSet that is a copy of this NodeSet, removing
* each item that also appears in +node_set+
*/
static VALUE
minus(VALUE rb_self, VALUE rb_other)
{
xmlNodeSetPtr c_self, c_other;
xmlNodeSetPtr new;
int j ;
if (!rb_obj_is_kind_of(rb_other, cNokogiriXmlNodeSet)) {
rb_raise(rb_eArgError, "node_set must be a Nokogiri::XML::NodeSet");
}
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
TypedData_Get_Struct(rb_other, xmlNodeSet, &xml_node_set_type, c_other);
new = xmlXPathNodeSetMerge(NULL, c_self);
for (j = 0 ; j < c_other->nodeNr ; ++j) {
xpath_node_set_del(new, c_other->nodeTab[j]);
}
return noko_xml_node_set_wrap(new, rb_iv_get(rb_self, "@document"));
}
static VALUE
index_at(VALUE rb_self, long offset)
{
xmlNodeSetPtr c_self;
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
if (offset >= c_self->nodeNr || abs((int)offset) > c_self->nodeNr) {
return Qnil;
}
if (offset < 0) { offset += c_self->nodeNr ; }
return noko_xml_node_wrap_node_set_result(c_self->nodeTab[offset], rb_self);
}
static VALUE
subseq(VALUE rb_self, long beg, long len)
{
long j;
xmlNodeSetPtr c_self;
xmlNodeSetPtr new_set ;
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
if (beg > c_self->nodeNr) { return Qnil ; }
if (beg < 0 || len < 0) { return Qnil ; }
if ((beg + len) > c_self->nodeNr) {
len = c_self->nodeNr - beg ;
}
new_set = xmlXPathNodeSetCreate(NULL);
for (j = beg ; j < beg + len ; ++j) {
xmlXPathNodeSetAddUnique(new_set, c_self->nodeTab[j]);
}
return noko_xml_node_set_wrap(new_set, rb_iv_get(rb_self, "@document"));
}
/*
* call-seq:
* [index] -> Node or nil
* [start, length] -> NodeSet or nil
* [range] -> NodeSet or nil
* slice(index) -> Node or nil
* slice(start, length) -> NodeSet or nil
* slice(range) -> NodeSet or nil
*
* Element reference - returns the node at +index+, or returns a NodeSet
* containing nodes starting at +start+ and continuing for +length+ elements, or
* returns a NodeSet containing nodes specified by +range+. Negative +indices+
* count backward from the end of the +node_set+ (-1 is the last node). Returns
* nil if the +index+ (or +start+) are out of range.
*/
static VALUE
slice(int argc, VALUE *argv, VALUE rb_self)
{
VALUE arg ;
long beg, len ;
xmlNodeSetPtr c_self;
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
if (argc == 2) {
beg = NUM2LONG(argv[0]);
len = NUM2LONG(argv[1]);
if (beg < 0) {
beg += c_self->nodeNr ;
}
return subseq(rb_self, beg, len);
}
if (argc != 1) {
rb_scan_args(argc, argv, "11", NULL, NULL);
}
arg = argv[0];
if (FIXNUM_P(arg)) {
return index_at(rb_self, FIX2LONG(arg));
}
/* if arg is Range */
switch (rb_range_beg_len(arg, &beg, &len, (long)c_self->nodeNr, 0)) {
case Qfalse:
break;
case Qnil:
return Qnil;
default:
return subseq(rb_self, beg, len);
}
return index_at(rb_self, NUM2LONG(arg));
}
/*
* call-seq:
* to_a
*
* Return this list as an Array
*/
static VALUE
to_array(VALUE rb_self)
{
xmlNodeSetPtr c_self ;
VALUE list;
int i;
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
list = rb_ary_new2(c_self->nodeNr);
for (i = 0; i < c_self->nodeNr; i++) {
VALUE elt = noko_xml_node_wrap_node_set_result(c_self->nodeTab[i], rb_self);
rb_ary_push(list, elt);
}
return list;
}
/*
* call-seq:
* unlink
*
* Unlink this NodeSet and all Node objects it contains from their current context.
*/
static VALUE
unlink_nodeset(VALUE rb_self)
{
xmlNodeSetPtr c_self;
int j, nodeNr ;
TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self);
nodeNr = c_self->nodeNr ;
for (j = 0 ; j < nodeNr ; j++) {
if (! NOKOGIRI_NAMESPACE_EH(c_self->nodeTab[j])) {
VALUE node ;
xmlNodePtr node_ptr;
node = noko_xml_node_wrap(Qnil, c_self->nodeTab[j]);
rb_funcall(node, rb_intern("unlink"), 0); /* modifies the C struct out from under the object */
Noko_Node_Get_Struct(node, xmlNode, node_ptr);
c_self->nodeTab[j] = node_ptr ;
}
}
return rb_self ;
}
VALUE
noko_xml_node_set_wrap(xmlNodeSetPtr c_node_set, VALUE document)
{
int j;
VALUE rb_node_set ;
if (c_node_set == NULL) {
rb_node_set = xml_node_set_allocate(cNokogiriXmlNodeSet);
} else {
rb_node_set = TypedData_Wrap_Struct(cNokogiriXmlNodeSet, &xml_node_set_type, c_node_set);
}
if (!NIL_P(document)) {
rb_iv_set(rb_node_set, "@document", document);
rb_funcall(document, decorate, 1, rb_node_set);
}
if (c_node_set) {
/* create ruby objects for all the results, so they'll be marked during the GC mark phase */
for (j = 0 ; j < c_node_set->nodeNr ; j++) {
noko_xml_node_wrap_node_set_result(c_node_set->nodeTab[j], rb_node_set);
}
}
return rb_node_set ;
}
VALUE
noko_xml_node_wrap_node_set_result(xmlNodePtr node, VALUE node_set)
{
if (NOKOGIRI_NAMESPACE_EH(node)) {
return noko_xml_namespace_wrap_xpath_copy((xmlNsPtr)node);
} else {
return noko_xml_node_wrap(Qnil, node);
}
}
xmlNodeSetPtr
noko_xml_node_set_unwrap(VALUE rb_node_set)
{
xmlNodeSetPtr c_node_set;
TypedData_Get_Struct(rb_node_set, xmlNodeSet, &xml_node_set_type, c_node_set);
return c_node_set;
}
void
noko_init_xml_node_set(void)
{
cNokogiriXmlNodeSet = rb_define_class_under(mNokogiriXml, "NodeSet", rb_cObject);
rb_define_alloc_func(cNokogiriXmlNodeSet, xml_node_set_allocate);
rb_define_method(cNokogiriXmlNodeSet, "&", intersection, 1);
rb_define_method(cNokogiriXmlNodeSet, "-", minus, 1);
rb_define_method(cNokogiriXmlNodeSet, "[]", slice, -1);
rb_define_method(cNokogiriXmlNodeSet, "delete", delete, 1);
rb_define_method(cNokogiriXmlNodeSet, "include?", include_eh, 1);
rb_define_method(cNokogiriXmlNodeSet, "length", length, 0);
rb_define_method(cNokogiriXmlNodeSet, "push", push, 1);
rb_define_method(cNokogiriXmlNodeSet, "slice", slice, -1);
rb_define_method(cNokogiriXmlNodeSet, "to_a", to_array, 0);
rb_define_method(cNokogiriXmlNodeSet, "unlink", unlink_nodeset, 0);
rb_define_method(cNokogiriXmlNodeSet, "|", rb_xml_node_set_union, 1);
rb_define_private_method(cNokogiriXmlNodeSet, "initialize_copy", rb_xml_node_set_initialize_copy, 1);
decorate = rb_intern("decorate");
}