/*
 *========================================================================
 * $Id: xml_xtract.c 96 2006-07-20 17:56:16Z rgb $
 *
 * See copyright in copyright.h and the accompanying file COPYING
 *========================================================================
 */

#include <wulfware/libwulf.h>

/*
 *========================================================================
 * To add a new value to be parsed, add it to values.h (three times,
 * generally -- the Values struct, the enum, the path vector) and add
 * a single call to xtract(int type, void *dest, char *xpath) to 
 * fill_values(), possibly after building an xpath "by hand" (to match up 
 * e.g. specific id attributes).  Each xpath passed MUST be unique in the 
 * tree -- if it is not xtracts return the number of entries and a NULL 
 * value in the dest ptr.  Note that xtract does any requisite conversion
 * for you but you MUST pass the type explicitly.  Supported types are
 * currently CHAR, STRING, INT, FLOAT, DOUBLE, and MUST match the type
 * of the void ptr passed for the return value.
 *========================================================================
 */

/*
 *========================================================================
 * int xtract(int type, void *dest, char *xpath, xmlXPathContextPtr xp_doc)
 *========================================================================
 */
int xtract(int type, void *dest, char *xpath, xmlXPathContextPtr xp_doc)
{

 /* 
  * Number of matches found for the tag.  If numvals = 1, we
  * will extract and return the contents of the tag in *dest, suitably
  * converted to the appropriate type.  In any other case we return a
  * NULL or zero in the *dest pointer.  In all cases the value of the
  * return is numvals.
  *
  * This facilitates two kinds of usage.  xtract can be used to count
  * e.g. the number of cpus or interfaces (or any of the tags that
  * occur more than once with different id attributes in the xmlsysd
  * message).  It is also used to extract the CONTENTS of any UNIQUELY
  * specified tag.  The caller should always check to be sure that the
  * return is 1 in this case, indicating that it found exactly one tag
  * from which to obtain the value.
  *
  * Bad Things (tm) will happen if type doesn't exactly specify the
  * actual type of the void *dest pointer.  You have been warned.
  */
 int numvals = 0;

 long dummy;
 
 /* xml (tree) node pointer, used to extract actual value */
 xmlNodePtr cur;
 /* xml (xpath) object pointer) */
 xmlXPathCompExprPtr comp;
 xmlXPathObjectPtr xp_op;

 /* 
  * Conversion pointers for the null dest pointer (inline casts don't 
  * seem to work in return()) .
  */
 char *cptr;
 int *iptr;
 long *liptr;
 unsigned long *uliptr;
 unsigned long long *ulliptr;
 float *fptr;
 double *dptr;

 if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
   fprintf(OUTFP,"D_EXTRACT_VALUES: xtract(%d,%x,%s,%x)\n",type,dest,xpath,xp_doc);
   fprintf(OUTFP,"D_EXTRACT_VALUES. xtract(): Use -v %d to focus.\n",D_EXTRACT_VALUES);
 }

 /*
  * Preload the return defaults -- all null.
  */
 switch(type) {
   case STRING:
     cptr = (char *) dest;
     strncpy(cptr,"",K);
     break;
   case CHAR:
     cptr = (char *) dest;
     *cptr = (char) 0;
     break;
   case INT:
     iptr = (int *) dest;
     *iptr = 0;
     break;
   case LONG:
     liptr = (long int *) dest;
     *liptr = 0;
     break;
   case UNSIGNED_LONG:
     uliptr = (unsigned long *) dest;
     *uliptr = 0;
     break;
   case UNSIGNED_LONG_LONG:
     ulliptr = (unsigned long long *) dest;
     *ulliptr = 0;
     break;
   case FLOAT:
     fptr = (float *) dest;
     *fptr = 0.0;
     break;
   case DOUBLE:
     dptr = (double *) dest;
     *dptr = 0.0;
     break;
 }

 /*
  * We start by using xpath to obtain a pointer to the node specified by
  * the path.
  */
 xp_op = xmlXPathEval((xmlChar*) xpath, xp_doc);

 /*
  * This is a bit odd, but I got it directly from the horse's mouth.
  * If xp_op is null, the path eval encountered a real error and no
  * xpath object was allocated.  From what I could tell this is a real
  * problem/bug and should never happen if xp_doc exists -- even a garbage
  * xpath will cause xp_op to be created with an empty nodelist.
  * We should therefore return null (or just plain quit) and should NOT
  * free xp_op since it was never allocated.
  */
 if( xp_op == NULL ) {
   fprintf(stderr,"D_EXTRACT_VALUES: Error: xp_doc broken!  Returning NULL.\n");
   return numvals;
 }
 
 /*
  * If we get here, xp_op was allocated (is not null).  HOWEVER, the xpath
  * we tried to match may not have existed in xp_doc.  In that case the
  * nodeset array will be empty.  Result:  We return 0 for numvals (and
  * a null for the value).  We do have to free the xp_op.
  */
 if(xp_op->nodesetval == NULL) {
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_EXTRACT_VALUES: xpath %s not found!  Returning NULL.\n",xpath);
   }
   xmlXPathFreeObject(xp_op);
   return numvals;
 }

 /*
  * It's not clear whether this one should go before or after the one
  * above.  There are other types that xp_op could have, but in order to
  * extract a value from a set of nodes, it has better be a nodeset.
  */
 if( xp_op->type != XPATH_NODESET ){
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_EXTRACT_VALUES: xpath type = %d is not a nodeset!  Returning NULL.\n",xp_op->type);
   }
   /* Free the path objects so we don't leak */
   xmlXPathFreeObject(xp_op);
   return numvals;
 }

 /* 
  * This is the number of matches to the path above in the list -- the
  * number of xpath nodes found in xp_doc.
  */
 numvals = xp_op->nodesetval->nodeNr;

 /* if type is COUNT, we're done -- who cares about content? */
 if(type == COUNT) {
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_EXTRACT_VALUES: Found %d nodes for path %s.\n",numvals,xpath);
   }
   /* Free path object */
   xmlXPathFreeObject(xp_op);
   return numvals;
 }

 /* Check to make sure we got at least one node */
 if(numvals <= 0){
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_XMLTRACT: xpath contains no nodes in the set!.\n");
   }
   /* Free path object */
   xmlXPathFreeObject(xp_op);
   return numvals = 0;
 }

 /* If we got more than one, return the count */
 if(numvals > 1){
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_XMLTRACT: xpath contains %d nodes, returning count!.\n",numvals);
   }
   /* Free path object */
   xmlXPathFreeObject(xp_op);
   return numvals;
 }

 /*
  * If we get here, the only possibility is that xp_op contains a
  * table of pointers to xmlDoc nodes with a single entry -- the 
  * uniquely specified node we are looking for.  Its CONTENT lives 
  * here (under cur->content of here, actually):
  */
 if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
   fprintf(OUTFP,"D_EXTRACT_VALUES: Found %d node for path %s.  About to extract value.\n",numvals,xpath);
 }
 cur = xp_op->nodesetval->nodeTab[0]->xmlChildrenNode;

 /*
  * I don't really know if this is an error or not.  I don't think that
  * it can happen if xp_op was successfully created and contains exactly
  * one node, but it is pretty harmless to check in case the xpath
  * evaluator didn't.
  */
 if( cur == NULL ){
   fprintf(stderr,"D_EXTRACT_VALUES Error: xtract() cur is NULL.\n");
   fprintf(stderr,"Yow!  This cannot happen.  Something is seriously broken.\n");
   exit(1);
 }
  
 /* 
  * At this point we've found the xml node, it is unique, we have a pointer
  * to it.  Its (xmlChar *) contents live in cur-content.  Piece of cake.
  * We now put it where it belongs in the correct format.
  */
 if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)) {
   fprintf(OUTFP,"D_EXTRACT_VALUES: extracting %s\n",cur->content);
 }
 switch(type) {
   /* I don't know but hope that sizeof works for null pointers */
   case STRING:
     strncpy(cptr,(char*) cur->content,K);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning string = \"%s\" in dest.\n",cptr);
     }
     break;
   case CHAR:
     *cptr = cur->content[0];
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning char = '%c' in dest.\n",*cptr);
     }
     break;
   case INT:
     *iptr = atoi((char*) cur->content);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning int = %d in dest.\n",*iptr);
     }
     break;
   case LONG:
     *liptr = strtol((char*) cur->content,(char **) NULL,10);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning long int = %ld in dest.\n",*liptr);
     }
     break;
   case UNSIGNED_LONG:
     *uliptr = strtoul((char*) cur->content,(char **) NULL,10);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning unsigned long int = %lu in dest.\n",*uliptr);
     }
     break;
   case UNSIGNED_LONG_LONG:
     *ulliptr = strtoull((char*) cur->content,(char **) NULL,10);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Converted string %s.\n",cur->content);
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning unsigned long long int = %llu in dest.\n",*ulliptr);
     }
     break;
   case FLOAT:
     *fptr = atof((char*) cur->content);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning float = %f in dest.\n",*fptr);
     }
     break;
   case DOUBLE:
     *dptr = atof((char*) cur->content);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning float = %f in dest.\n",*dptr);
     }
     break;
 }

 /* Finally free the object pointer as we're done */
 xmlXPathFreeObject(xp_op);
 return numvals;

}

/*
 *========================================================================
 * int xtract_attribute(int type, void *dest, char *xpath, 
 *                        char *attribute, xmlXPathContextPtr xp_doc)
 *========================================================================
 */
int xtract_attribute(int type, void *dest, char *xpath,
                       char *attribute,  xmlXPathContextPtr xp_doc)
{

 /* 
  * Number of matches found for the tag.  If numvals = 1, AND we find
  * the attribute string, we will will extract and return the contents 
  * of the attribute in *dest, suitably converted to the appropriate 
  * type.  In any other case we return a NULL or zero in the *dest 
  * pointer.  In all cases the value of the return is numvals.  Note
  * that for this routine to work at all, the xpath MUST be unique.
  * The caller should therefore check that the returned value is 1.
  *
  * It (like xtract() can still be used to count the number of entries
  * with a specified path, but I can't imagine why you'd want to as then
  * the attribute specifier would be moot.
  *
  * Bad Things (tm) will happen if type doesn't exactly specify the
  * actual type of the void *dest pointer.  You have been warned.
  */
 int numvals = 0;
 
 /* xml (tree) node pointer, used to extract actual value */
 xmlNodePtr cur;
 /* xml (xpath) object pointer) */
 xmlXPathObjectPtr xp_op;

 /* 
  * Conversion pointers for the null dest pointer (inline casts don't 
  * seem to work in return()) .
  */
 char *attribute_value;
 char *cptr;
 int *iptr;
 long *liptr;
 unsigned long *uliptr;
 unsigned long long *ulliptr;
 float *fptr;
 double *dptr;

 if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
   fprintf(OUTFP,"D_EXTRACT_VALUES: xtract_attribute(%d,%x,%s,%s,%x)\n",
              type,dest,xpath,attribute,xp_doc);
   fprintf(OUTFP,"D_EXTRACT_VALUES: xtract_attribute(). Use -v %d to focus.\n",D_EXTRACT_VALUES);
 }

 /*
  * Preload the return defaults -- all null.
  */
 switch(type) {
   case STRING:
     cptr = (char *) dest;
     strncpy(cptr,"",K);
     break;
   case CHAR:
     cptr = (char *) dest;
     *cptr = (char) 0;
     break;
   case INT:
     iptr = (int *) dest;
     *iptr = 0;
     break;
   case LONG:
     liptr = (long int *) dest;
     *liptr = 0;
     break;
   case UNSIGNED_LONG:
     uliptr = (unsigned long int *) dest;
     *uliptr = 0;
     break;
   case UNSIGNED_LONG_LONG:
     ulliptr = (unsigned long long int *) dest;
     *ulliptr = 0;
     break;
   case FLOAT:
     fptr = (float *) dest;
     *fptr = 0.0;
     break;
   case DOUBLE:
     dptr = (double *) dest;
     *dptr = 0.0;
     break;
 }

 /*
  * We start by using xpath to obtain a pointer to the node specified by
  * the path.
  */
 xp_op = xmlXPathEval((xmlChar*) xpath, xp_doc);

 /*
  * This is a bit odd, but I got it directly from the horse's mouth.
  * If xp_op is null, the path eval encountered a real error and no
  * xpath object was allocated.  From what I could tell this is a real
  * problem/bug and should never happen if xp_doc exists -- even a garbage
  * xpath will cause xp_op to be created with an empty nodelist.
  * We should therefore return null (or just plain quit) and should NOT
  * free xp_op since it was never allocated.
  */
 if( xp_op == NULL ) {
   fprintf(stderr,"D_EXTRACT_VALUES: Error: xp_doc broken!  Returning NULL.\n");
   return numvals;
 }
 
 /*
  * If we get here, xp_op was allocated (is not null).  HOWEVER, the xpath
  * we tried to match may not have existed in xp_doc.  In that case the
  * nodeset array will be empty.  Result:  We return 0 for numvals (and
  * a null for the value).  We do have to free the xp_op.
  */
 if(xp_op->nodesetval == NULL) {
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_EXTRACT_VALUES: xpath %s not found!  Returning NULL.\n",xpath);
   }
   xmlXPathFreeObject(xp_op);
   return numvals;
 }

 /*
  * It's not clear whether this one should go before or after the one
  * above.  There are other types that xp_op could have, but in order to
  * extract a value from a set of nodes, it has better be a nodeset.
  */
 if( xp_op->type != XPATH_NODESET ){
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_EXTRACT_VALUES: xpath type = %d is not a nodeset!  Returning NULL.\n",xp_op->type);
   }
   /* Free the path objects so we don't leak */
   xmlXPathFreeObject(xp_op);
   return numvals;
 }

 /* 
  * This is the number of matches to the path above in the list -- the
  * number of xpath nodes found in xp_doc.
  */
 numvals = xp_op->nodesetval->nodeNr;

 /* if type is COUNT, we're done -- who cares about content? */
 if(type == COUNT) {
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_XMLTRACT: xpath returning node count = %d.\n",numvals);
   }
   /* Free path object */
   xmlXPathFreeObject(xp_op);
   return numvals;
 }

 /* Make sure we got at least one node */
 if(numvals <= 0){
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_XMLTRACT: xpath contains no nodes in the set!.\n");
   }
   /* Free path object */
   xmlXPathFreeObject(xp_op);
   return numvals = 0;
 }

 /* If we got more than one, return the count (this is an error) */
 if(numvals > 1){
   if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
     fprintf(OUTFP,"D_XMLTRACT: xpath contains %d nodes, returning count!.\n",numvals);
   }
   /* Free path object */
   xmlXPathFreeObject(xp_op);
   return numvals;
 }

 /*
  * If we get here, the only possibility is that xp_op contains a
  * table of pointers to xmlDoc nodes with a single entry -- the 
  * uniquely specified node we are looking for.  It lives here:
  */
 cur = xp_op->nodesetval->nodeTab[0];

 /*
  * I don't really know if this is an error or not.  I don't think that
  * it can happen if xp_op was successfully created and contains exactly
  * one node, but it is pretty harmless to check in case the xpath
  * evaluator didn't.
  */
 if( cur == NULL ){
   fprintf(stderr,"D_EXTRACT_VALUES Error: xtract_attribute() cur is NULL.\n");
   fprintf(stderr,"Yow!  This cannot happen.  Something is seriously broken.\n");
   exit(1);
 }
  
 /* 
  * At this point we've found the xml node, it is unique, we have a pointer
  * to it.  All that remains is to extract the contents of the attribute
  * of the given name, if it exists.  Note that we have to free the memory
  * when done.
  */
 attribute_value = (char *)xmlGetProp(cur, (const xmlChar *) attribute);

 /*
  * Finally, we run through the usual ritual of converting this to the
  * desired type, freeing both xp_op and attribute_value, and returning.
  */
 if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
   fprintf(OUTFP,"D_EXTRACT_VALUES: extracting %s\n",attribute_value);
 }
 switch(type) {
   /* I don't know but hope that sizeof works for null pointers */
   case STRING:
     /*
      * strncpy barfs on a null pointer, which is what you get if the
      * attribute isn't present.
      */
     if(attribute_value != 0){
       strncpy(cptr,attribute_value,K);
     }
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning string = \"%s\" in dest.\n",cptr);
     }
     break;
   case CHAR:
     *cptr = attribute_value[0];
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning char = '%c' in dest.\n",*cptr);
     }
     break;
   case INT:
     *iptr = atoi(attribute_value);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning int = %d in dest.\n",*iptr);
     }
     break;
   case LONG:
     *liptr = strtol(attribute_value,(char **) NULL,10);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning long int = %d in dest.\n",*liptr);
     }
     break;
   case UNSIGNED_LONG:
     *uliptr = strtoul(attribute_value,(char **) NULL,10);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning unsigned long int = %lu in dest.\n",*uliptr);
     }
     break;
   case UNSIGNED_LONG_LONG:
     *ulliptr = strtoull(attribute_value,(char **) NULL,10);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning unsigned long long int = %llu in dest.\n",*ulliptr);
     }
     break;
   case FLOAT:
     *fptr = atof(attribute_value);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning float = %f in dest.\n",*fptr);
     }
     break;
   case DOUBLE:
     *dptr = atof(attribute_value);
     if((verbose == D_ALL) || (verbose == D_EXTRACT_VALUES)){
       fprintf(OUTFP,"D_EXTRACT_VALUES: Returning float = %f in dest.\n",*dptr);
     }
     break;
 }

 /* 
  * Finally free the object pointer and attribute_value as we're done 
  */
 
 free(attribute_value);
 xmlXPathFreeObject(xp_op);
 return numvals;

}


