/* $Id: ruby_xml_xpath.c,v 1.2 2006/04/14 14:45:25 roscopeco Exp $ */ /* Please see the LICENSE file for copyright and distribution information */ #include "libxml.h" #include "ruby_xml_xpath.h" /* * Document-class: XML::XPath * * Includes Enumerable. */ VALUE cXMLXPath; VALUE eXMLXPathInvalidPath; #ifdef LIBXML_XPATH_ENABLED /* * call-seq: * xpath.debug => (true|false) * * Dump libxml debugging information to stdout. * Requires Libxml be compiled with debugging enabled. */ VALUE ruby_xml_xpath_debug(VALUE self) { #ifdef LIBXML_DEBUG_ENABLED ruby_xml_xpath *rxxp; Data_Get_Struct(self, ruby_xml_xpath, rxxp); if (rxxp->xpop != NULL) { xmlXPathDebugDumpObject(stdout, rxxp->xpop, 0); return(Qtrue); } else { return(Qfalse); } #else rb_warn("libxml does not have debugging turned on"); return(Qfalse); #endif } // TODO Maybe we should support [] or some other kind of access if poss. /* * call-seq: * xpath.each { |node| ... } => self * * Call the supplied block for each matching node. */ VALUE ruby_xml_xpath_each(VALUE self) { ruby_xml_xpath *rxxp; VALUE rxnset; Data_Get_Struct(self, ruby_xml_xpath, rxxp); if (rxxp->xpop == NULL || rxxp->xpop->type != XPATH_NODESET) return(Qnil); rxnset = ruby_xml_node_set_new(cXMLNodeSet, rxxp->xd, self, rxxp->xpop->nodesetval); ruby_xml_node_set_each(rxnset); return(rxnset); } /////////////////////////////////////////////////// // TODO xpath_find is throwing TypeError: // // TypeError: can't convert nil into String // // When given a namespace when non exist. /* * call-seq: * XML::XPath.find(path, namespaces = [any]) => xpath * * Find nodes matching the specified xpath (and optionally any of the * supplied namespaces) and return as an XML::Node::Set. * * The optional namespaces argument may take one of * two forms: * * * A string in the form of: "prefix:uri", or * * An array of: * * strings in the form like above * * arrays in the form of ['prefix','uri'] * * If not specified, matching nodes from any namespace * will be included. */ VALUE ruby_xml_xpath_find(int argc, VALUE *argv, VALUE class) { #ifdef LIBXML_XPATH_ENABLED xmlXPathCompExprPtr comp; ruby_xml_node *node; ruby_xml_xpath *rxxp; ruby_xml_xpath_context *rxxpc; ruby_xml_ns *rxns; VALUE rnode, rprefix, ruri, xxpc, xpath, xpath_expr; char *cp; long i; switch(argc) { case 3: /* array of namespaces we allow. * * Accept either: * A string in the form of: "prefix:uri", or * An array of: * *) strings in the form like above * *) arrays in the form of ['prefix','uri'] */ /* Intentionally fall through, we deal with the last arg below * after the XPathContext object has been setup */ case 2: rnode = argv[0]; xpath_expr = argv[1]; break; default: rb_raise(rb_eArgError, "wrong number of arguments (1 or 2)"); } Data_Get_Struct(rnode, ruby_xml_node, node); xxpc = ruby_xml_xpath_context_new4(rnode); if (NIL_P(xxpc)) return(Qnil); Data_Get_Struct(xxpc, ruby_xml_xpath_context, rxxpc); xpath = ruby_xml_xpath_new(cXMLXPath, rnode, xxpc, NULL); Data_Get_Struct(xpath, ruby_xml_xpath, rxxp); rxxpc->ctxt->node = node->node; if (node->node->type == XML_DOCUMENT_NODE) { rxxpc->ctxt->namespaces = xmlGetNsList(node->node->doc, xmlDocGetRootElement(node->node->doc)); } else { rxxpc->ctxt->namespaces = xmlGetNsList(node->node->doc, node->node); } rxxpc->ctxt->nsNr = 0; if (rxxpc->ctxt->namespaces != NULL) { while (rxxpc->ctxt->namespaces[rxxpc->ctxt->nsNr] != NULL) rxxpc->ctxt->nsNr++; } /* Need to loop through the 2nd argument and iterate through the * list of namespaces that we want to allow */ if (argc == 3) { switch (TYPE(argv[2])) { case T_STRING: cp = strchr(StringValuePtr(argv[2]), (int)':'); if (cp == NULL) { rprefix = argv[2]; ruri = Qnil; } else { rprefix = rb_str_new(StringValuePtr(argv[2]), (int)((long)cp - (long)StringValuePtr(argv[2]))); ruri = rb_str_new2(&cp[1]); } /* Should test the results of this */ ruby_xml_xpath_context_register_namespace(xxpc, rprefix, ruri); break; case T_ARRAY: for (i = 0; i < RARRAY(argv[2])->len; i++) { switch (TYPE(RARRAY(argv[2])->ptr[i])) { case T_STRING: cp = strchr(StringValuePtr(RARRAY(argv[2])->ptr[i]), (int)':'); if (cp == NULL) { rprefix = RARRAY(argv[2])->ptr[i]; ruri = Qnil; } else { rprefix = rb_str_new(StringValuePtr(RARRAY(argv[2])->ptr[i]), (int)((long)cp - (long)StringValuePtr(RARRAY(argv[2])->ptr[i]))); ruri = rb_str_new2(&cp[1]); } /* Should test the results of this */ ruby_xml_xpath_context_register_namespace(xxpc, rprefix, ruri); break; case T_ARRAY: if (RARRAY(RARRAY(argv[2])->ptr[i])->len == 2) { rprefix = RARRAY(RARRAY(argv[2])->ptr[i])->ptr[0]; ruri = RARRAY(RARRAY(argv[2])->ptr[i])->ptr[1]; ruby_xml_xpath_context_register_namespace(xxpc, rprefix, ruri); } else { rb_raise(rb_eArgError, "nested array must be an array of strings, prefix and href/uri"); } break; default: if (rb_obj_is_kind_of(RARRAY(argv[2])->ptr[i], cXMLNS) == Qtrue) { Data_Get_Struct(argv[2], ruby_xml_ns, rxns); rprefix = rb_str_new2((const char*)rxns->ns->prefix); ruri = rb_str_new2((const char*)rxns->ns->href); ruby_xml_xpath_context_register_namespace(xxpc, rprefix, ruri); } else rb_raise(rb_eArgError, "Invalid argument type, only accept string, array of strings, or an array of arrays"); } } break; default: if (rb_obj_is_kind_of(argv[2], cXMLNS) == Qtrue) { Data_Get_Struct(argv[2], ruby_xml_ns, rxns); rprefix = rb_str_new2((const char*)rxns->ns->prefix); ruri = rb_str_new2((const char*)rxns->ns->href); ruby_xml_xpath_context_register_namespace(xxpc, rprefix, ruri); } else rb_raise(rb_eArgError, "Invalid argument type, only accept string, array of strings, or an array of arrays"); } } comp = xmlXPathCompile((xmlChar*)StringValuePtr(xpath_expr)); if (comp == NULL) { xmlXPathFreeCompExpr(comp); rb_raise(eXMLXPathInvalidPath, "Invalid XPath expression"); } rxxp->xpop = xmlXPathCompiledEval(comp, rxxpc->ctxt); xmlXPathFreeCompExpr(comp); if (rxxpc->ctxt->namespaces != NULL) xmlFree(rxxpc->ctxt->namespaces); if (rxxp->xpop == NULL) rb_raise(eXMLXPathInvalidPath, "Invalid XPath expression for this document"); if (rxxp->xpop->type != XPATH_NODESET) return(Qnil); return(ruby_xml_node_set_new2(node->xd, xpath, rxxp->xpop->nodesetval)); #else rb_warn("libxml was compiled without XPath support"); return(Qfalse); #endif } VALUE ruby_xml_xpath_find2(int argc, VALUE *argv) { return(ruby_xml_xpath_find(argc, argv, cXMLXPath)); } void ruby_xml_xpath_free(ruby_xml_xpath *rxxp) { if (rxxp->xpop != NULL) { xmlXPathFreeObject(rxxp->xpop); rxxp->xpop = NULL; } free(rxxp); } void ruby_xml_xpath_mark(ruby_xml_xpath *rxxp) { if (rxxp == NULL) return; if (!NIL_P(rxxp->ctxt)) rb_gc_mark(rxxp->ctxt); if (!NIL_P(rxxp->xd)) rb_gc_mark(rxxp->xd); } VALUE ruby_xml_xpath_new(VALUE class, VALUE xd, VALUE ctxt, xmlXPathObjectPtr xpop) { ruby_xml_xpath *rxxp; rxxp = ALLOC(ruby_xml_xpath); rxxp->ctxt = ctxt; rxxp->xd = xd; rxxp->xpop = xpop; return(Data_Wrap_Struct(class, ruby_xml_xpath_mark, ruby_xml_xpath_free, rxxp)); } /* * call-seq: * xpath.set => nodeset * * Obtain an XML::Node::Set with nodes matching this xpath. */ VALUE ruby_xml_xpath_set(VALUE self) { ruby_xml_xpath *rxxp; Data_Get_Struct(self, ruby_xml_xpath, rxxp); if (rxxp->xpop == NULL || rxxp->xpop->type != XPATH_NODESET) return(Qnil); return(ruby_xml_node_set_new(cXMLNodeSet, rxxp->xd, self, rxxp->xpop->nodesetval)); } /* * call-seq: * xpath.set_type => num * * Obtains the type identifier of this xpath * set. */ VALUE ruby_xml_xpath_set_type(VALUE self) { ruby_xml_xpath *rxxp; Data_Get_Struct(self, ruby_xml_xpath, rxxp); return(INT2FIX(rxxp->xpop->type)); } // TODO maybe 'string' should alias as 'to_s'? /* * call-seq: * xpath.string => "xpath" * * Obtain a string representation of this xpath. */ VALUE ruby_xml_xpath_string(VALUE self) { ruby_xml_xpath *rxxp; Data_Get_Struct(self, ruby_xml_xpath, rxxp); if (rxxp->xpop->stringval == NULL) return(Qnil); else return(rb_str_new2((const char*)rxxp->xpop->stringval)); } // Rdoc needs to know #ifdef RDOC_NEVER_DEFINED mXML = rb_define_module("XML"); #endif void ruby_init_xml_xpath(void) { cXMLXPath = rb_define_class_under(mXML, "XPath", rb_cObject); rb_include_module(cXMLNode, rb_const_get(rb_cObject, rb_intern("Enumerable"))); eXMLXPathInvalidPath = rb_define_class_under(cXMLXPath, "InvalidPath", rb_eException); rb_define_const(cXMLXPath, "UNDEFINED", INT2NUM(XPATH_UNDEFINED)); rb_define_const(cXMLXPath, "NODESET", INT2NUM(XPATH_NODESET)); rb_define_const(cXMLXPath, "BOOLEAN", INT2NUM(XPATH_BOOLEAN)); rb_define_const(cXMLXPath, "NUMBER", INT2NUM(XPATH_NUMBER)); rb_define_const(cXMLXPath, "STRING", INT2NUM(XPATH_STRING)); rb_define_const(cXMLXPath, "POINT", INT2NUM(XPATH_POINT)); rb_define_const(cXMLXPath, "RANGE", INT2NUM(XPATH_RANGE)); rb_define_const(cXMLXPath, "LOCATIONSET", INT2NUM(XPATH_LOCATIONSET)); rb_define_const(cXMLXPath, "USERS", INT2NUM(XPATH_USERS)); rb_define_const(cXMLXPath, "XSLT_TREE", INT2NUM(XPATH_XSLT_TREE)); rb_define_singleton_method(cXMLXPath, "find", ruby_xml_xpath_find, 2); rb_define_method(cXMLXPath, "debug", ruby_xml_xpath_debug, 0); rb_define_method(cXMLXPath, "each", ruby_xml_xpath_each, 0); rb_define_method(cXMLXPath, "set", ruby_xml_xpath_set, 0); rb_define_method(cXMLXPath, "set_type", ruby_xml_xpath_set_type, 0); rb_define_method(cXMLXPath, "string", ruby_xml_xpath_string, 0); } #endif /* ifdef LIBXML_XPATH_ENABLED */