
/***********************************************************************
*
*      Module  : SxPath
*      Fichier : SxPathExpr.c
*      Auteur  : J. Ducloy
*
*      Derniere mise a jour Decembre 2001
*      $Id: SxPathExpr.c,v 1.11 2007/01/22 08:40:25 parmentf Exp $
*
************************************************************************
* 
*     Copyleft Ducloy
*
************************************************************************/
/**
   @file

   @brief Functions for creating SxPathExpressions
   
   @author &copy; INIST-CNRS
   @author Jacques DUCLOY <DilibMaster@inist.fr>
 */

#include <ctype.h>
#include "SxPath.h"
#include "StrDict.h"
#include "Except.h"

/** Table associating axis (like "child", "attribute") to their code */
StrDict         *SxPathParserSystemTable=NULL;
SxmlParser      *SxPathSxmlParser=NULL;

/**
   Creates and fills the @ref SxPathParserSystemTable. This table associates
   axis (like "child", "attribute") to their code.

   <table>
   <tr><th>axis</th>             <th>value</tr>
   <tr><td>child</td>            <td>SXPATH_AXIS_CHILD</td></tr>
   <tr><td>attribute</td>        <td>SXPATH_AXIS_ATTRIBUTE</td></tr>
   <tr><td>childName</td>        <td>SXPATH_AXIS_CHILD_NAME</td></tr>
   <tr><td>descendant</td>       <td>SXPATH_AXIS_DESCENDANT</td></tr>
   <tr><td>following-sibling</td><td>SXPATH_AXIS_FOLLOWING_SIBLING</td></tr>
   <tr><td>locationPath</td>     <td>SXPATH_LOCATION_PATH</td></tr>
   <tr><td>union</td>            <td>SXPATH_OPERATOR_UNION</td></tr>
   <tr><td>testName</td>         <td>SXPATH_TEST_NAME</td></tr>
   <tr><td>position</td>         <td>SXPATH_PREDICATE_POSITION</td></tr>
   <tr><td>testAttribute</td>    <td>SXPATH_PREDICATE_ATTRIBUTE</td></tr>
   </table>
*/
void SxPathParserSystemTableCreate()
{
  SxPathParserSystemTable=NewStrDict();
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "child",               SXPATH_AXIS_CHILD);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "attribute",           SXPATH_AXIS_ATTRIBUTE);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "childName",           SXPATH_AXIS_CHILD_NAME);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "descendant",          SXPATH_AXIS_DESCENDANT);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "following-sibling",   SXPATH_AXIS_FOLLOWING_SIBLING);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "locationPath",        SXPATH_LOCATION_PATH);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "union",               SXPATH_OPERATOR_UNION);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "testName",	      SXPATH_TEST_NAME);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "position",	      SXPATH_PREDICATE_POSITION);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "testAttribute",	      SXPATH_PREDICATE_ATTRIBUTE);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "childPosition",       SXPATH_AXIS_CHILD_POSITION);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "relationalExpr",     SXPATH_RELATIONAL_EXPR);
  StrDictAddNewDatumChar(SxPathParserSystemTable,
	       "firstChildByTagName",       SXPATH_FIRST_CHILD_BY_TAG_NAME);
}

/**
   Recursively translates SxmlNode types from @c SXML_NODE_ELEMENT to @c
   SXML_NODE_SXPATH_EXPRESSION, and sets their subtypes from their
   name. */
SxmlNode *SxPathExpressionTranslate(SxmlNode *xp1)
{
  SxmlNode *x2;
  char *codeStr;
  if (!SxPathParserSystemTable)SxPathParserSystemTableCreate();
  if (SxmlNodeType(xp1)==SXML_NODE_ELEMENT)
    {

      if ((codeStr=StrDictSearch(SxPathParserSystemTable, SxmlNodeName(xp1))))
	{
	  SxmlSetNodeType(xp1,SXML_NODE_XPATH_EXPRESSION);
	  SxmlSetNodeSubType(xp1,*codeStr);
	}
    }
  SxmlReset(xp1);
  while((x2=SxmlNextNode(xp1))){SxPathExpressionTranslate(x2);}
  return xp1;    
}

/**
   Returns the first part of bufName of the SxmlParser @a xp.

   Stops when it encounters:
	space,>,=,?,/,[,(,\@,::

   The returned string must be used (or saved) before calling any
   other Dilib function.

   @param xp
   @returns the first part of bufName of @a xp (must be freed).
 */
char *SxmlParserGetSxPathName(SxmlParser *xp)
{
  int c;
  BufferReset(xp->bufName);

  while((c=*(xp->nextStr)))
    {
      switch(c)
	{
	case ' ':
	case '>':
	case '=':
	case '?':
	case '/':
	case '[':
	case '(':
	case '@':
	  return BufferString(xp->bufName);

	case ':':
	  if (*(xp->nextStr+1)==':')
	    {
	      return BufferString(xp->bufName);
	    }
	default:
	  BufferCharCat(xp->bufName,c);
	  xp->nextStr++;
	  break;
	}
    }
  if (BufferLen(xp->bufName)==0)return NULL;
  return BufferString(xp->bufName);
}

/**
  Considers the current context of the SxmlParser @a xp1, and adds into
  @a x1 the Sxml representation of the SxPath.

  When the current context begins with @c "*", it adds a child to @a
  x1, which is a node named @c "all".

  When the context contains a tag name, it adds a child to @a x1, which
  is an element @c "testName" containing the tag name as text.

  When the context contains a axis name, an element is built, which
  name is @c "test" followed by the name of the axis. In this case, the
  last @c "::" characters are skipped.

  @param xp1
  @param x1
  @returns @a x1
 */
SxmlNode *SxPathExpressionGetTestNode(SxmlParser *xp1,
				    SxmlNode   *x1)
{
  int nextChar;

  if (SxmlParserContextChar(xp1)=='*')
    {
      SxmlParserSkip1(xp1);
      SxmlAppendChild(x1,SxmlElementCreate("all"));
    }
  else if (isalpha((int)SxmlParserContextChar(xp1)))
    {
      char *name1;
 
      name1=SxmlParserGetSxPathName(xp1);
      nextChar=SxmlParserContextChar(xp1);
      if(nextChar=='(')
	{
	  static Buffer* bnam=NULL;
	  if(!bnam)bnam=BufferCreate(10,10);
	  BufferStrcpy(bnam,"test");
	  BufferCharCat(bnam,tolower(name1[0]));
	  BufferStrcat(bnam,name1+1);
	  SxmlAppendChild(x1,SxmlElementCreate(BufferString(bnam)));
	  SxmlParserSkip(xp1,2);
	}
      else
	{
	  SxmlAppendChild(x1,SxmlLeafCreate("testName",name1));
	}	
    }
  nextChar=SxmlParserContextChar(xp1);
  return x1;
}

/**
  Adds to @a xn1 the predicate(s?), should it be a number like in @c "[1]"
  or an attribute and its value, like in @c "[@att=value]".

  @param xp1
  @param xn1
  @returns the predicate(s?).
 */
SxmlNode *SxPathExpressionGetPredicates(SxmlParser *xp1,
				      SxmlNode   *xn1)
{
  char *str;
  SxmlNode *xl1;
  str=SxmlParserGetBetweenParenthesis(xp1,'[',']');
  if(isdigit((int)*str))
    {
      xl1=SxmlLeafCreate("position",BufferGetIntString(str));
      SxmlAppendChild(xn1,xl1);
      return xl1;
    }
  else if(isalpha((int)*str))
    {
      char *operator;
      int   numOperand;

      operator=strpbrk(str,"<>=");
      if (!operator)
	{
	  ExceptSetError("SxPathExpressionGetPredicate","NI"," predicate not yet implemented for name",
			 "","",2);
	  return NULL;
	}
      if (isdigit((int)operator[1]))
	{
	  numOperand=atoi(operator+1);
	}
      else
	{
	  ExceptSetError("SxPathExpressionGetPredicate","NI"," predicate not yet implemented for name",
			 "","",2);
	  return NULL;
	}
      xl1=SxmlElementCreate("relationalExpr");
      if (operator[0]=='<') SxmlSetAttribute(xl1, "operator", "lt");
      else SxmlSetAttribute(xl1, "operator", "gt");
      operator[0]='\0';
      SxmlAppendChild(xl1, SxmlLeafCreate("firstChildByTagName", str));
      SxmlAppendChild(xl1, SxmlLeafCreate("number", operator+1));
      SxmlAppendChild(xn1,xl1);
      
      return xl1;
    }
  else if (*str=='@')
    {
      SxmlParser *xp2;
      xp2=SxmlParserCreate();
      SxmlParserInit(xp2,str+1);
      xl1=SxmlParserGetAttribute(xp2,NULL);
      SxmlParserFree(xp2);
      SxmlAppendChild(xn1,xl1);
      return xl1;
    }
  ExceptSetError("SxPathExpressionGetPredicate","NI"," predicate not yet implemented",
		 "","",2);
  return NULL;
}

SxmlNode *SxPathExpressionGetDilibAttributes(SxmlParser *xp1,
				      SxmlNode   *xn1)
{
  char *attName;
  char *attVal;
  SxmlNode *xP;

  SxmlParserSkip1(xp1);
  attName=SxmlParserGetName(xp1);
  SxmlParserSkipBrk(xp1," =");
  attVal=SxmlParserGetTextUntilBrk(xp1,"/#@");
  xP=SxmlLeafCreate("testAttribute",attVal);
  SxmlSetAttribute(xP,"name",attName);
  SxmlAppendChild(xn1,xP);
  if (SxmlParserContextChar(xp1)=='@') return SxPathExpressionGetDilibAttributes(xp1, xn1);
  return xn1;
}

/**
 * Adds a locationPath to the @a xc1 locationPath SxmlNode given as a
 * parameter, and returns this SxmlNode. When @a xc1 is null, a new
 * SxmlNode is created and returned. If @a xp1 is null, returns null. The
 * locationPath is recursively built (until @a xp1 is empty).
 *
 * @param xp1
 * @param xc1
 * 
 */
SxmlNode *SxPathExpressionIterativeParse(SxmlParser *xp1,
				       SxmlNode   *xc1)
{
  SxmlNode *xn1;
  SxmlNode *xn2;
  if (SxmlParserNull(xp1))return NULL;

  if (!xc1)
    {
      xn2=SxmlElementCreate("locationPath");
    }
  else xn2=xc1;

  if (isalpha((int)SxmlParserContextChar(xp1)))
    {
      char *name1;
      int nextChar;
      name1=SxmlParserGetSxPathName(xp1);
      nextChar=SxmlParserContextChar(xp1);
      switch(nextChar)
	{
	case '\0':
	  xn1=SxmlElementCreate("child");
	  SxmlAppendChild(xn1,SxmlLeafCreate("testName",name1));
	  SxmlAppendChild(xn2,xn1);	
	  return (xn2);

	case '/':
	  xn1=SxmlElementCreate("child");
	  SxmlAppendChild(xn1,SxmlLeafCreate("testName",name1));
	  SxmlParserSkip1(xp1);
	  SxmlAppendChild(xn2,xn1);
	  SxPathExpressionIterativeParse(xp1,xn2);
	  return (xn2);

	case '[':   /* SxPath attributes */
	  xn1=SxmlElementCreate("child");
	  SxmlAppendChild(xn1,SxmlLeafCreate("testName",name1));
	  SxmlAppendChild(xn2,xn1);
	  SxPathExpressionGetPredicates(xp1,xn1);
	  if (SxmlParserContextChar(xp1)=='/')
	    {
	      SxmlParserSkip1(xp1);
	      SxPathExpressionIterativeParse(xp1,xn2);
	    }
	  return (xn2);
	  
	case '@':    /*   dilib attribute */
	  {
	    /*
	    char *attName;
	    char *attVal;
	    SxmlNode *xP;
	    */
	    xn1=SxmlElementCreate("child");
	    SxmlAppendChild(xn1,SxmlLeafCreate("testName",name1));
	    SxmlAppendChild(xn2,xn1);
	    SxPathExpressionGetDilibAttributes(xp1, xn1);
	    /*
	      SxmlParserSkip1(xp1);
	    attName=SxmlParserGetName(xp1);
	    SxmlParserSkipBrk(xp1," =");
	    attVal=SxmlParserGetTextUntilBrk(xp1,"/#@");
	    xP=SxmlLeafCreate("testAttribute",attVal);
	    SxmlSetAttribute(xP,"name",attName);
	    SxmlAppendChild(xn1,xP);
	    */
	  }
	  if (SxmlParserContextChar(xp1)=='/')
	    {
	      SxmlParserSkip1(xp1);
	      SxPathExpressionIterativeParse(xp1,xn2);
	    }
	  else if (SxmlParserContextChar(xp1)=='@')
	    {
	      SxPathExpressionIterativeParse(xp1,xn2);
	    }
	  
	  return (xn2);

	case ':':	/* axis :: */
	  if (SxmlParserContextStr(xp1)[1]!=':')
	    ExceptSetError("SxPathExpression","SY","syntax error at",
			   SxmlParserContextStr(xp1),"",2);
	  SxmlParserSkip(xp1,2);
	  /*#if _DILIB_TEST */
	  if(!SxPathParserSystemTable) SxPathParserSystemTableCreate();
	  if(!(StrDictSearch(SxPathParserSystemTable,name1)))
	    ExceptSetError("SxPathExpression","SY","SxPath axis",name1,
			   "not fit or not yet implemented",2);
          /*#endif*/
	  xn1=SxmlElementCreate(name1);
	  SxmlAppendChild(xn2,xn1);
	  SxPathExpressionGetTestNode(xp1,xn1);
	  if (SxmlParserContextChar(xp1)=='[')
	    {
	      SxPathExpressionGetPredicates(xp1,xn1);
	    }
	  if (SxmlParserContextChar(xp1)=='/')
	    {
	      SxmlParserSkip1(xp1);
	      SxPathExpressionIterativeParse(xp1,xn2);
	    }
	  return xn2;
	default:
	  ExceptSetError("SxmlExpressionParse","NI","not yet implemented in : ",
			  SxmlParserContextStr(xp1),"",2);
	}
    }
  if (isdigit((int)SxmlParserContextChar(xp1)))
    {
      char *int1;
      int nextChar;
      int1=SxmlParserGetInteger(xp1);
      xn1=SxmlLeafCreate("childPosition", int1);
      /*
      xn1=SxmlElementCreate("child");
      SxmlAppendChild(xn1,SxmlLeafCreate("position",int1));
      */
      SxmlAppendChild(xn2,xn1);	
      nextChar=SxmlParserContextChar(xp1);
      switch(nextChar)
	{
	case '\0':
	  return (xn2);
	case '/':
	  SxmlParserSkip1(xp1);
	  SxPathExpressionIterativeParse(xp1,xn2);
	  return (xn2);
	}
    }
  else ExceptSetError("SxmlExpressionParse","NI","not yet implemented, in :",SxmlParserContextStr(xp1),"",2);
  return NULL;
}

/**
 * Initializes the @a xp1 SxmlParser with the @a s1 C string, and uses
 * it to parse the string, returning an SXML tree that represents the
 * SxPath contained in the string.
 * 
 * @param xp1
 * @param s1
 */
SxmlNode *SxPathExpressionParse(SxmlParser *xp1,
			      char      *s1)
{
  SxmlNode *x1;
  SxmlParserInit(xp1,s1);
  x1=SxPathExpressionIterativeParse(xp1,NULL);
  return x1;
}

/**
 * Creates an @ref SxPathExpression, that is to say an SXML tree
 * representing the SxPath contained in @a s1. The returned
 * SxPathExpression must be freed with SxmlFree.
 *
 * @param s1
 * @see @ref xpath
 */
SxPathExpression *SxPathExpressionCreate(char *s1)
{
  SxmlNode *x1;

  if(!SxPathSxmlParser)SxPathSxmlParser=SxmlParserCreate();
  x1=SxPathExpressionParse(SxPathSxmlParser,s1);
  SxPathExpressionTranslate(x1);
  return x1;
}

/**
 * Adds a @c child element before the first child of the locationPath
 * translated from @a s1 if it's not a @c position, else, adds it
 * after the first @c position element. (perhaps to take records
 * -tabulations- into account)
 *
 * @param s1
 *
 * @remark It is used in SxmlCut.c, perhaps to take the root into account.
 */
SxPathExpression *SxPathSxmlParseForRecord(char *s1)
{
  SxmlNode *exp1;
  SxmlNode *step1;
  SxmlNode *step11;

  if(!SxPathSxmlParser)SxPathSxmlParser=SxmlParserCreate();
  exp1=SxPathExpressionParse(SxPathSxmlParser,s1);
  if ((step1=SxmlFirstChild(exp1))
      &&(SxmlNodeHasName(step1,"child"))
      &&(step11=SxmlFirstChild(step1))
      &&(SxmlNodeHasName(step11,"position")))
    {
      SxmlInsertAfter(step1, SxmlElementCreate("child"));
    } 
  else if ((step1=SxmlFirstChild(exp1))
	   &&(SxmlNodeHasName(step1,"childPosition")));
  else SxmlAddFirstChild(exp1, SxmlElementCreate("child"));
  return exp1;
}

SxPathExpression *SxPathExpressionUnionCreate()
{
  SxmlNode *e1;
  e1=SxmlElementCreate("union");
  SxPathExpressionTranslate(e1);
  return e1;
}
