This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
#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");
|
||||
}
|
||||
Reference in New Issue
Block a user