/** * @file xpath.c * SQLite extension module to select parts of XML documents * using libxml2 XPath and SQLite's virtual table mechanism. * * 2013 March 15 * * The author disclaims copyright to this source code. * In place of a legal notice, here is a blessing: * * May you do good and not evil. * May you find forgiveness for yourself and forgive others. * May you share freely, never taking more than you give. * ******************************************************************** */ #if defined(_WIN32) || defined(_WIN64) #include #endif #include #include #include #include #include #include #include #ifdef WITH_XSLT #include #include #include #endif #ifdef STANDALONE #include #else #include static SQLITE_EXTENSION_INIT1 #endif /** * @typedef XDOC * @struct XDOC * Structure to cache XML document. */ typedef struct XDOC { xmlDocPtr doc; /**< XML document. */ int refcnt; /**< Reference counter. */ } XDOC; /** * @typedef XMOD * @struct XMOD * Structure holding per module/database data. */ typedef struct XMOD { int refcnt; /**< Reference counter. */ sqlite3_mutex *mutex; /**< DOC table mutex. */ int sdoc; /**< Size of docs array. */ int ndoc; /**< Number of used entries in docs array. */ XDOC *docs; /**< Array of modules's DOCs. */ } XMOD; static int initialized = 0; static XMOD *xmod = 0; /** * @typedef XTAB * @struct XTAB * Structure to describe virtual table. */ typedef struct XTAB { sqlite3_vtab vtab; /**< SQLite virtual table. */ sqlite3 *db; /**< Open database. */ XMOD *xm; /**< Module data. */ struct XCSR *xc; /**< Current cursor. */ int sdoc; /**< Size of idocs array. */ int ndoc; /**< Number of used entries in idocs array. */ int *idocs; /**< Indexes in module-wide DOC table. */ } XTAB; /** * @typedef XEXP * @struct XEXP * Structure to describe XPath expression. */ typedef struct XEXP { struct XEXP *next; /**< Next item. */ struct XEXP *prev; /**< Previous item. */ xmlDocPtr doc; /**< Current XML document. */ xmlXPathContextPtr pctx; /**< Current XPath context. */ xmlXPathObjectPtr pobj; /**< Current XPath objects. */ xmlNodePtr parent; /**< Current parent node or NULL. */ int pos; /**< Position within XPath expr. */ int conv; /**< Conversion: string/boolean/number. */ char expr[1]; /**< XPath expression text. */ } XEXP; /** * @typedef XCSR * @struct XCSR * Structure to describe virtual table cursor. */ typedef struct XCSR { sqlite3_vtab_cursor cursor; /**< SQLite virtual table cursor */ int pos; /**< Current index. */ int nexpr; /**< Number of XPath expr. */ XEXP *first; /**< First XPath expr. */ XEXP *last; /**< Last XPath expr. */ } XCSR; /** * Connect to virtual table. * @param db SQLite database pointer * @param aux user specific pointer * @param argc argument count * @param argv argument vector * @param vtabp pointer receiving virtual table pointer * @param errp pointer receiving error messag * @result SQLite error code * * Argument vector contains: * * argv[0] - module name
* argv[1] - database name
* argv[2] - table name (virtual table)
*/ static int xpath_connect(sqlite3* db, void *aux, int argc, const char * const *argv, sqlite3_vtab **vtabp, char **errp) { int rc = SQLITE_ERROR; XTAB *xt; xt = sqlite3_malloc(sizeof (XTAB)); if (!xt) { nomem: *errp = sqlite3_mprintf("out of memory"); return rc; } memset(xt, 0, sizeof (XTAB)); xt->db = db; xt->xm = (XMOD *) aux; xt->xc = 0; xt->sdoc = 128; xt->ndoc = 0; xt->idocs = sqlite3_malloc(xt->sdoc * sizeof (int)); if (!xt->idocs) { sqlite3_free(xt); goto nomem; } rc = sqlite3_declare_vtab(db, "CREATE TABLE x(" " DOCID INTEGER PRIMARY KEY," " XML HIDDEN BLOB," " PATH HIDDEN TEXT," " OPTIONS HIDDEN INTEGER," " ENCODING HIDDEN TEXT," " BASEURL HIDDEN TEXT," " XMLDUMP HIDDEN TEXT" ")"); if (rc != SQLITE_OK) { sqlite3_free(xt->idocs); sqlite3_free(xt); *errp = sqlite3_mprintf("table definition failed (error %d)", rc); return rc; } *vtabp = &xt->vtab; *errp = 0; return SQLITE_OK; } /** * Create virtual table. * @param db SQLite database pointer * @param aux user specific pointer * @param argc argument count * @param argv argument vector * @param vtabp pointer receiving virtual table pointer * @param errp pointer receiving error messag * @result SQLite error code */ static int xpath_create(sqlite3* db, void *aux, int argc, const char *const *argv, sqlite3_vtab **vtabp, char **errp) { return xpath_connect(db, aux, argc, argv, vtabp, errp); } /** * Disconnect virtual table. * @param vtab virtual table pointer * @result always SQLITE_OK */ static int xpath_disconnect(sqlite3_vtab *vtab) { XTAB *xt = (XTAB *) vtab; XMOD *xm = xt->xm; int i, n; if (xm->mutex) { sqlite3_mutex_enter(xm->mutex); for (i = 0; xm->docs && (i < xt->ndoc); i++) { n = xt->idocs[i]; if ((n >= 0) && (n < xm->sdoc)) { xmlDocPtr doc = xm->docs[n].doc; if (doc) { xm->docs[n].refcnt -= 1; if (xm->docs[n].refcnt <= 0) { xm->docs[n].doc = 0; xm->docs[n].refcnt = 0; xm->ndoc--; xmlFreeDoc(doc); } } } } sqlite3_mutex_leave(xm->mutex); } sqlite3_free(xt->idocs); sqlite3_free(xt); return SQLITE_OK; } /** * Destroy virtual table. * @param vtab virtual table pointer * @result always SQLITE_OK */ static int xpath_destroy(sqlite3_vtab *vtab) { return xpath_disconnect(vtab); } /** * Determines information for filter function according to constraints. * @param vtab virtual table * @param info index/constraint information * @result SQLite error code */ static int xpath_bestindex(sqlite3_vtab *vtab, sqlite3_index_info *info) { return SQLITE_OK; } /** * Open virtual table and return cursor. * @param vtab virtual table pointer * @param cursorp pointer receiving cursor pointer * @result SQLite error code */ static int xpath_open(sqlite3_vtab *vtab, sqlite3_vtab_cursor **cursorp) { XCSR *xc = sqlite3_malloc(sizeof (XCSR)); if (!xc) { return SQLITE_ERROR; } xc->cursor.pVtab = vtab; xc->pos = -1; xc->nexpr = 0; xc->first = xc->last = 0; *cursorp = &xc->cursor; return SQLITE_OK; } /** * Close virtual table cursor. * @param cursor cursor pointer * @result SQLite error code */ static int xpath_close(sqlite3_vtab_cursor *cursor) { XCSR *xc = (XCSR *) cursor; XEXP *xp = xc->first, *next; XTAB *xt = (XTAB *) xc->cursor.pVtab; while (xp) { next = xp->next; if (xp->pobj) { xmlXPathFreeObject(xp->pobj); } if (xp->pctx) { xmlXPathFreeContext(xp->pctx); } sqlite3_free(xp); xp = next; } if (xt->xc == xc) { xt->xc = 0; } sqlite3_free(xc); return SQLITE_OK; } /** * Retrieve next row from virtual table cursor. * @param cursor virtual table cursor * @result SQLite error code */ static int xpath_next(sqlite3_vtab_cursor *cursor) { XCSR *xc = (XCSR *) cursor; XTAB *xt = (XTAB *) xc->cursor.pVtab; XEXP *xp; if (xc->pos < xt->ndoc) { int ninc = 0; if ((xc->pos >= 0) && xc->nexpr) { int newpos; xmlNodePtr node, parent = 0; xp = xc->first; while (xp) { if (xp->pobj) { if (xp == xc->first) { parent = xp->parent; } else if (parent != xp->parent) { break; } } xp = xp->next; } if (parent && !xp) { int pchg = 0; xp = xc->first; while (xp) { if (xp->pobj && (xp->pobj->type == XPATH_NODESET) && xp->pobj->nodesetval) { newpos = xp->pos + 1; if (newpos < xp->pobj->nodesetval->nodeNr) { node = xp->pobj->nodesetval->nodeTab[newpos]; if (node->parent != xp->parent) { pchg++; } } else { pchg++; } } xp = xp->next; } if ((pchg != 0) && (pchg != xc->nexpr)) { xp = xc->first; while (xp) { if (xp->pobj && (xp->pobj->type == XPATH_NODESET) && xp->pobj->nodesetval) { newpos = xp->pos + 1; if (newpos < xp->pobj->nodesetval->nodeNr) { node = xp->pobj->nodesetval->nodeTab[newpos]; if (node->parent == xp->parent) { xp->pos = newpos; ninc++; } } else { xp->pos = xp->pobj->nodesetval->nodeNr; ninc++; } } xp = xp->next; } } } if (!ninc) { xp = xc->first; while (xp) { if (xp->pobj && (xp->pobj->type == XPATH_NODESET) && xp->pobj->nodesetval) { newpos = xp->pos + 1; if (newpos < xp->pobj->nodesetval->nodeNr) { xp->pos = newpos; ninc++; } else { xp->pos = xp->pobj->nodesetval->nodeNr; } } xp = xp->next; } } } if (!ninc) { xc->pos++; xp = xc->first; while (xp) { xp->pos = -1; xp->parent = 0; xp = xp->next; } } } return SQLITE_OK; } /** * Filter function for virtual table. * @param cursor virtual table cursor * @param idxNum not used * @param idxStr nod used * @param argc number arguments (not used) * @param argv argument (nothing or RHS of filter expression, not used) * @result SQLite error code */ static int xpath_filter(sqlite3_vtab_cursor *cursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { XCSR *xc = (XCSR *) cursor; XTAB *xt = (XTAB *) xc->cursor.pVtab; xc->pos = -1; xt->xc = xc; return xpath_next(cursor); } /** * Return end of table state of virtual table cursor. * @param cursor virtual table cursor * @result true/false */ static int xpath_eof(sqlite3_vtab_cursor *cursor) { XCSR *xc = (XCSR *) cursor; XTAB *xt = (XTAB *) xc->cursor.pVtab; return xc->pos >= xt->ndoc; } /** * Return column data of virtual table. * @param cursor virtual table cursor * @param ctx SQLite function context * @param n column index * @result SQLite error code */ static int xpath_column(sqlite3_vtab_cursor *cursor, sqlite3_context *ctx, int n) { XCSR *xc = (XCSR *) cursor; XTAB *xt = (XTAB *) xc->cursor.pVtab; XMOD *xm = (XMOD *) xt->xm; if ((xc->pos < 0) || (xc->pos >= xt->ndoc)) { sqlite3_result_error(ctx, "column out of bounds", -1); return SQLITE_ERROR; } if (n == 0) { n = xt->idocs[xc->pos]; if (xm->docs[n].doc) { sqlite3_result_int(ctx, n + 1); return SQLITE_OK; } } else if (n == 6) { n = xt->idocs[xc->pos]; if (xm->docs[n].doc) { xmlChar *dump = 0; int dump_len = 0; xmlDocDumpFormatMemoryEnc(xm->docs[n].doc, &dump, &dump_len, "utf-8", 1); if (dump) { sqlite3_result_text(ctx, (char *) dump, dump_len, SQLITE_TRANSIENT); xmlFree(dump); return SQLITE_OK; } } } sqlite3_result_null(ctx); return SQLITE_OK; } /** * Return current rowid of virtual table cursor. * @param cursor virtual table cursor * @param rowidp value buffer to receive current rowid * @result SQLite error code */ static int xpath_rowid(sqlite3_vtab_cursor *cursor, sqlite3_int64 *rowidp) { XCSR *xc = (XCSR *) cursor; XTAB *xt = (XTAB *) xc->cursor.pVtab; XMOD *xm = (XMOD *) xt->xm; int n = xt->idocs[xc->pos]; if (xm->docs[n].doc) { *rowidp = (sqlite3_int64) (n + 1); return SQLITE_OK; } return SQLITE_ERROR; } /** * Insert/delete row into/from virtual table. * @param vtab virtual table pointer * @param argc number of arguments * @param argv argument vector * @param rowidp value buffer to receive rowid * @result SQLite error code * * Examples: * * CREATE VIRTUAL TABLE X USING xpath();
* INSERT INTO X(XML) VALUES('xml-string ...');
* INSERT INTO X(PATH,OPTIONS) VALUES(<url>,0);
* DELETE FROM X WHERE DOCID=<docid>;
* * Virtual table columns: * * DOCID - document identifier and ROWID
* XML - XML string
* PATH - pathname or URL
* OPTIONS - parser options, see libxml's XML_PARSE_* defines
* ENCODING - optional document encoding, default UTF-8
* BASEURL - optional base URL when XML string given
* XMLDUMP - output column, XML dump of document tree
* * All columns except DOCID are hidden. UPDATE on the virtual table * is not supported. Default parser options are XML_PARSE_NOERROR, * XML_PARSE_NOWARNING, and XML_PARSE_NONET. */ static int xpath_update(sqlite3_vtab *vtab, int argc, sqlite3_value **argv, sqlite3_int64 *rowidp) { int n = -1, rc = SQLITE_ERROR; XTAB *xt = (XTAB *) vtab; XMOD *xm = (XMOD *) xt->xm; xmlDocPtr doc = 0, docToFree = 0; if (argc == 1) { /* DELETE */ int i, k = -1; n = sqlite3_value_int(argv[0]); for (i = 0; i < xt->ndoc; i++) { if ((n - 1) == xt->idocs[i]) { k = xt->idocs[i]; memmove(xt->idocs + i, xt->idocs + i + 1, (xt->ndoc - (i + 1)) * sizeof (int)); xt->ndoc--; break; } } if ((k >= 0) && xm->mutex) { n = k; doc = xm->docs[n].doc; } rc = SQLITE_OK; } else if ((argc > 1) && (sqlite3_value_type(argv[0]) == SQLITE_NULL)) { /* INSERT */ int i, docid; int opts = (XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NONET); char *enc = 0; if (sqlite3_value_type(argv[1]) != SQLITE_NULL) { if (vtab->zErrMsg) { sqlite3_free(vtab->zErrMsg); } vtab->zErrMsg = sqlite3_mprintf("ROWID must be NULL"); rc = SQLITE_CONSTRAINT; goto done; } if (sqlite3_value_type(argv[2]) != SQLITE_NULL) { docid = sqlite3_value_int(argv[2]); if ((sqlite3_value_type(argv[3]) != SQLITE_NULL) || (sqlite3_value_type(argv[4]) != SQLITE_NULL)) { if (vtab->zErrMsg) { sqlite3_free(vtab->zErrMsg); } vtab->zErrMsg = sqlite3_mprintf("XML and PATH must be NULL"); rc = SQLITE_CONSTRAINT; goto done; } sqlite3_mutex_enter(xm->mutex); for (i = 0; xm->docs && (i < xt->ndoc); i++) { if ((docid - 1) == xt->idocs[i]) { sqlite3_mutex_leave(xm->mutex); if (vtab->zErrMsg) { sqlite3_free(vtab->zErrMsg); } vtab->zErrMsg = sqlite3_mprintf("constraint violation"); rc = SQLITE_CONSTRAINT; goto done; } } if ((docid > 0) && (docid <= xm->sdoc)) { doc = xm->docs[docid - 1].doc; if (doc) { xm->docs[docid - 1].refcnt++; } } sqlite3_mutex_leave(xm->mutex); if (!doc) { if (vtab->zErrMsg) { sqlite3_free(vtab->zErrMsg); } vtab->zErrMsg = sqlite3_mprintf("invalid DOCID"); goto done; } goto havedoc; } if (((sqlite3_value_type(argv[3]) == SQLITE_NULL) && (sqlite3_value_type(argv[4]) == SQLITE_NULL)) || ((sqlite3_value_type(argv[3]) != SQLITE_NULL) && (sqlite3_value_type(argv[4]) != SQLITE_NULL))) { if (vtab->zErrMsg) { sqlite3_free(vtab->zErrMsg); } vtab->zErrMsg = sqlite3_mprintf("specify one of XML or PATH"); rc = SQLITE_CONSTRAINT; goto done; } if (sqlite3_value_type(argv[5]) != SQLITE_NULL) { opts = sqlite3_value_int(argv[5]); } if (sqlite3_value_type(argv[6]) != SQLITE_NULL) { enc = (char *) sqlite3_value_text(argv[6]); } if (sqlite3_value_type(argv[4]) != SQLITE_NULL) { doc = xmlReadFile((char *) sqlite3_value_text(argv[4]), enc, opts); } else { char *url = 0; if (sqlite3_value_type(argv[7]) != SQLITE_NULL) { url = (char *) sqlite3_value_text(argv[7]); } doc = xmlReadMemory(sqlite3_value_blob(argv[3]), sqlite3_value_bytes(argv[3]), url ? url : "", enc, opts); } if (!doc) { if (vtab->zErrMsg) { sqlite3_free(vtab->zErrMsg); } vtab->zErrMsg = sqlite3_mprintf("read error"); goto done; } docToFree = doc; havedoc: if (xt->ndoc >= xt->sdoc) { int *idocs = sqlite3_realloc(xt->idocs, xt->sdoc + 128 * sizeof (int)); if (!idocs) { goto nomem; } xt->idocs = idocs; xt->sdoc += 128; } if (!xm->mutex) { goto nomem; } sqlite3_mutex_enter(xm->mutex); if (xm->ndoc >= xt->sdoc) { XDOC *docs = sqlite3_realloc(xm->docs, xt->sdoc + 128 * sizeof (XDOC)); if (!docs) { sqlite3_mutex_leave(xm->mutex); goto nomem; } xm->docs = docs; docs += xt->sdoc; memset(docs, 0, 128 * sizeof (XDOC)); xt->sdoc += 128; } for (i = 0; i < xm->sdoc; i++) { if (!xm->docs[i].doc) { xm->docs[i].doc = doc; xm->docs[i].refcnt = 1; xm->ndoc++; xt->idocs[xt->ndoc++] = i; *rowidp = (sqlite3_int64) (i + 1); doc = docToFree = 0; rc = SQLITE_OK; break; } } } else { /* UPDATE */ if (vtab->zErrMsg) { sqlite3_free(vtab->zErrMsg); } vtab->zErrMsg = sqlite3_mprintf("UPDATE not supported"); } done: if (docToFree) { xmlFreeDoc(docToFree); } else if (doc && (n >= 0)) { sqlite3_mutex_enter(xm->mutex); xm->docs[n].refcnt -= 1; if (xm->docs[n].refcnt <= 0) { xm->docs[n].doc = 0; xm->docs[n].refcnt = 0; xm->ndoc--; xmlFreeDoc(doc); } sqlite3_mutex_leave(xm->mutex); } return rc; nomem: if (vtab->zErrMsg) { sqlite3_free(vtab->zErrMsg); } vtab->zErrMsg = sqlite3_mprintf("out of memory"); rc = SQLITE_NOMEM; goto done; } /** * Common XPath select function for virtual table. * @param ctx SQLite function context * @param conv conversion (0=string, 1=boolean, 2=number) * @param argc number of arguments * @param argv argument vector * * Examples: * * CREATE VIRTUAL TABLE X USING xpath();
* INSERT INTO X(XML) VALUES('xml-string ...');
* SELECT xpath_string(docid, '//book/title') FROM X;
* SELECT xpath_number(docid, '//book/price') FROM X;
* * The RHS of the xpath_* functions should be a constant string. */ static void xpath_vfunc_common(sqlite3_context *ctx, int conv, int argc, sqlite3_value **argv) { XTAB *xt = (XTAB *) sqlite3_user_data(ctx); XMOD *xm = xt->xm; XCSR *xc = xt->xc; XEXP *xp; xmlXPathContextPtr pctx = 0; xmlXPathObjectPtr pobj = 0; int n; char *p; if ((argc < 2) || !sqlite3_value_text(argv[1])) { sqlite3_result_error(ctx, "wrong arguments", -1); goto done; } if (!xc) { sqlite3_result_error(ctx, "not in virtual table context", -1); goto done; } if ((xc->pos < 0) || (xc->pos >= xt->ndoc)) { sqlite3_result_error(ctx, "cursor out of bounds", -1); goto done; } n = xt->idocs[xc->pos]; if (!xm->docs[n].doc) { sqlite3_result_error(ctx, "no docid", -1); goto done; } p = (char *) sqlite3_value_text(argv[1]); if (!p || !p[0]) { sqlite3_result_error(ctx, "no or empty XPath expression", -1); goto done; } xp = xc->first; while (xp) { if (!strcmp(p, xp->expr)) { break; } xp = xp->next; } if (!xp) { xp = sqlite3_malloc(sizeof (XEXP) + strlen(p)); if (!xp) { sqlite3_result_error(ctx, "out of memory", -1); goto done; } xp->next = xp->prev = 0; strcpy(xp->expr, p); pctx = xmlXPathNewContext(xm->docs[n].doc); if (!pctx) { sqlite3_free(xp); sqlite3_result_error(ctx, "out of memory", -1); goto done; } pobj = xmlXPathEvalExpression((xmlChar *) xp->expr, pctx); if (!pobj) { sqlite3_free(xp); sqlite3_result_error(ctx, "bad XPath expression", -1); goto done; } xp->doc = xm->docs[n].doc; xp->pctx = pctx; xp->pobj = pobj; xp->parent = 0; xp->pos = -1; xp->conv = conv; pctx = 0; pobj = 0; xc->nexpr++; if (xc->first) { xc->last->next = xp; xp->prev = xc->last; xc->last = xp; } else { xc->first = xc->last = xp; } } else if (xm->docs[n].doc != xp->doc) { if (xp->pobj) { xmlXPathFreeObject(xp->pobj); xp->pobj = 0; } if (xp->pctx) { xmlXPathFreeContext(xp->pctx); xp->pctx = 0; } xp->doc = xm->docs[n].doc; xp->parent = 0; xp->pos = -1; if (xp->doc) { pctx = xmlXPathNewContext(xm->docs[n].doc); if (!pctx) { sqlite3_result_error(ctx, "out of memory", -1); goto done; } pobj = xmlXPathEvalExpression((xmlChar *) xp->expr, pctx); if (!pobj) { sqlite3_result_error(ctx, "bad XPath expression", -1); goto done; } xp->pctx = pctx; xp->pobj = pobj; pctx = 0; pobj = 0; } } if (xp->pos < 0) { xp->pos = 0; } if (!xp->pobj) { xp->parent = 0; sqlite3_result_null(ctx); goto done; } if ((xp->pobj->type == XPATH_NODESET) && xp->pobj->nodesetval) { if ((xp->pos < 0) || (xp->pos >= xp->pobj->nodesetval->nodeNr)) { xp->parent = 0; sqlite3_result_null(ctx); } else { xmlNodePtr node = xp->pobj->nodesetval->nodeTab[xp->pos]; xmlBufferPtr buf = 0; xp->parent = node->parent; if (node) { switch (xp->conv) { case 1: p = (char *) xmlXPathCastNodeToString(node); n = xmlXPathCastStringToBoolean((xmlChar *) p); sqlite3_result_int(ctx, n); if (p) { xmlFree(p); } break; case 2: sqlite3_result_double(ctx, xmlXPathCastNodeToNumber(node)); break; case 3: buf = xmlBufferCreate(); if (!buf) { sqlite3_result_error(ctx, "out of memory", -1); goto done; } xmlNodeDump(buf, xp->doc, node, 0, 0); sqlite3_result_text(ctx, (char *) xmlBufferContent(buf), xmlBufferLength(buf), SQLITE_TRANSIENT); xmlBufferFree(buf); break; default: p = (char *) xmlXPathCastNodeToString(node); sqlite3_result_text(ctx, p, -1, SQLITE_TRANSIENT); if (p) { xmlFree(p); } break; } } else { sqlite3_result_null(ctx); } } } else { xp->parent = 0; switch (xp->conv) { case 1: sqlite3_result_int(ctx, xmlXPathCastToBoolean(xp->pobj)); break; case 2: sqlite3_result_double(ctx, xmlXPathCastToNumber(xp->pobj)); break; default: p = (char *) xmlXPathCastToString(xp->pobj); sqlite3_result_text(ctx, p, -1, SQLITE_TRANSIENT); if (p) { xmlFree(p); } break; } } done: if (pobj) { xmlXPathFreeObject(pobj); } if (pctx) { xmlXPathFreeContext(pctx); } } /** * XPath select function returning string value from virtual table. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector */ static void xpath_vfunc_string(sqlite3_context *ctx, int argc, sqlite3_value **argv) { xpath_vfunc_common(ctx, 0, argc, argv); } /** * XPath select function returning boolean value from virtual table. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector */ static void xpath_vfunc_boolean(sqlite3_context *ctx, int argc, sqlite3_value **argv) { xpath_vfunc_common(ctx, 1, argc, argv); } /** * XPath select function returning number from virtual table. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector */ static void xpath_vfunc_number(sqlite3_context *ctx, int argc, sqlite3_value **argv) { xpath_vfunc_common(ctx, 2, argc, argv); } /** * XPath select function returning XML from virtual table. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector */ static void xpath_vfunc_xml(sqlite3_context *ctx, int argc, sqlite3_value **argv) { xpath_vfunc_common(ctx, 3, argc, argv); } /** * Find overloaded function on virtual table. * @param vtab virtual table * @param nargs number arguments * @param name function name * @param pfunc pointer to function (value return) * @param parg pointer to function's argument (value return) * @result 0 or 1 */ static int xpath_findfunc(sqlite3_vtab *vtab, int nargs, const char *name, void (**pfunc)(sqlite3_context *, int, sqlite3_value **), void **parg) { if (nargs != 2) { return 0; } if (!strcmp(name, "xpath_string")) { *pfunc = xpath_vfunc_string; *parg = vtab; return 1; } if (!strcmp(name, "xpath_boolean")) { *pfunc = xpath_vfunc_boolean; *parg = vtab; return 1; } if (!strcmp(name, "xpath_number")) { *pfunc = xpath_vfunc_number; *parg = vtab; return 1; } if (!strcmp(name, "xpath_xml")) { *pfunc = xpath_vfunc_xml; *parg = vtab; return 1; } return 0; } #if (SQLITE_VERSION_NUMBER > 3004000) /** * Rename virtual table. * @param newname new name for table * @result SQLite error code */ static int xpath_rename(sqlite3_vtab *vtab, const char *newname) { return SQLITE_OK; } #endif /** * SQLite module descriptor. */ static sqlite3_module xpath_mod = { 1, /* iVersion */ xpath_create, /* xCreate */ xpath_connect, /* xConnect */ xpath_bestindex, /* xBestIndex */ xpath_disconnect, /* xDisconnect */ xpath_destroy, /* xDestroy */ xpath_open, /* xOpen */ xpath_close, /* xClose */ xpath_filter, /* xFilter */ xpath_next, /* xNext */ xpath_eof, /* xEof */ xpath_column, /* xColumn */ xpath_rowid, /* xRowid */ xpath_update, /* xUpdate */ 0, /* xBegin */ 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ xpath_findfunc, /* xFindFunction */ #if (SQLITE_VERSION_NUMBER > 3004000) xpath_rename, /* xRename */ #endif }; /** * Common XPath select function. * @param ctx SQLite function context * @param conv conversion (0=string, 1=boolean, 2=number) * @param argc number of arguments * @param argv argument vector * * Examples: * * SELECT xpath_string(<docid>, '//book/title');
* SELECT xpath_number(<xml-string>, '//book/price', * <options;>, <encoding>, * <base-url>);
* * The <docid> argument is the DOCID value of a row * in a virtual table. Otherwise a string containing an * XML document is expected. The optional arguments are
* <options> - parser options, see libxml's XML_PARSE_* defines
* <encoding> - encoding of the XML document
* <base-url> - base URL of the XML document
*/ static void xpath_func_common(sqlite3_context *ctx, int conv, int argc, sqlite3_value **argv) { xmlDocPtr doc = 0, docToFree = 0; xmlXPathContextPtr pctx = 0; xmlXPathObjectPtr pobj = 0; XMOD *xm = (XMOD *) sqlite3_user_data(ctx); int index = 0; char *p; if (argc < 2) { sqlite3_result_null(ctx); goto done; } if (sqlite3_value_type(argv[0]) == SQLITE_INTEGER) { index = sqlite3_value_int(argv[0]); if (!xm->mutex) { sqlite3_result_error(ctx, "init error", -1); goto done; } sqlite3_mutex_enter(xm->mutex); if ((index <= 0) || (index > xm->sdoc) || !xm->docs[index - 1].doc) { sqlite3_mutex_leave(xm->mutex); sqlite3_result_error(ctx, "invalid DOCID", -1); goto done; } doc = xm->docs[index - 1].doc; xm->docs[index - 1].refcnt += 1; sqlite3_mutex_leave(xm->mutex); } else { int opts = (XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NONET); char *enc = 0, *url = 0; p = (char *) sqlite3_value_blob(argv[0]); if (!p) { sqlite3_result_null(ctx); return; } if ((argc > 2) && (sqlite3_value_type(argv[2]) != SQLITE_NULL)) { opts = sqlite3_value_int(argv[2]); } if ((argc > 3) && (sqlite3_value_type(argv[3]) != SQLITE_NULL)) { enc = (char *) sqlite3_value_text(argv[3]); } if ((argc > 4) && (sqlite3_value_type(argv[4]) != SQLITE_NULL)) { url = (char *) sqlite3_value_text(argv[4]); } doc = xmlReadMemory(p, sqlite3_value_bytes(argv[0]), url ? url : "", enc, opts); docToFree = doc; if (!doc) { sqlite3_result_error(ctx, "read error", -1); goto done; } } p = (char *) sqlite3_value_text(argv[1]); if (!p) { sqlite3_result_null(ctx); goto done; } pctx = xmlXPathNewContext(doc); if (!pctx) { sqlite3_result_error(ctx, "out of memory", -1); goto done; } pobj = xmlXPathEvalExpression((xmlChar *) p, pctx); if (!pobj) { sqlite3_result_error(ctx, "bad XPath expression", -1); goto done; } switch (conv) { case 1: sqlite3_result_int(ctx, xmlXPathCastToBoolean(pobj)); break; case 2: sqlite3_result_double(ctx, xmlXPathCastToNumber(pobj)); break; case 3: if ((pobj->type == XPATH_NODESET) && pobj->nodesetval && (pobj->nodesetval->nodeNr)) { xmlNodePtr node = pobj->nodesetval->nodeTab[0]; xmlBufferPtr buf = 0; buf = xmlBufferCreate(); if (!buf) { sqlite3_result_error(ctx, "out of memory", -1); goto done; } xmlNodeDump(buf, doc, node, 0, 0); sqlite3_result_text(ctx, (char *) xmlBufferContent(buf), xmlBufferLength(buf), SQLITE_TRANSIENT); xmlBufferFree(buf); } else { sqlite3_result_null(ctx); } break; default: p = (char *) xmlXPathCastToString(pobj); sqlite3_result_text(ctx, p, -1, SQLITE_TRANSIENT); if (p) { xmlFree(p); } break; } done: if (pobj) { xmlXPathFreeObject(pobj); } if (pctx) { xmlXPathFreeContext(pctx); } if (docToFree) { xmlFreeDoc(docToFree); } else if (doc) { if (xm->mutex) { sqlite3_mutex_enter(xm->mutex); if (xm->docs && index) { xm->docs[index - 1].refcnt -= 1; if (xm->docs[index - 1].refcnt <= 0) { docToFree = doc; xm->docs[index - 1].refcnt = 0; xm->docs[index - 1].doc = 0; } } sqlite3_mutex_leave(xm->mutex); if (docToFree) { xmlFreeDoc(docToFree); } } } } /** * XPath select function returning string value. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector */ static void xpath_func_string(sqlite3_context *ctx, int argc, sqlite3_value **argv) { xpath_func_common(ctx, 0, argc, argv); } /** * XPath select function returning boolean value. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector */ static void xpath_func_boolean(sqlite3_context *ctx, int argc, sqlite3_value **argv) { xpath_func_common(ctx, 1, argc, argv); } /** * XPath select function returning number. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector */ static void xpath_func_number(sqlite3_context *ctx, int argc, sqlite3_value **argv) { xpath_func_common(ctx, 2, argc, argv); } /** * XPath select function returning XML. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector */ static void xpath_func_xml(sqlite3_context *ctx, int argc, sqlite3_value **argv) { xpath_func_common(ctx, 3, argc, argv); } /** * Function to dump XML document. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector * * Examples: * * SELECT xml_dump(<docid>, <encoding> <fmt>)
* * The <docid> argument is the DOCID value of a row * in a virtual table. The <encoding> argument is * the optional encoding (default UTF-8). The <fmt> * argument is the optional formatting flag for the * xmlDocDumpFormatMemoryEnc() libxml2 function. */ static void xpath_func_dump(sqlite3_context *ctx, int argc, sqlite3_value **argv) { XMOD *xm = (XMOD *) sqlite3_user_data(ctx); int index = 0, dump_len = 0, fmt = 1; xmlChar *dump = 0; char *enc = "utf-8"; if (argc < 1) { sqlite3_result_null(ctx); return; } index = sqlite3_value_int(argv[0]); if (argc > 1) { enc = (char *) sqlite3_value_text(argv[1]); if (!enc) { enc = "utf-8"; } } if (argc > 2) { fmt = sqlite3_value_int(argv[2]); } if (!xm->mutex) { sqlite3_result_error(ctx, "init error", -1); return; } sqlite3_mutex_enter(xm->mutex); if ((index <= 0) || (index > xm->sdoc) || !xm->docs[index - 1].doc) { sqlite3_mutex_leave(xm->mutex); sqlite3_result_error(ctx, "invalid DOCID", -1); return; } xmlDocDumpFormatMemoryEnc(xm->docs[index - 1].doc, &dump, &dump_len, enc, fmt); if (dump) { sqlite3_result_text(ctx, (char *) dump, dump_len, SQLITE_TRANSIENT); xmlFree(dump); } sqlite3_mutex_leave(xm->mutex); } #ifdef WITH_XSLT /** * Function to transform XML document using XSLT stylesheet. * @param ctx SQLite function context * @param argc number of arguments * @param argv argument vector * * Examples: * * SELECT xslt_transform(<docid>, <stylesheet>, ...)
* SELECT xslt_transform(<xml-string>, <stylesheet>, * <options;>, <encoding>, * <base-url>, ...);
* * The <docid> argument is the DOCID value of a row * in a virtual table. <stylesheet> is the stylesheet * to apply on that document. When the transformation succeeded, * the transformed document replaces the original document.
* In the second form an XML string is transformed with the * same parameters as in the xpath_string() SQLite function.
* The ellipsis optional arguments are the string parameters for * the XSLT transformation. */ static void xpath_func_transform(sqlite3_context *ctx, int argc, sqlite3_value **argv) { xmlDocPtr doc = 0, docToFree = 0, res = 0; xsltStylesheetPtr cur = 0; XMOD *xm = (XMOD *) sqlite3_user_data(ctx); int index = 0, nparams = 0, param0, i; char *p; const char **params = 0; if (argc < 2) { sqlite3_result_null(ctx); goto done; } if (sqlite3_value_type(argv[0]) == SQLITE_INTEGER) { index = sqlite3_value_int(argv[0]); if (!xm->mutex) { sqlite3_result_error(ctx, "init error", -1); goto done; } sqlite3_mutex_enter(xm->mutex); if ((index <= 0) || (index > xm->sdoc) || !xm->docs[index - 1].doc) { sqlite3_mutex_leave(xm->mutex); sqlite3_result_error(ctx, "invalid DOCID", -1); goto done; } doc = xm->docs[index - 1].doc; xm->docs[index - 1].refcnt += 1; sqlite3_mutex_leave(xm->mutex); param0 = 2; nparams = argc - 2; } else { int opts = (XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NONET); char *enc = 0, *url = 0; p = (char *) sqlite3_value_blob(argv[0]); if (!p) { sqlite3_result_null(ctx); return; } if ((argc > 2) && (sqlite3_value_type(argv[2]) != SQLITE_NULL)) { opts = sqlite3_value_int(argv[2]); } if ((argc > 3) && (sqlite3_value_type(argv[3]) != SQLITE_NULL)) { enc = (char *) sqlite3_value_text(argv[3]); } if ((argc > 4) && (sqlite3_value_type(argv[4]) != SQLITE_NULL)) { url = (char *) sqlite3_value_text(argv[4]); } doc = xmlReadMemory(p, sqlite3_value_bytes(argv[0]), url ? url : "", enc, opts); docToFree = doc; if (!doc) { sqlite3_result_error(ctx, "read error", -1); goto done; } param0 = 5; nparams = argc - 5; } p = (char *) sqlite3_value_text(argv[1]); if (!p) { sqlite3_result_null(ctx); goto done; } cur = xsltParseStylesheetFile((xmlChar *) p); if (!cur) { sqlite3_result_error(ctx, "read error on stylesheet", -1); goto done; } if (nparams <= 0) { nparams = 1; } else { nparams++; } params = sqlite3_malloc(nparams * sizeof (char *)); if (!params) { sqlite3_result_error(ctx, "out of memory", -1); goto done; } for (i = 0; i < (argc - param0); i++) { params[i] = (const char *) sqlite3_value_text(argv[i + param0]); if (!params[i]) { params[i] = ""; } } params[i] = 0; res = xsltApplyStylesheet(cur, doc, params); if (!res) { sqlite3_result_error(ctx, "transformation failed", -1); goto done; } if (docToFree) { xmlChar *str = 0; xmlFreeDoc(docToFree); docToFree = res; i = 0; xsltSaveResultToString(&str, &i, res, cur); if (str) { sqlite3_result_text(ctx, (char *) str, i, SQLITE_TRANSIENT); xmlFree(str); } else { sqlite3_result_null(ctx); } } done: if (params) { sqlite3_free(params); } if (cur) { xsltFreeStylesheet(cur); } if (docToFree) { xmlFreeDoc(docToFree); } else if (doc) { if (xm->mutex) { sqlite3_mutex_enter(xm->mutex); if (xm->docs && index) { docToFree = doc; xm->docs[index - 1].doc = 0; xmlFreeDoc(docToFree); docToFree = 0; xm->docs[index - 1].refcnt -= 1; xm->docs[index - 1].doc = res; if (xm->docs[index - 1].refcnt <= 0) { docToFree = res; xm->docs[index - 1].refcnt = 0; xm->docs[index - 1].doc = 0; } } sqlite3_mutex_leave(xm->mutex); if (docToFree) { xmlFreeDoc(docToFree); } } } } #endif /** * Module finalizer. * @param aux pointer to module data * @result SQLite error code */ static void xpath_fini(void *aux) { XMOD *xm = (XMOD *) aux; XDOC *docs; int i, n, cleanup = 0; sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); if (!mutex) { return; } sqlite3_mutex_enter(mutex); if (initialized) { xm->refcnt--; if (xm->refcnt <= 0) { xmod = 0; initialized = 0; cleanup = 1; } } else { cleanup = 1; } sqlite3_mutex_leave(mutex); if (cleanup) { sqlite3_mutex_enter(xm->mutex); mutex = xm->mutex; xm->mutex = 0; docs = xm->docs; n = xm->ndoc; xm->docs = 0; xm->sdoc = xm->ndoc = 0; sqlite3_mutex_leave(mutex); sqlite3_mutex_free(mutex); for (i = 0; i < n; i++) { if (docs->refcnt <= 0) { xmlFreeDoc(docs->doc); docs->doc = 0; } } sqlite3_free(docs); sqlite3_free(xm); } } /** * Module initializer creating SQLite module and functions. * @param db database pointer * @result SQLite error code */ #ifndef STANDALONE static #endif int xpath_init(sqlite3 *db) { XMOD *xm; int rc; sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); if (!mutex) { return SQLITE_NOMEM; } sqlite3_mutex_enter(mutex); if (!initialized) { xm = sqlite3_malloc(sizeof (XMOD)); if (!xm) { sqlite3_mutex_leave(mutex); return SQLITE_NOMEM; } xm->refcnt = 1; xm->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); if (!xm->mutex) { sqlite3_mutex_leave(mutex); sqlite3_free(xm); return SQLITE_NOMEM; } xm->sdoc = 128; xm->ndoc = 0; xm->docs = sqlite3_malloc(xm->sdoc * sizeof (XDOC)); if (!xm->docs) { sqlite3_mutex_leave(mutex); sqlite3_mutex_free(xm->mutex); sqlite3_free(xm); return SQLITE_NOMEM; } memset(xm->docs, 0, xm->sdoc * sizeof (XDOC)); xmod = xm; initialized = 1; } else { xm = xmod; xm->refcnt++; } sqlite3_mutex_leave(mutex); sqlite3_create_function(db, "xpath_string", -1, SQLITE_UTF8, (void *) xm, xpath_func_string, 0, 0); sqlite3_create_function(db, "xpath_boolean", -1, SQLITE_UTF8, (void *) xm, xpath_func_boolean, 0, 0); sqlite3_create_function(db, "xpath_number", -1, SQLITE_UTF8, (void *) xm, xpath_func_number, 0, 0); sqlite3_create_function(db, "xpath_xml", -1, SQLITE_UTF8, (void *) xm, xpath_func_xml, 0, 0); sqlite3_create_function(db, "xml_dump", -1, SQLITE_UTF8, (void *) xm, xpath_func_dump, 0, 0); #ifdef WITH_XSLT sqlite3_create_function(db, "xslt_transform", -1, SQLITE_UTF8, (void *) xm, xpath_func_transform, 0, 0); #endif rc = sqlite3_create_module_v2(db, "xpath", &xpath_mod, (void *) xm, xpath_fini); if (rc != SQLITE_OK) { sqlite3_create_function(db, "xpath_string", -1, SQLITE_UTF8, (void *) xm, 0, 0, 0); sqlite3_create_function(db, "xpath_boolean", -1, SQLITE_UTF8, (void *) xm, 0, 0, 0); sqlite3_create_function(db, "xpath_number", -1, SQLITE_UTF8, (void *) xm, 0, 0, 0); sqlite3_create_function(db, "xpath_xml", -1, SQLITE_UTF8, (void *) xm, 0, 0, 0); sqlite3_create_function(db, "xml_dump", -1, SQLITE_UTF8, (void *) xm, 0, 0, 0); #ifdef WITH_XSLT sqlite3_create_function(db, "xslt_transform", -1, SQLITE_UTF8, (void *) xm, 0, 0, 0); #endif xpath_fini(xm); } return rc; } #ifndef STANDALONE /** * Initializer for SQLite extension load mechanism. * @param db SQLite database pointer * @param errmsg pointer receiving error message * @param api SQLite API routines * @result SQLite error code */ int sqlite3_extension_init(sqlite3 *db, char **errmsg, const sqlite3_api_routines *api) { SQLITE_EXTENSION_INIT2(api); return xpath_init(db); } #endif