#include "nsllib.h" #include "lt-umalloc.h" #include #include "ctype16.h" #include "string16.h" #include "stdio16.h" #include "dtd.h" /* Prototypes for functions defined in this file */ NSL_Q_Attr *ParseQueryAttributeString(NSL_Doctype_I *doctype, NSL_ElementSummary_I *elt, const Char *eltName, const Char **source, boolean allowRx); int SQMatch ( const NSL_Query_I *qu, const NSL_Item *item ); const NSL_Query_I *ParseOneQueryQ ( NSL_Doctype doctype, const Char **quptr, boolean allowRx ); const NSL_Query_I *ParseQueryQ ( NSL_Doctype doctype, const Char *quin, boolean allowRx ); boolean SQAttrPR(NSL_Q_Attr *query_attr, const Char* item_attr_val); int SQAttr(const NSL_Query_I *qu, const NSL_Item *item ); boolean SQSatisfy( const NSL_Query_I *qu, const NSL_Data *dptr); /* user defined comparison operator for query language */ boolean (* LTNSL_User_defined_comparison)(const Char *ival, const Char *qval) = NULL; /* Added for debugging */ void printQuery(const NSL_Query_I* q, int depth); void printShortQuery(FILE16 *stream, const NSL_Query_I* q, boolean first); boolean debugFlag = FALSE; static void debug(FILE16 *stream, const char *before, const char *after, const NSL_Query_I* q, const NSL_Data *data); /* ---------------------------------------------------- */ /* Parse one chunk of a query (one element description) */ /* XXX doesn't free allocated space if it fails! */ const NSL_Query_I *ParseOneQueryQ ( NSL_Doctype doctype, const Char **quptr, boolean allowRx ) { /* note on error there's a memory leak */ Char enbuf[256]; Char *ptr; const Char *qu = *quptr; const Char *orig=qu; NSL_Query_I *query; int len; if (*qu==0) { LT_ERROR(NEQUER, "Query ends after |\n"); return NULL; } ECNN(query=tsalloc(NSL_Query_I, 1)); query->number=-1; query->elname=NULL; query->alist=NULL; query->type=_qu_norm; query->prev=query->next=query->alt=NULL; query->stringType = NSL_use_names; if( !doctype ){ if( NSL_Global_Names == NSL_use_strings ){ /* In this case we are allowed to have a null doctype */ /* and we store element names as strings */ query->elt=NULL; query->stringType = NSL_use_strings; } else { LT_ERROR1(NEQUER,"No Doctype given for ParseQuery \"%S\"\n",qu); return NULL; } } switch (qu[0]) { case '[': /* Added for backward compatibility. No element name means . */ query->elt=NULL; break; case '.': /* open-code some special cases for speed */ switch ((++qu)[0]) { case '*': qu++; query->type=_qu_anystar; break; case '/': case '\000': query->type=_qu_any; break; case '[': break; default: LT_ERROR1(NEQUER,"Invalid GI pattern: %S\n",qu-1); return NULL; } query->elt=NULL; break; case '#': query->type=_qu_data; qu++; break; default: for(len=0; qu[len] && qu[len] != '[' && qu[len] != '/' && qu[len] != '*' && qu[len] != '|'; len++) ; if (len > 0) { if (len<256) { if(doctype && !doctype->XMLMode) { for (ptr=enbuf; len; len--, qu++, ptr++) { *ptr=Toupper(*qu); } } else { for (ptr=enbuf; len; len--, qu++, ptr++) { *ptr=*qu; } } } else { LT_ERROR1(NEQUER,"GI too long (>=256) in query term: %S\n",qu); return NULL; } } else { LT_ERROR1(NEQUER,"GI or . missing in query term: %S\n",orig); return NULL; } query->elname=enbuf; if( ( query->stringType == NSL_use_strings ) ){ *ptr = '\000'; ECNN(query->elname = Strdup(enbuf)); query->elt=0; } else { if(!(query->elt=FindElementAndName(doctype, &query->elname,ptr-enbuf))) { LT_ERROR2(NEQUER,"GI unknown: %.*S\n",ptr-enbuf,enbuf); return NULL; } } } /* Now we're into the [..] bit of the query */ if (qu[0]=='[') { qu++; /* first check see if there is a number */ /* don't use isdigit since that doesn't work for chars > 255 */ if (*qu >= '0' && *qu <= '9') { query->number = *qu++ - '0'; while(*qu >= '0' && *qu <= '9') query->number = query->number * 10 + (*qu++ - '0'); while (is_xml_whitespace(*qu)) { qu++; } } else { if (query->type==_qu_data) { LT_ERROR1(NEQUER,"Only numeric qualifier allowed for data pattern: %S\n", qu-2); } query->number=-1; } /* Then Parse any attribute field string */ query->alist=ParseQueryAttributeString(doctype,query->elt, query->elname,&qu,allowRx); /* no error check since if no crash we can proceed with NULL alist */ if (*(qu++)!=']') { LT_ERROR1(NEQUER,"Missing close bracket: %S\n", qu); *quptr = qu; return query; } } while (is_xml_whitespace(*qu)) { qu++; } if( qu[0] == '|' ){ /* an alternate element description */ qu++; while (is_xml_whitespace(*qu)) { qu++; } query->alt=ParseOneQueryQ(doctype,&qu,allowRx); if(!query->alt) return NULL; } *quptr = qu; return query; } const NSL_Query_I *ParseQueryQ ( NSL_Doctype doctype, const Char *quin, boolean allowRx ) { /* query: pattern on items to select, basically path based with terms separated by /, :=?'*'? :=|'.' :='['|| ']' := :=(' ')* :=('=')? Aname and aval are as per SGML. A GI of . matches any tag. A condition with an index matches only the index'th sub-element of the enclosing element. Attribute tests are not exhaustive, and will match against both explicitly present and defaulted attribute values, using string equality. Bare anames are satisfied by ANY value, explicit or defaulted. Terms ending with * match any number of links in the chain, including 0. Matching is bottom-up, deterministic and shortest-first. */ NSL_Query_I *query; Char *qu = (Char *)quin; if (*qu==0) return NULL; if (*qu=='/') qu++; query = (NSL_Query_I*)ParseOneQueryQ(doctype,(const Char **)&qu,allowRx); if( query ){ if (qu[0]=='*') { query->type=_qu_star; qu++; } if(!qu[0]) { query->next = NULL; return query; } query->next=ParseQueryQ(doctype,qu,allowRx); if (query->next) { if (query->type==_qu_data) { LT_ERROR1(NEQUER,"Data pattern must be last in query: %S\n",qu); query->next=NULL; return query; } /* WE're allowed to do this (override r-o member) */ ((NSL_Query_I *)query->next)->prev=query; } else { return NULL; } } return query; } /* Public function - to parse a query string into a NSL_Query Does NOT allow regular expressions in attribute values (see next function (ParseQueryR) */ NSL_Query ParseQuery8( NSL_Doctype doctype, const char8 *qu ) { NSL_Query q; Char *Qu; ECNN(Qu = strdup_char8_to_Char(qu)); q = ParseQuery(doctype, Qu); sfree(Qu); return q; } NSL_Query ParseQuery( NSL_Doctype doctype, const Char *qu ) { /* allow us to set reg_flag */ return ParseQueryQ( doctype, qu, FALSE ); } /* Public function - to parse a query string into a NSL_Query DOES allow regular expressions in attribute values */ NSL_Query ParseQueryR8( NSL_Doctype doctype, const char8 *qu ) { NSL_Query q; Char *Qu; ECNN(Qu = strdup_char8_to_Char(qu)); q = ParseQueryR(doctype, Qu); sfree(Qu); return q; } NSL_Query ParseQueryR( NSL_Doctype doctype, const Char *qu ) { /* allow us to set reg_flag */ return ParseQueryQ( doctype, qu, TRUE ); } /* ==== SQAttrPR ============================================= */ /* Do a regular expression match between an attribute value (item_attr_val) and a query (query_attr_val) */ /* Version using regexp */ /* Depending on value of reg_flag do either string equality test or regular expression matching between strings query_attr_val and item_attr_val */ /* Now added possibility of having 'not equal' comparison */ boolean SQAttrPR(NSL_Q_Attr *query_attr, const Char* item_attr_val) { double ival, qval; int res; const char8 *str8; switch( query_attr->comparison ){ /* Numerical comparisons */ case _qa_comp_less: case _qa_comp_greater: case _qa_comp_not_less: case _qa_comp_not_greater: str8 = Char_to_char8(item_attr_val, query_attr->transbuf); ival=atof(str8); str8 = Char_to_char8(query_attr->pattern.string, query_attr->transbuf); qval=atof(str8); if( ival < qval ){ return query_attr->comparison == _qa_comp_less || query_attr->comparison == _qa_comp_not_greater; } else if( ival > qval ){ return query_attr->comparison == _qa_comp_greater || query_attr->comparison == _qa_comp_not_less; } else { return query_attr->comparison == _qa_comp_not_less || query_attr->comparison == _qa_comp_not_greater; } /* Textual comparisons */ case _qa_comp_equal: case _qa_comp_not_equal: if( Strcmp(query_attr->pattern.string, item_attr_val) == 0 ){ return ( query_attr->comparison == _qa_comp_equal ); } else { return ( query_attr->comparison == _qa_comp_not_equal ); } /* Regular expression comparisons */ case _qa_comp_regexp: case _qa_comp_not_regexp: str8 = Char_to_char8(item_attr_val, query_attr->transbuf); res = hsregexec(query_attr->pattern.regexp, str8); if(res){ return ( query_attr->comparison == _qa_comp_regexp ); } else { return ( query_attr->comparison == _qa_comp_not_regexp ); } /* User defined comparisons */ case _qa_comp_user_defined: case _qa_comp_not_user_defined: /* User defined routine */ if( LTNSL_User_defined_comparison ){ if( LTNSL_User_defined_comparison(item_attr_val, query_attr->pattern.string)) { return query_attr->comparison == _qa_comp_user_defined; } else { return query_attr->comparison == _qa_comp_not_user_defined; } } else { LT_ERROR(NEMKPR,"User defined comparison function *LTNSL_User_defined_comparison not defined.\n"); return FALSE; } default: SHOULDNT; } return FALSE; } boolean FreeQuery(const NSL_Query_I *q) { /* have to cheat on type heavily -- problem is that NSL_Query_I is not public */ if (q->next) { ((NSL_Query_I *)q->next)->prev=NULL; ECFF(FreeQuery((NSL_Query_I *)(q->next))); } if (q->prev) { ((NSL_Query_I *)q->prev)->next=NULL; ECFF(FreeQuery((NSL_Query_I *)(q->prev))); } if (q->alt) { ECFF(FreeQuery((NSL_Query_I *)(q->alt))); } if (q->alist) { ECFF(FreeQAttr((NSL_Q_Attr *)(q->alist),q->stringType)); } if( q->stringType == NSL_use_strings && q->elname ){ ECFF(sfree((Char*)q->elname)); } return sfree((NSL_Query_I *)q); } #define SkipWhiteSpace while(is_xml_whitespace(*ptr)){ ptr++; } /* Parses string pointed to by *thePtr to find comparison operator (which is returned in a typed form), also increments *thePtr. If forceRx is TRUE, then = and != are interpreted as ~ and !~ respectively (so thart the -r option of sggrep still works). Made static to remove Mac warning. */ static QAComparisontype ParseQueryOperator(const Char **thePtr, boolean forceRx ){ int negatedOperator = FALSE; /* Allow '!' as general negation operator */ if( **thePtr == '!' ){ (*thePtr)++; negatedOperator = TRUE; } /* Get main operator */ switch (**thePtr) { case '=': (*thePtr)++; if( negatedOperator ){ return forceRx ? _qa_comp_not_regexp : _qa_comp_not_equal; } else { return forceRx ? _qa_comp_regexp : _qa_comp_equal; } break; case '~': (*thePtr)++; if( negatedOperator ){ return _qa_comp_not_regexp; } else { return _qa_comp_regexp; } break; case '<': (*thePtr)++; if( negatedOperator ){ return _qa_comp_not_less; } else { return _qa_comp_less; } break; case '>': (*thePtr)++; if( negatedOperator ){ return _qa_comp_not_greater; } else { return _qa_comp_greater; } break; case '?': (*thePtr)++; if( negatedOperator ){ return _qa_comp_not_user_defined; } else { return _qa_comp_user_defined; } break; default: if( negatedOperator ){ LT_ERROR1(NEMKPR,"Invalid comparison operator: %S",*thePtr-1); return _qa_comp_error; } else { return _qa_comp_no_op; } break; } } /* ParseQueryAttributeString - parse a list of ATTRIBUTE OP VALUE pairs in a query string */ #if 0 /* Removed - see below */ static const Char *phonyName=0; static ElementDefinition phonyElt=0; #endif NSL_Q_Attr *ParseQueryAttributeString(NSL_Doctype_I *doctype, NSL_ElementSummary_I *elt, const Char *eltName, const Char **source, boolean allowRx) { Char enbuf[256]; const Char *identifier; const Char *ptr = *source, *vptr; Char *value=NULL,*vvptr; Char qt=0; int len; NSL_Q_Attr *refvar; const AttributeSummary *atsum=NULL; NSL_AVType atype; /* string should be of the form X [ OP Y ] */ /* delimiter set is " =]" */ /* There's nothing for it but duplicating a lot of PAS1 and GetMarkupToken since we can't touch the string */ /* skip spaces, NULL if ], strpbrk(*source," =]"), err if 0, ucase and lookup, skip spaces, return if not =, skip spaces, read string (sigh) */ SkipWhiteSpace; if (*ptr==']') { *source= ptr; return NULL; } /* Get attribute name */ vptr=ptr; vvptr=(Char*) enbuf; len=0; if(doctype && !doctype->XMLMode) { while (is_xml_namechar(*ptr, xml_char_map) && len<256) { *vvptr++=Toupper(*ptr++); len++; } } else { while (is_xml_namechar(*ptr, xml_char_map) && len<256) { *vvptr++=*ptr++; len++; } } if (vptr==ptr) { LT_ERROR1(NEATPR, "** Error parsing query attribute string: invalid identifier near '%S'\n", ptr); return NULL; } enbuf[len]='\000'; identifier=enbuf; if (is_xml_namechar(*ptr, xml_char_map)) { WARN1(NEATPR, "Attribute name truncated to 255 chars: %S\n",enbuf); } if( !doctype ){ if( NSL_Global_Names == NSL_use_strings ){ /* In this case we are allowed to have a null doctype */ /* and we store attribute names as strings */ atsum = NULL; ECNN(identifier = Strdup(identifier)); } else { LT_ERROR1(NEARGNL, "No Doctype given for ParseQuery \"%S\"\n", *source); return NULL; } } else { if ((elt && !(atsum=FindAttrSumAndName(doctype,&elt,eltName, &identifier,len))) || (!(identifier=AttrUniqueName(doctype,identifier,len)))) { if (doctype->XMLMode) { #if 0 /* I don't believe this is ever used, because FindAttrSumAndName and AttrUniqueName both add the attribute (or its name) if it's not found in the XML case. */ /* doesn't check if there is a full dtd */ if (elt) { SHOULDNT; } else { AttributeDefinition atdef; if (!phonyElt) { phonyElt = TentativelyDefineElementN(((NSL_Doctype_I *)doctype)->rxp_dtd, char8toChar("!DUMMY"), 6); ECNN(phonyElt); phonyName=phonyElt->name; } ECNN(atdef = DefineAttributeN(phonyElt, identifier, len, AT_cdata, 0, DT_implied, 0)); identifier = atdef->name; } #else SHOULDNT; #endif } else { if(elt) { LT_ERROR2(NEATPR, "Attribute name in query string not allowed for %S: %S\n", eltName, enbuf); } else { LT_ERROR1(NEATPR, "Attribute name in query string not allowed for any element in DTD: %S\n", enbuf); } return NULL; } } } SkipWhiteSpace; ECNN(refvar=AllocQAttr()); refvar->name=identifier; /* Get OP and attribute value */ refvar->comparison = ParseQueryOperator(&ptr, allowRx); switch( refvar->comparison ){ case _qa_comp_no_op: refvar->pattern.string = NULL; break; default: SkipWhiteSpace; /* Get attribute value */ vptr=ptr; switch (*ptr) { case '"': case '\'': qt=*ptr; vptr++; while (*++ptr && *ptr!=qt) {}; if (!*ptr) { LT_ERROR1(NEMKPR,"Runaway string: %S",value); return NULL; } break; default: while (is_xml_namechar(*ptr, xml_char_map)) { ptr++; } } ECNN(value=salloc(((ptr-vptr)+1) * sizeof(Char))); len=ptr-vptr; if (qt && *ptr==qt) { ptr++; } /* NOTE if no elt we never uppercase pattern */ atype=(atsum?(AttrValueType[(int)atsum->declaredValue]):NSL_attr_string); switch (atype) { case NSL_attr_string: case NSL_attr_entity: case NSL_attr_entities: case NSL_attr_num: case NSL_attr_nums: Strncpy(value,vptr,len); value[len]='\000'; break; default: if(doctype && !doctype->XMLMode) { for (vvptr=value; len; len--, vptr++, vvptr++) { *vvptr=Toupper(*vptr); } vvptr[0]='\000'; /* This used to be [1] */ } else { Strncpy(value,vptr,len); value[len]='\000'; } } if ( refvar->comparison == _qa_comp_regexp || refvar->comparison == _qa_comp_not_regexp ) { char8 *pat; ECNN(pat = strdup_Char_to_char8(value)); sfree(value); ECNN(refvar->pattern.regexp=hsregcomp(pat)); sfree(pat); } else { refvar->pattern.string=value; } } *source= ptr; refvar->next= ParseQueryAttributeString(doctype,elt,eltName,source,allowRx); return (NSL_Q_Attr *)refvar; } /* Check if a query matches against an attribute value */ int SQAttr(const NSL_Query_I *qu, const NSL_Item *item ) { NSL_Q_Attr *aptr; const NSL_Attr *attr; const AttributeSummary *atsum; const Char *temp; for (aptr=qu->alist; aptr; aptr=aptr->next) { /* note we assume aptr->name is correct case -- ht */ if ((attr=FindAttr(item->attr, aptr->name))) { temp= attr->value.string; if (aptr->pattern.string && temp && !SQAttrPR(aptr,temp)) { return 0; } } else if ((atsum=FindAttrSpec(item->defn, item->doctype, aptr->name))) { /* try the default */ if (aptr->pattern.string) { if (!SQAttrPR(aptr,GetAttrDefVal(atsum)) ) { return 0; } } else if (!atsum->defaultPtr) { /* if no value, all we care is there is SOME default */ return 0; } } else { return 0; } } return 1; } /* This starts at the bottom and goes up */ boolean SQSatisfy( const NSL_Query_I *qu, const NSL_Data *dptr) { if (qu==NULL && dptr==NULL) { return TRUE; } if (qu && qu->type==_qu_anystar && qu->prev==NULL && ((!dptr) || (dptr->type==NSL_item_data))) { return TRUE; } if (qu==NULL || dptr==NULL) { return FALSE; } if (dptr->type==NSL_text_data) { if (qu->type==_qu_data && (qu->number<0 || qu->number==dptr->ref)) { return SQSatisfy(qu->prev, dptr->in?dptr->in->in:NULL); } return FALSE; } switch (qu->type) { case _qu_any: return SQSatisfy( qu->prev, dptr->in?dptr->in->in:NULL ); case _qu_anystar: for (; dptr; dptr=dptr->in?dptr->in->in:NULL) { if (SQSatisfy(qu->prev, dptr)) { return TRUE; } } return FALSE; case _qu_star: for (; dptr; dptr=dptr->in?dptr->in->in:NULL) { if (SQSatisfy(qu->prev, dptr)) { return TRUE; } if (!SQMatch(qu,(NSL_Item*)dptr->first)) { return FALSE; } } return FALSE; case _qu_norm: if (SQMatch(qu, (NSL_Item*)dptr->first)) { return SQSatisfy(qu->prev, dptr->in?dptr->in->in:NULL); } else { return FALSE; } break; case _qu_data: return FALSE; break; default: SHOULDNT; return FALSE; } } boolean ExecQueryUp( const NSL_Query_I *qu, const NSL_Data *dptr ) { const NSL_Query_I *q; for (q=qu; q->next; q=q->next) ; /* EMPTY */ return SQSatisfy(q, dptr ); } const NSL_Query_I *InitSegQueryUp( const NSL_Query_I *qu, const NSL_Data *dptr ) { const NSL_Query_I *q; for (q=qu; q->prev; q=q->prev) /* EMPTY */; for (; q; q=q->next) { if (SQSatisfy( q, dptr)) { return q; } } return NULL; } int SQMatchInternal(const NSL_Query_I *qu, const NSL_Item *item ); /* Match a query against an item */ /* Now allows alternatives */ int SQMatch(const NSL_Query_I *qu, const NSL_Item *item ) { debug(Stderr,"SQMatch(",")\n",qu,item->in); if( !item ){ LT_ERROR(NEARGNL,"null item in SQMatch\n"); return FALSE; } else if( SQMatchInternal(qu,item) ){ return TRUE; } else if( qu->alt ){ return SQMatch(qu->alt,item); } else { return FALSE; } } /* Match a query against an item (Internal) */ /* This function does not look at alternatives */ int SQMatchInternal(const NSL_Query_I *qu, const NSL_Item *item ) { if (qu->elname && ( ( qu->stringType == NSL_use_names && qu->elname!=item->label ) || ( qu->stringType == NSL_use_strings && Strcasecmp(qu->elname,item->label) ) ) ){ return FALSE; } if (qu->number>=0) { if ((!item->in) || item->in->ref!=qu->number) { return FALSE; } } if (qu->alist) { return SQAttr(qu, item); } return TRUE; } /* ============================================== */ /* Stuff for printing out queries (for debugging) */ /* ============================================== */ /* Print a parsed query back out again */ #define indent() for(i=0; i=", ">", "<=", "?", "!?" }; static void printAttr(const NSL_Q_Attr *at, int depth){ int i; if( at ){ indent(); Printf("%S %s ", at->name, OperatorNames[at->comparison]); if(at->comparison == _qa_comp_regexp || at->comparison == _qa_comp_not_regexp) Printf("%s\n", at->pattern.regexp); else Printf("%S\n", at->pattern.string); printAttr(at->next,depth); } } static const char* ItemTypeNames[7] = { "qu_norm", "qu_star", "qu_any", "qu_anystar", "qu_data", "qu_repeated", "qu_anyrepeated" }; void printQuery(const NSL_Query_I* q, int depth){ int i; printf("Query(%d):",(int)q); if( q ){ /*printf(" %s number=%d, type=%s focus=%d\n", q->elname?q->elname:"NULL", q->number, ItemTypeNames[q->type], q->focus);*/ Printf(" %S number=%d, type=%s \n", q->elname, q->number, ItemTypeNames[q->type]); depth += 3; printAttr(q->alist,depth); /*indent(); printf("text = '%s'\n",q->text?q->text:(Char *)"NULL");*/ indent(); Printf("alt = "); printQuery(q->alt,depth); indent(); Printf("child = "); printQuery(q->next,depth); /* child */ indent(); Printf( "parent = Query(%d)\n",(int)q->prev); /* parent */ /*indent(); printf("next = "); printQuery(q->next,depth); indent(); Printf("prev = Query(%d)\n",(int)q->prev);*/ } else { printf("NULL\n"); } } void printShortQuery(FILE16 *stream, const NSL_Query_I* q, boolean first){ const NSL_Q_Attr *attrl; if(q){ if( q->next && first ){ Fprintf(stream, "(");} Fprintf(stream, "%S",q->elname); if( q->number > -1 || q->alist ){ Fprintf(stream, "["); if( q->number > -1 ){ Fprintf(stream, " %d ",q->number); } for( attrl = q->alist; attrl; attrl = attrl->next){ Fprintf(stream, "%S %s ", attrl->name, OperatorNames[attrl->comparison]); if(attrl->comparison == _qa_comp_regexp || attrl->comparison == _qa_comp_not_regexp) Fprintf(stream, "%s ", attrl->pattern.regexp); else Fprintf(stream, "%S ", attrl->pattern.string); } Fprintf(stream, "]"); } /* if( q->text ){ fprintf(stream,"'%s'",q->text); } */ if( q->alt ){ Fprintf(stream, "|"); printShortQuery(stream,q->alt,FALSE); } switch( q->type ){ case _qu_star: case _qu_anystar: Fprintf(stream,"*"); break; /*case _qu_repeated: case _qu_anyrepeated: Fprintf(stream,"&"); break; */ default: break; } /* if( q->focus ){ fprintf(stream,"!");} */ if( q->next ){ /* child */ Fprintf(stream,"/"); printShortQuery(stream,q->next,TRUE); } /* if( q->next ){ fprintf(stream,","); printShortQuery(stream,q->next,FALSE); if( first ){ fprintf(stream,")"); } } */ } } static void debug(FILE16 *stream, const char *before, const char *after, const NSL_Query_I* q, const NSL_Data *data){ Char hash[] = {'#', 0}; if( debugFlag ){ Fprintf(stream,"%squery(%d)=",before,(int)q); printShortQuery(stream,q,FALSE); Fprintf(stream,", data(%d)=",(int)data); if( data ){ Fprintf(stream,"%S[%d]%s", (data->type==NSL_text_data? hash:((NSL_Item*)data->first)->label), data->ref, after); }else{ Fprintf(stream,"NULL%s",after); } } }