/*********************************************************************** * Copyright (C) 1995 Joe English * Freely redistributable *********************************************************************** * * costq.c,v 1.36 1999/07/25 20:49:36 joe Exp * * Author: Joe English * Created: 1 March 1995 * Description: Implementation of costq query module. * */ #include #include #include #include #include "project.h" #include "strmgt.h" #include "esis.h" #include "costq.h" /*+++ Local definitions: */ typedef CQStatus (*cq_clauseproc) (ESISNode node, char *args[], int nargs, CQQuery q, CQContinuation c, void *closure); typedef struct { char *name; cq_clauseproc proc; int nargs; } cq_clausedesc; /*+++ * A rather obscure representation is used for storing the query: * The whole query is a vector of pointers. * The first element points to a cq_clausedesc; * subsequent elements point to the (string) arguments to the clause. * This is followed by another cq_querydesc and its arguments, * and so on. The query is terminated by a NULL. */ #define CQ_CLAUSE(q) ((cq_clausedesc *)((q)[0])) #define CQ_CLAUSEARGS(q) ((char **)((q)+1)) #define CQ_NEXTCLAUSE(q) ((q)+1+CQ_CLAUSE(q)->nargs) /*+++ Macros for defining clauses: */ #define DEFINECLAUSE(p, n) \ static CQStatus p(ESISNode node, char *args[], int nargs, \ CQQuery q, CQContinuation cont, void *closure) #define NUMARGS(n) ASSERT(nargs == n,"Wrong #args") #define FAIL() CQ_FAIL #define SUCCEED(val) (cont)(node, val, closure) #define CONTINUE(nd) Continue(nd, q, cont, closure) static CQStatus Continue(ESISNode nd, CQQuery q, CQContinuation cont, void *closure) { if (*q) return CQ_CLAUSE(q)->proc( nd, CQ_CLAUSEARGS(q), CQ_CLAUSE(q)->nargs, CQ_NEXTCLAUSE(q), cont, closure); else return (*cont)(nd, 0, closure); } /*+++ Top-level query entry point: */ /*ARGSUSED*/ static CQStatus nullcontinuation(ESISNode nd, const char *data, void *closure) { return CQ_SUCCEED; } CQStatus cq_doquery(ESISNode nd, CQQuery q, CQContinuation cont, void *closure) { if (!cont) cont = nullcontinuation; return Continue(nd, q, cont, closure); } int cq_testquery(ESISNode nd, CQQuery q) { CQStatus status; status = cq_doquery(nd, q, 0, 0); return status == CQ_SUCCEED; } /*+++ Navigational clauses: */ DEFINECLAUSE(qparent, 0) { ESISNode parent = esis_parent(node); NUMARGS(0) return parent ? CONTINUE(parent) : FAIL(); } DEFINECLAUSE(qancestor, 0) { ESISNode parent = node; NUMARGS(0) while (parent) { CQStatus status = CONTINUE(parent); if (status == CQ_SUCCEED) return status; parent = esis_parent(parent); } return FAIL(); } DEFINECLAUSE(qrootpath, 0) { ESISPathlocAddr docpos; ESISNode p; NUMARGS(0) if (esis_nodetype(node) != EN_EL && esis_nodetype(node) != EN_PEL) node = esis_parent(node); if (!(node && esis_docpos(node, &docpos))) return FAIL(); p = esis_docroot(node); do { CQStatus status = CONTINUE(p); if (status == CQ_SUCCEED) return status; p = esis_stepdown(p, &docpos); } while (p && esis_depth(p) <= esis_depth(node)); return FAIL(); } DEFINECLAUSE(qdocroot, 0) { ESISNode docroot = esis_docroot(node); NUMARGS(0) return docroot ? CONTINUE(docroot) : FAIL(); } DEFINECLAUSE(qdoctree, 0) { ESISNode docroot = esis_docroot(node); ESISNode next = esis_firstpreorder(docroot); NUMARGS(0) if (!next) return FAIL(); do { CQStatus status = CONTINUE(next); if (status == CQ_SUCCEED) return CQ_SUCCEED; next = esis_nextpreorder(docroot, next); } while (next); return FAIL(); } DEFINECLAUSE(qentity, 1) { char *ename = args[0]; ESISNode entity = esis_entity(node, ename); NUMARGS(1) return entity ? CONTINUE(entity) : FAIL(); } DEFINECLAUSE(qchild, 0) { ESISNode child = esis_firstchild(node); NUMARGS(0) while (child) { CQStatus status = CONTINUE(child); if (status == CQ_SUCCEED) return CQ_SUCCEED; /* else */ child = esis_nextsib(child); } return FAIL(); } DEFINECLAUSE(qforward, 0) { ESISNode docroot = esis_docroot(node); ESISNode next = esis_lastpreorder(node); NUMARGS(0) if (!next) return FAIL(); while ((next = esis_nextpreorder(docroot, next)) != NULL) { CQStatus status = CONTINUE(next); if (status == CQ_SUCCEED) return CQ_SUCCEED; } return FAIL(); } DEFINECLAUSE(qbackward, 0) { ESISNode docroot = esis_docroot(node); ESISNode next = esis_prevpreorder(docroot, node); NUMARGS(0) if (!next) return FAIL(); do { CQStatus status = CONTINUE(next); if (status == CQ_SUCCEED) return CQ_SUCCEED; next = esis_prevpreorder(docroot, next); } while (next); return FAIL(); } DEFINECLAUSE(qearlier, 0) { ESISNode docroot = esis_docroot(node); ESISNode next = docroot; NUMARGS(0) if (!next) return FAIL(); while (next != node) { CQStatus status = CONTINUE(next); if (status == CQ_SUCCEED) return CQ_SUCCEED; next = esis_nextpreorder(docroot, next); } return FAIL(); } DEFINECLAUSE(qsubtree, 0) { ESISNode next = esis_firstpreorder(node); NUMARGS(0) if (!next) return FAIL(); do { CQStatus status = CONTINUE(next); if (status == CQ_SUCCEED) return CQ_SUCCEED; next = esis_nextpreorder(node, next); } while (next); return FAIL(); } DEFINECLAUSE(qdescendant, 0) { ESISNode next = esis_nextpreorder(esis_firstpreorder(node),node); NUMARGS(0) if (!next) return FAIL(); do { CQStatus status = CONTINUE(next); if (status == CQ_SUCCEED) return CQ_SUCCEED; next = esis_nextpreorder(node, next); } while (next); return FAIL(); } DEFINECLAUSE(qnext, 0) { ESISNode next = esis_nextsib(node); NUMARGS(0) while (next) { CQStatus status = CONTINUE(next); if (status == CQ_SUCCEED) return CQ_SUCCEED; /* else */ next = esis_nextsib(next); } return FAIL(); } DEFINECLAUSE(qprev, 0) { ESISNode next = esis_prevsib(node); NUMARGS(0) while (next) { CQStatus status = CONTINUE(next); if (status == CQ_SUCCEED) return CQ_SUCCEED; /* else */ next = esis_prevsib(next); } return FAIL(); } /* esib: elder siblings, in document order: */ DEFINECLAUSE(qesib, 0) { ESISNode next; NUMARGS(0) next = esis_parent(node); if (next) next = esis_firstchild(next); while (next && next != node) { CQStatus status = CONTINUE(next); if (status == CQ_SUCCEED) return CQ_SUCCEED; next = esis_nextsib(next); } return FAIL(); } DEFINECLAUSE(qleft, 0) { ESISNode next = esis_prevsib(node); NUMARGS(0) if (next) return CONTINUE(next); else return FAIL(); } DEFINECLAUSE(qright, 0) { ESISNode next = esis_nextsib(node); NUMARGS(0) if (next) return CONTINUE(next); else return FAIL(); } /*+++ Attributes: */ DEFINECLAUSE(qattribute, 1) { ESISNode at; NUMARGS(1); at = esis_findatt(node,args[0]); return at ? CONTINUE(at) : FAIL(); } DEFINECLAUSE(qattlist, 0) { ESISNode at; CQStatus status = CQ_FAIL; NUMARGS(0); at = esis_firstatt(node); while (at && (status = CONTINUE(at)) == CQ_FAIL) at = esis_nextatt(at); return status; } DEFINECLAUSE(qattname, 0) { ESISToken attname = esis_attname(node); NUMARGS(0) return attname ? SUCCEED(attname) : FAIL(); } /*+++ Test clauses: */ DEFINECLAUSE(qin, 1) { const char *gi = args[0]; ESISNode parent = esis_parent(node); NUMARGS(1) return (tokcmpic(gi,esis_gi(parent))) ? CONTINUE(parent) : FAIL(); } DEFINECLAUSE(qwithin, 1) { const char *gi = args[0]; NUMARGS(1) while (node) { if (tokcmpic(gi,esis_gi(node))) return CONTINUE(node); node = esis_parent(node); } return FAIL(); } DEFINECLAUSE(qwithgi, 1) { ESISToken testgi = args[0], gi=esis_gi(node); NUMARGS(1) return (gi && tokcmpic(testgi,gi)) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qelements, 1) { const char *gi = esis_gi(node), *test = args[0]; NUMARGS(1) return (gi && tokmatchic(gi,test)) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qwithdcn, 1) { char *dcn = esis_dcn(node), *test = args[0]; NUMARGS(1) return (dcn && tokmatchic(dcn,test)) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qhasatt, 1) { ESISToken attname = args[0]; NUMARGS(1) return (esis_hasatt(node, attname)) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qwithattval, 2) { const char *attname = args[0], *testval = args[1]; ESISString attval; int status; NUMARGS(2) attval = esis_attval(node, attname); status = attval && tokcmpic(attval, testval); /* ? case */ return status ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qel, 0) { NUMARGS(0) return (esis_nodetype(node) == EN_EL) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qpel, 0) { NUMARGS(0) return (esis_nodetype(node) == EN_PEL) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qcdata, 0) { NUMARGS(0) return (esis_nodetype(node) == EN_CDATA) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qsdata, 0) { NUMARGS(0) return (esis_nodetype(node) == EN_SDATA) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qdataent, 0) { ESISNodeType nodetype = esis_nodetype(node); NUMARGS(0) return (nodetype == EN_REFERENCE || nodetype == EN_ENTITY) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qre, 0) { NUMARGS(0) return (esis_nodetype(node) == EN_RE) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qpi, 0) { NUMARGS(0) return (esis_nodetype(node) == EN_PI) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qsd, 0) { NUMARGS(0) return (esis_nodetype(node) == EN_SD) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qtextnode, 0) { ESISNodeType nt = esis_nodetype(node); NUMARGS(0) return (nt == EN_CDATA || nt == EN_RE || nt == EN_SDATA) ? CONTINUE(node) : FAIL(); } /*+++ Value-returning clauses: */ DEFINECLAUSE(qnodetype, 0) { const char *nodetype = esis_nodetype_name(esis_nodetype(node)); NUMARGS(0) return SUCCEED(nodetype); } DEFINECLAUSE(qattval, 1) { const char *attname = args[0]; ESISString attval = esis_attval(node, attname); NUMARGS(1) return attval ? SUCCEED(attval) : FAIL(); } DEFINECLAUSE(qtext, 0) { ESISString text = esis_text(node); NUMARGS(0) return text ? SUCCEED(text) : FAIL(); } DEFINECLAUSE(qgi, 0) { ESISToken gi = esis_gi(node); NUMARGS(0) return gi ? SUCCEED(gi) : FAIL(); } DEFINECLAUSE(qename, 0) { ESISToken ename = esis_ename(node); NUMARGS(0) return ename ? SUCCEED(ename) : FAIL(); } DEFINECLAUSE(qdcn,0) { ESISString prop = esis_dcn(node); return prop ? SUCCEED(prop) : FAIL(); } DEFINECLAUSE(qsysid,0) { ESISString prop = esis_sysid(node); return prop ? SUCCEED(prop) : FAIL(); } DEFINECLAUSE(qpubid,0) { ESISString prop = esis_pubid(node); return prop ? SUCCEED(prop) : FAIL(); } DEFINECLAUSE(qhasprop, 1) { NUMARGS(1) return esis_hasprop(node,args[0]) ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qpropval, 1) { char *pval = esis_getprop(node, args[0]); NUMARGS(1) return pval ? SUCCEED(pval) : FAIL(); } DEFINECLAUSE(qwithpropval, 2) { char *propval = esis_getprop(node, args[0]); char *testval = args[1]; int status; NUMARGS(2) status = propval && !strcmp(propval, testval); /* ? case */ return status ? CONTINUE(node) : FAIL(); } /*+++ Ilinks and relations: */ DEFINECLAUSE(qrelation, 1) { ESISToken relname = ucintern(args[0]); ESISNode ilink; NUMARGS(1); for (ilink = esis_relation_first(node, relname); ilink != 0; ilink = esis_relation_next(ilink)) { if (CQ_SUCCEED == CONTINUE(ilink)) return CQ_SUCCEED; } return FAIL(); } DEFINECLAUSE(qilink, 2) { ESISToken relname = ucintern(args[0]); ESISToken anchname = ucintern(args[1]); ESISNode ilink; NUMARGS(2); ilink = esis_first_ilink(node, relname, anchname); while (ilink) { if (CQ_SUCCEED == CONTINUE(ilink)) return CQ_SUCCEED; ilink = esis_next_ilink(node, relname, anchname, ilink); } return FAIL(); } DEFINECLAUSE(qorigin, 0) { ESISNode origin; NUMARGS(0); if (esis_nodetype(node) != EN_ILINK) return CQ_FAIL; return (origin = esis_ilink_origin(node))?CONTINUE(origin):FAIL(); } DEFINECLAUSE(qanchor, 1) { ESISToken anchname = ucintern(args[0]); ESISNode anchor; NUMARGS(1); if (esis_nodetype(node) != EN_ILINK) return CQ_FAIL; anchor = esis_ilink_anchor(node, anchname); return anchor ? CONTINUE(anchor) : FAIL(); } DEFINECLAUSE(qanchtrav, 3) { ESISToken relname = ucintern(args[0]); ESISToken srcanchname = ucintern(args[1]); ESISToken dstanchname = ucintern(args[2]); ESISNode ilink = esis_first_ilink(node, relname, srcanchname); NUMARGS(3); while (ilink) { ESISNode anchor = esis_ilink_anchor(ilink, dstanchname); if (anchor && (CQ_SUCCEED == CONTINUE(anchor))) return CQ_SUCCEED; ilink = esis_next_ilink(node, relname, srcanchname, ilink); } return FAIL(); } /*+++ Misc. clauses: */ DEFINECLAUSE(qcut, 0) { NUMARGS(0) (void)CONTINUE(node); return CQ_SUCCEED; } DEFINECLAUSE(qseqno, 0) { char seqno[32]; NUMARGS(0); sprintf(seqno, "%d", esis_seqno(node)); return SUCCEED(seqno); } DEFINECLAUSE(qdepth, 0) { char depth[32]; NUMARGS(0); sprintf(depth, "%d", esis_depth(node)); return SUCCEED(depth); } /*+++ HyTime/HyTime-like clauses: */ DEFINECLAUSE(qdocpos, 0) { char result[128]; ESISPathlocAddr docpos; NUMARGS(0); if (!esis_docpos(node, &docpos)) return FAIL(); sprintf(result,"%ld %ld %ld %ld", docpos.marklist[0],docpos.marklist[1], docpos.marklist[2],docpos.marklist[3]); return SUCCEED(result); } DEFINECLAUSE(qtreeloc, 1) { ESISNode nd; NUMARGS(1); nd = esis_treeloc(node, args[0]); return nd ? CONTINUE(nd) : FAIL(); } DEFINECLAUSE(qaddress, 0) /* %%% Describe this */ { char result[128]; ESISPathlocAddr docpos; NUMARGS(0); if (!esis_docpos(node, &docpos)) return FAIL(); sprintf(result,"%ld:%ld", docpos.p.pathno,docpos.p.depth); return SUCCEED(result); } DEFINECLAUSE(qnode, 1) /* %%% Describe this */ { ESISPathlocAddr docpos; NUMARGS(1); docpos.p.width = docpos.p.height = 1; if (sscanf(args[0],"%ld:%ld", &docpos.p.pathno, &docpos.p.depth) != 2) { return SUCCEED("Error"); /* %%% report error */ } node = esis_locate(esis_docroot(node), &docpos); return node ? CONTINUE(node) : FAIL(); } DEFINECLAUSE(qnodes, 1) { char *sp; NUMARGS(1); sp = args[0]; while (isspace(*sp)) ++sp; while (*sp) { ESISPathlocAddr docpos; ESISNode nd; docpos.p.width = docpos.p.height = 1; if (sscanf(sp,"%ld:%ld", &docpos.p.pathno, &docpos.p.depth) != 2) { return SUCCEED("Error"); /* %%% report error */ } if ((nd=esis_locate(esis_docroot(node), &docpos)) != NULL) { CQStatus status = CONTINUE(nd); if (status == CQ_SUCCEED) return status; } while (*sp && !isspace(*sp)) ++sp; while (*sp && isspace(*sp)) ++sp; } return FAIL(); } /*+++ Query routines: */ static cq_clausedesc *find_clause(const char *clausename); /* forward */ CQQuery cq_buildquery(char *args[], int nargs, char **errmsg_rtn) { CQQuery q; int i; void **p; q = p = malloc((nargs + 1) * sizeof(char **)); i=0; while (i < nargs) { cq_clausedesc *c = find_clause(args[i]); int j; if (!c) { *errmsg_rtn = malloc(80); sprintf(*errmsg_rtn,"Bad clause name %.40s\n", args[i]); goto err; } if (i+c->nargs >= nargs) {/* %%% bad */ *errmsg_rtn = malloc(80); sprintf(*errmsg_rtn,"%.40s: not enough arguments\n", c->name); goto err; } /* copy clause and arguments: */ *p++ = c; ++i; for (j=0; jnargs; ++j) *p++ = savestring(args[i++]); } *p = 0; ASSERT(i == nargs, "This cannot happen") ASSERT(p-q == nargs, "Miscounted") return q; err: *p = 0; /* ASSERT: q is a consistent query */ cq_destroyquery(q); return 0; } void cq_destroyquery(CQQuery q) { void **p; for (p=q; p[0]; p=CQ_NEXTCLAUSE(p)) { char **pp = CQ_CLAUSEARGS(p); int n = CQ_CLAUSE(p)->nargs; while (n--) free(*pp++); } free(q); } /*+++ Clause table: */ static cq_clausedesc cqtab[] = { /* Navigation: */ { "parent", qparent, 0 }, { "ancestor", qancestor, 0 }, { "rootpath", qrootpath, 0 }, { "left", qleft, 0 }, { "right", qright, 0 }, { "prev", qprev, 0 }, { "next", qnext, 0 }, { "esib", qesib, 0 }, { "ysib", qnext, 0 }, /* ysib, next synonyms */ { "child", qchild, 0 }, { "forward", qforward, 0 }, { "backward", qbackward, 0 }, { "earlier", qearlier, 0 }, { "later", qforward, 0 }, /* later, forward synonyms */ { "subtree", qsubtree, 0 }, { "descendant", qdescendant, 0 }, { "docroot", qdocroot, 0 }, { "doctree", qdoctree, 0 }, /* Test: */ { "el", qel, 0 }, { "pel", qpel, 0 }, { "cdata", qcdata, 0 }, { "sdata", qsdata, 0 }, { "dataent", qdataent, 0 }, { "re", qre, 0 }, { "pi", qpi, 0 }, { "sd", qsd, 0 }, { "textnode", qtextnode, 0 }, /* Elements: */ { "gi", qgi, 0 }, { "withGI", qwithgi, 1 }, { "element", qwithgi, 1 }, /* synonym */ { "elements", qelements, 1 }, /* Attributes */ { "attval", qattval, 1 }, { "hasatt", qhasatt, 1 }, { "withatt", qhasatt, 1 }, /* %%% 'hasatt?' */ { "withattval", qwithattval, 2 }, { "attribute", qattribute, 1 }, { "attlist", qattlist, 0 }, { "attname", qattname, 0 }, /* Properties */ { "hasprop", qhasprop, 1 }, { "propval", qpropval, 1 }, { "withpropval", qwithpropval, 2 }, { "nodetype", qnodetype, 0 }, { "data", qtext, 0 /* %%% */}, { "content", qtext, 0 /* %%% */}, /* Entities */ { "entity", qentity, 1 }, { "ename", qename, 0 }, { "dcn", qdcn, 0 }, { "withdcn", qwithdcn, 1 }, { "sysid", qsysid, 0 }, { "pubid", qpubid, 0 }, /* Relations and ilinks: */ { "relation", qrelation, 1 }, { "origin", qorigin, 0 }, { "ilink", qilink, 2 }, { "anchor", qanchor, 1 }, { "anchtrav", qanchtrav, 3 }, /* Navigate and test: */ { "in", qin, 1 }, { "within", qwithin, 1 }, /* Misc. */ { "!", qcut, 0 }, { "cut", qcut, 0 }, { "seqno", qseqno, 0 }, { "depth", qdepth, 0 }, { "treeloc", qtreeloc, 1}, { "docpos", qdocpos, 0}, { "address", qaddress, 0}, { "node", qnode, 1}, { "nodes", qnodes, 1}, { NULL, NULL, 0 } }; static cq_clausedesc *find_clause(const char *clausename) { int i; for (i=0; cqtab[i].name != NULL; ++i) if (tokcmpic(clausename,cqtab[i].name)) return cqtab + i; return 0; }