// To Be Dones:
//    evalDiv and evalMod - divide by zero causes XPE_DIVIDEBYZERO to
//                          be thrown
//    evalVariable        - to be defined for farSlang globals?
//    evalId
//    namespace           - no namespace support in the DOM so none
//                          here either - for now
//    Unicode             - doesn't handle the entity form &#nn;#nn;
//    XP_ALLNAME
//

//*********************************************************************
// xpath.cpp
//
// Copyright 2003 Paul J. Medlock all rights reserved
//
//*********************************************************************
//
// Synopsis of the XPath Recommendation
//
// The following material is taken from the XML Path Language (XPath
// Version 1.0 W3C Recommendation 16 November 1999 at
//
//    http://www.w3.org/TR/xpath#section-Expressions
//
// The primary syntactic construct in XPath is the expression. An
// expression matches the production Expr. An expression is evaluated
// to yield an object, which has one of the following four basic types:
//    node-set (an unordered collection of nodes without duplicates)
//    boolean (true or false)
//    number (a floating-point number)
//    string (a sequence of UCS characters)
//
// Expression evaluation occurs with respect to a context. XSLT and
// XPointer specify how the context is determined for XPath expressions
// used in XSLT and XPointer respectively. The context consists of:
//   a node (the context node)
//   a pair of non-zero positive integers (the context position and the
//   context size)
//   a set of variable bindings
//   a function library
//   the set of namespace declarations in scope for the expression
//
// The context position is always less than or equal to the context
// size.
//
//---------------------------------------------------------------------
//
// Syntax
//
// [1]  LocationPath               RelativeLocationPath
//                                 | AbsoluteLocationPath
// [2]  AbsoluteLocationPath       '/' RelativeLocationPath?
//                                 | AbbreviatedAbsoluteLocationPath
// [3]  RelativeLocationPath       Step
//                                 | RelativeLocationPath '/' Step
//                                 | AbbreviatedRelativeLocationPath
// [4]  Step                       AxisSpecifier NodeTest Predicate*
//                                 | AbbreviatedStep
// [5]  AxisSpecifier              AxisName '::'
//                                 | AbbreviatedAxisSpecifier
// [6]  AxisName                   'ancestor'
//                                 | 'ancestor-or-self'
//                                 | 'attribute'
//                                 | 'child'
//                                 | 'descendant'
//                                 | 'descendant-or-self'
//                                 | 'following'
//                                 | 'following-sibling'
//                                 | 'namespace'
//                                 | 'parent'
//                                 | 'preceding'
//                                 | 'preceding-sibling'
//                                 | 'self'
// [7]  NodeTest                   NameTest
//                                 | NodeType '(' ')'
//                                 | 'processing-instruction'
//                                   '(' Literal ')'
// [8]  Predicate                  '[' PredicateExpr ']'
// [9]  PredicateExpr              Expr
// [10] AbbreviatedAbsoluteLocationPath
//                                 '//' RelativeLocationPath
// [11] AbbreviatedRelativeLocationPath
//                                 RelativeLocationPath '//' Step
// [12] AbbreviatedStep            '.'
//                                 | '..'
// [13] AbbreviatedAxisSpecifier   '@'?
// [14] Expr                       OrExpr
// [15] PrimaryExpr                VariableReference
//                                 | '(' Expr ')'
//                                 | Literal
//                                 | Number
//                                 | FunctionCall
// [16] FunctionCall               FunctionName
//                                   '(' ( Argument ( ',' Argument )* )? ')'
// [17] Argument                   Expr
// [18] UnionExpr                  PathExpr
//                                 | UnionExpr '|' PathExpr
// [19] PathExpr                   LocationPath
//                                 | FilterExpr
//                                 | FilterExpr '/' RelativeLocationPath
//                                 | FilterExpr '//' RelativeLocationPath
// [20] FilterExpr                 PrimaryExpr
//                                 | FilterExpr Predicate
// [21] OrExpr                     AndExpr
//                                 | OrExpr 'or' AndExpr
// [22] AndExpr                    EqualityExpr
//                                 | AndExpr 'and' EqualityExpr
// [23] EqualityExpr               RelationalExpr
//                                 | EqualityExpr '=' RelationalExpr
//                                 | EqualityExpr '!=' RelationalExpr
// [24] RelationalExpr             AdditiveExpr
//                                 | RelationalExpr '<' AdditiveExpr
//                                 | RelationalExpr '>' AdditiveExpr
//                                 | RelationalExpr '<=' AdditiveExpr
//                                 | RelationalExpr '>=' AdditiveExpr
// [25] AdditiveExpr               MultiplicativeExpr
//                                 | AdditiveExpr '+' MultiplicativeExpr
//                                 | AdditiveExpr '-' MultiplicativeExpr
// [26] MultiplicativeExpr         UnaryExpr
//                                 | MultiplicativeExpr MultiplyOperator UnaryExpr
//                                 | MultiplicativeExpr 'div' UnaryExpr
//                                 | MultiplicativeExpr 'mod' UnaryExpr
// [27] UnaryExpr                  UnionExpr
//                                 | '-' UnaryExpr
//
//---------------------------------------------------------------------
// Lexical Structure
//
// When tokenizing, the longest possible token is always returned.
//
// For readability, whitespace may be used in expressions even though
// not explicitly allowed by the grammar: ExprWhitespace may be freely
// added within patterns before or after any ExprToken.
//
// The following special tokenization rules must be applied in the
// order specified to disambiguate the ExprToken grammar:
//
//   * If there is a preceding token and the preceding token is not one
//     of @, ::, (, [, , or an Operator, then a * must be recognized as
//     a MultiplyOperator and an NCName must be recognized as an
//     OperatorName.
//
//   * If the character following an NCName (possibly after intervening
//     ExprWhitespace) is (, then the token must be recognized as a
//     NodeType or a FunctionName.
//
//   * If the two characters following an NCName (possibly after
//     intervening ExprWhitespace) are ::, then the token must be
//     recognized as an AxisName.
//
//   * Otherwise, the token must not be recognized as a
//     MultiplyOperator, an OperatorName, a NodeType, a FunctionName,
//     or an AxisName.
//
//
// [28] ExprToken                  '(' | ')' | '[' | ']' | '.' | '..'
//                                 | '@' | ',' | '::'
//                                 | NameTest
//                                 | NodeType
//                                 | Operator
//                                 | FunctionName
//                                 | AxisName
//                                 | Literal
//                                 | Number
//                                 | VariableReference
// [29] Literal                    '"' [^"]* '"'
//                                 | "'" [^']* "'"
// [30] Number                     Digits ('.' Digits?)?
//                                 | '.' Digits
// [31] Digits                     [0-9]+
// [32] Operator                   OperatorName
//                                 | MultiplyOperator
//                                 | '/' | '//' | '|' | '+' | '-' | '='
//                                 | '!=' | '<' | '<=' | '>' | '>='
// [33] OperatorName               'and' | 'or' | 'mod' | 'div'
// [34] MultiplyOperator           '*'
// [35] FunctionName               QName - NodeType
// [36] VariableReference          '$' QName
// [37] NameTest                   '*'
//                                 | NCName ':' '*'
//                                 | QName
// [38] NodeType                   'comment'
//                                 | 'text'
//                                 | 'processing-instruction'
//                                 | 'node'
// [39] ExprWhitespace             S (space)
//
// Boolean Functions
//
//    Function: boolean boolean(object)
//    Function: boolean not(boolean)
//    Function: boolean true()
//    Function: boolean false()
//    Function: boolean lang(string)
//
// Number Functions
//
//    Function: number number(object?)
//    Function: number sum(node-set)
//    Function: number floor(number)
//    Function: number ceiling(number)
//    Function: number round(number)
//
// String Functions
//
//    Function: string string(object?)
//    Function: string concat(string, string, string*)
//    Function: boolean starts-with(string, string)
//    Function: boolean contains(string, string)
//    Function: string substring-before(string, string)
//    Function: string substring-after(string, string)
//    Function: string substring(string, number, number?)
//    Function: number string-length(string?)
//    Function: string normalize-space(string?)
//    Function: string translate(string, string, string)
//
// Node Set Functions
//
//    Function: number last()
//    Function: number position()
//    Function: number count(node-set)
//    Function: node-set id(object)
//    Function: string local-name(node-set?)
//    Function: string namespace-uri(node-set?)
//    Function: string name(node-set?)
//
//*********************************************************************
//
// Implementation notes:
//
// This XPath implementation operates in two phases:
//
//    1. Parse the input expression, validating and preparing it for
//       input into phase 2.
//
//    2. Evaluate the parsed expression against the specified node.
//
// The Parser
//
// The parser applies the XPath production rules against the input
// expression to output symbols into an intermediate buffer. It mixes
// postfix notation with sequential notation, depending on the applied
// production rule; viz., location path parts are output in the order
// they are received while other expressions, such as binary operators
// and funtions and their parameters, are output in postfix order.
//
// The parser prepends each output symbol with a special character
// (XP_ALL to XP_VARIABLE listed below) to indicate the type of the
// symbol. Errors in the input expression cause the parser to throw an
// error code (see errs.h) back to the user.
//
// The parser generates special context-stack unwind operator symbols
// at the end of each LocationPath in the input XPath expression. The
// unwinder operator specifies the number of items to pop from the
// context stack to get it back to a correct state for the context of
// the LocationPath.
//
// The parser uses the outputPostfix() / method to output symbols. This
// method prepends a space character before each output symbol to make
// it easy for the evaluator to tokenize.
//
// The Evaluator
//
// The evaluator maintains two stacks:
//
//    1. The evaluation stack - contains the results of the evaluation.
//
//    2. The context stack - presents the context nodes and their
//       associated information.
//
// The results of operations are pushed onto the evaluation stack, and
// removed as needed by other operations. Because some functions allow
// an arbitrary number of arguments, the left parenthesis is pushed
// onto the stack before arguments are computed so that the function
// can verify the number of arguments it actually receives by popping
// the evaluation stack down to the left paranthesis.
//
// Each of the three user interface methods (SelectSingleNode,
// SelectNodes, and Evaluate) prepare the context stack by pushing the
// specified node onto it. The Root operator (first /) pushes the
// document root node onto the context stack, while the Path operator
// (/) pops the top of the evaluation stack and pushes that value onto
// the context stack. (It's an error if the top of the evaluation stack
// is not a nodeSet.) The evalPredicate() and the stringValue() methods
// also push a node onto the context stack before using the eval()
// method for their special purposes. The Unwind operator along with
// the evalPredicate(), and the stringValue() methods pop items off the
// context stack.
//
//*********************************************************************

#include "math.h"
#include "prefixb.h"
#include "common.h"
#include "domtree.h"
#include "errs.h"
#include "regx.h"
#include "xpath.h"

//---------------------------------------------------------------------
//
const uchar XP_IGNORE         = '%';
const uchar XP_NOTKNOWN       =   0;
const uchar XP_EMPTY          =   1;

const uchar XP_ALL            = 128;
const uchar XP_ALLNAME        = 129;
const uchar XP_AND            = 130;
const uchar XP_ATTR           = 131;
const uchar XP_AXISNAME       = 132;
const uchar XP_COLON          = 133;
const uchar XP_COMMA          = 134;
const uchar XP_DBLCOLON       = 135;
const uchar XP_DBLDOT         = 136;
const uchar XP_DBLPATH        = 137;
const uchar XP_DIV            = 138;
const uchar XP_DOT            = 139;
const uchar XP_EQUAL          = 140;
const uchar XP_FUNNAME        = 141;
const uchar XP_GREATEROREQUAL = 142;
const uchar XP_GREATERTHAN    = 143;
const uchar XP_LBOX           = 144;
const uchar XP_LESSOREQUAL    = 145;
const uchar XP_LESSTHAN       = 146;
const uchar XP_LPAREN         = 147;
const uchar XP_MINUS          = 148;
const uchar XP_MOD            = 149;
const uchar XP_NODETYPE       = 150;
const uchar XP_MUL            = 151;
const uchar XP_NOT            = 152;
const uchar XP_NOTEQUAL       = 153;
const uchar XP_OR             = 154;
const uchar XP_PARSEERROR     = 155;
const uchar XP_PATH           = 156;
const uchar XP_PLUS           = 157;
const uchar XP_QNAME          = 158;
const uchar XP_QUOTEDSTR      = 159;
const uchar XP_RBOX           = 160;
const uchar XP_ROOT           = 161;
const uchar XP_RPAREN         = 162;
const uchar XP_SPACE          = 163;
const uchar XP_UNARYMINUS     = 164;
const uchar XP_UNION          = 165;
const uchar XP_UNWIND         = 166;
const uchar XP_VARIABLE       = 167;

#define _ROUND(a) (floor(a+0.5))

//=====================================================================
// CXPath Implementation
//=====================================================================

//---------------------------------------------------------------------
//
CXPath::CXPath(void):
   CAtom     (),
   stoken    (0),
   op        (XP_NOTKNOWN),
   ch        (spacer),
   iExp      (0),
   extend    (false),
   atEnd     (false),
   isOp      (false),
   wasOp     (false),
   lop       (XP_NOTKNOWN),
   inFixExp  (0),
   pInFixExp (0),
   postFixExp(0) {

   ctxtStack = new CXPathStack();
   evalStack = new CXPathStack();
   } // CXPath

//---------------------------------------------------------------------
//
CXPath::~CXPath() {
   delete[] stoken;
   stoken = 0;
   delete[] inFixExp;
   inFixExp = 0;
   delete[] postFixExp;
   postFixExp = 0;
   } // ~CXPath

//---------------------------------------------------------------------
//
RTTI_CPP(XPath, Atom)

//---------------------------------------------------------------------
//
PCNode CXPath::SelectSingleNode(RPCNode _node,
                                Pchar   _path,
                                bool    _extend) {
   extend = _extend;

   if (!_node                 ||
       (!_node->Isa(iDocument) &&
        !_node->Isa(iElement))) {
      err = XPE_NODEEXPECTED;
      return 0;
      }

   node = _node;

//?if (node->Isa(iDocument))
//?   node = TYPECAST(node, Document)->DocumentElement();

   // Prepare the context stack
   ctxtStack->Init();
   PCXPathItem item = new CXPathItem(node);
   item->node       = node;
   item->position   = 1;
   item->size       = 1;
   ctxtStack->PushX(item);

   // and the evaluation stack
   evalStack->Init();

   // Parse the XPath expression
   char work[sizeString];
   Strcpy(work, Parse(_path), sizeString);
   if (err)
      return 0;

   // Evaluate the parsed expression
   try {
      eval(work);
      }
   catch (ulong _err) {
      err = _err;
      return 0;
      }

   // Make sure the evaluator used up everything on the stack
   if      (evalStack->Number() != 1) {
      err = XPE_STACKNOTEMPTY;
      return 0;
      }

   // Return the item at TOS
   item = evalStack->Pop();

   // Make sure that the result of the evaluation is a node list
   if (item->type != XP_NODESET) {
      err = XPE_NODESETEXPECTED;
      return 0;
      }

   // Check that there is at least one node to return
   if (item->nValue->Length() == 0) {
      err = XPE_EMPTYRESULT;
      return 0;
      }

   node = item->nValue->Item(0);
   return node;
   } // SelectSingleNode

//---------------------------------------------------------------------
//
PCNodeList CXPath::SelectNodes(RPCNode _node, Pchar _path) {
   extend = false;

   if (!_node                 ||
       (!_node->Isa(iDocument) &&
        !_node->Isa(iElement))) {
      err = XPE_NODEEXPECTED;
      return 0;
      }

   node = _node;

//?if (node->Isa(iDocument))
//?   node = TYPECAST(node, Document)->DocumentElement();

   // Prepare the context stack
   ctxtStack->Init();
   PCXPathItem item = new CXPathItem(node);
   item->node       = node;
   item->position   = 1;
   item->size       = 1;
   ctxtStack->PushX(item);

   // and the evaluation stack
   evalStack->Init();

   // Parse the XPath expression
   char work[sizeString];
   Strcpy(work, Parse(_path), sizeString);
   if (err)
      return 0;

   // Evaluate the parsed expression
   try {
      eval(work);
      }
   catch (ulong _err) {
      err = _err;
      return 0;
      }

   // Make sure the evaluator used up everything on the stack
   if      (evalStack->Number() != 1) {
      err = XPE_STACKNOTEMPTY;
      return 0;
      }

   // Return the item at TOS
   item = evalStack->Pop();

   // Make sure that the result of the evaluation is a node list
   if (item->type != XP_NODESET) {
      err = XPE_NODESETEXPECTED;
      return 0;
      }

   return item->nValue;
   } // SelectNodes

//---------------------------------------------------------------------
//
PCXPathItem CXPath::Evaluate(RPCNode _node, Pchar _path) {
   extend = false;
   if (!_node                 ||
       (!_node->Isa(iDocument) &&
        !_node->Isa(iElement))) {
      err = XPE_NODEEXPECTED;
      return 0;
      }

   node = _node;

//?if (node->Isa(iDocument))
//?   node = TYPECAST(node, Document)->DocumentElement();

   // Prepare the context stack
   ctxtStack->Init();
   PCXPathItem item = new CXPathItem(node);
   item->node       = node;
   item->position   = 1;
   item->size       = 1;
   ctxtStack->PushX(item);

   // and the evaluation stack
   evalStack->Init();

   // Parse the XPath expression
   char work[sizeString];
   Strcpy(work, Parse(_path), sizeString);
   if (err)
      return 0;

   // Evaluate the parsed XPath expression
   try {
      eval(work);
      }
   catch (ulong _err) {
      err = _err;
      }

   // Make sure the evaluator used up everything on the stack
   if      (evalStack->Number() != 1) {
      err = XPE_STACKNOTEMPTY;
      return 0;
      }

   // Return the item at TOS
   item = evalStack->Pop();
   item->clear();

   return item;
   } // Evaluate

//=====================================================================
// Evaluator
//=====================================================================

//---------------------------------------------------------------------
//
void CXPath::eval(Pchar _postFixExp) {
   initEvalTokenizer(_postFixExp);
   getEvalToken();
   while (op != XP_EMPTY)
      switch (op) {
         case XP_IGNORE:       // ignore tokens beginning with a %-sign
            break;               // they are present for debugging only
         case XP_ALL:
            throw(XPE_OPNOTEXPECTED);
         case XP_ALLNAME:
            throw(XPE_OPNOTEXPECTED);
         case XP_AND:
            evalAnd();
            break;
         case XP_ATTR:
            throw(XPE_OPNOTEXPECTED);
         case XP_AXISNAME:
            evalStep();
            break;
         case XP_COLON:
            throw(XPE_OPNOTEXPECTED);
         case XP_COMMA:
            throw(XPE_OPNOTEXPECTED);
         case XP_DBLCOLON:
            throw(XPE_OPNOTEXPECTED);
         case XP_DBLDOT:
            throw(XPE_OPNOTEXPECTED);
         case XP_DBLPATH:
            throw(XPE_OPNOTEXPECTED);
         case XP_DIV:
            evalDiv();
            break;
         case XP_DOT:
            throw(XPE_OPNOTEXPECTED);
         case XP_EQUAL:
            evalEQ();
            break;
         case XP_FUNNAME:
            evalFunction();
            break;
         case XP_GREATEROREQUAL:
            evalGTE();
            break;
         case XP_GREATERTHAN:
            evalGT();
            break;
         case XP_LBOX:
            evalLBox();
            break;
         case XP_LESSOREQUAL:
            evalLTE();
            break;
         case XP_LESSTHAN:
            evalLT();
            break;
         case XP_LPAREN:
            evalStack->PushL();
            getEvalToken();
            break;
         case XP_MINUS:
            evalMinus();
            break;
         case XP_MOD:
            evalMod();
            break;
         case XP_MUL:
            evalMul();
            break;
         case XP_NODETYPE:
            throw(XPE_OPNOTEXPECTED);
         case XP_NOT:
            evalNegate();
            break;
         case XP_NOTEQUAL:
            evalNE();
            break;
         case XP_NUMBER:
            evalNmbr();
            break;
         case XP_OR:
            evalOr();
            break;
         case XP_PATH:
            evalPath();
            break;
         case XP_PLUS:
            evalPlus();
            break;
         case XP_QNAME:
            evalStack->PushS(stoken);
            getEvalToken();
            break;
         case XP_QUOTEDSTR:
            evalQuotedString();
            break;
         case XP_RBOX:
            throw(XPE_OPNOTEXPECTED);
         case XP_ROOT:
            evalRoot();
            break;
         case XP_RPAREN:
            throw(XPE_OPNOTEXPECTED);
         case XP_UNARYMINUS:
            evalUnaryMinus();
            break;
         case XP_UNION:
            evalUnion();
            break;
         case XP_UNWIND:
            evalUnwind();
            break;
         case XP_VARIABLE:
            evalVariable();
            break;
         default:
            evalStack->PushS(stoken);
            getEvalToken();
         }
   } // eval

//---------------------------------------------------------------------
// Evaluate a and b
//
void CXPath::evalAnd(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack and convert it to boolean
   PCXPathItem b = convertToBoolean(evalStack->Pop());

   // Pop a off the stack and convert it to boolean
   PCXPathItem a  = convertToBoolean(evalStack->Pop());

   // Push (a and b) onto the stack
   evalStack->PushB(a->bValue & b->bValue);

   // Get the next token
   getEvalToken();
   } // evalAnd

//---------------------------------------------------------------------
// Evaluate a / b
//
void CXPath::evalDiv(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack and convert it to numeric
   PCXPathItem b = convertToNumber(evalStack->Pop());

   // Pop a off the stack and convert it to numeric
   PCXPathItem a  = convertToNumber(evalStack->Pop());

   if (b->dValue == 0)
      throw(XPE_DIVIDEBYZERO);

   // Push (a div b) onto the stack
   evalStack->PushD(a->dValue / b->dValue);

   // Get the next token
   getEvalToken();
   } // evalDiv

//---------------------------------------------------------------------
// Evaluate a = b
//
void CXPath::evalEQ(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack
   PCXPathItem b = evalStack->Pop();

   // Pop a off the stack
   PCXPathItem a = evalStack->Pop();

   // Compare the operands according to their type
   if      (a->type == XP_NODESET)
      if      (b->type == XP_NODESET)
         evalEQ_NDS_NDS(a, b);
      else if (b->type == XP_STRING)
         evalEQ_NDS_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalEQ_NDS_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalEQ_NDS_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else if (a->type == XP_STRING)
      if      (b->type == XP_NODESET)
         evalEQ_NDS_STR(b, a);
      else if (b->type == XP_STRING)
         evalEQ_STR_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalEQ_STR_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalEQ_STR_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else if (a->type == XP_NUMBER)
      if      (b->type == XP_NODESET)
         evalEQ_NDS_DBL(b, a);
      else if (b->type == XP_STRING)
         evalEQ_STR_DBL(b, a);
      else if (b->type == XP_NUMBER)
         evalEQ_DBL_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalEQ_DBL_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else if (a->type == XP_BOOLEAN)
      if      (b->type == XP_NODESET)
         evalEQ_NDS_BOL(b, a);
      else if (b->type == XP_STRING)
         evalEQ_STR_BOL(b, a);
      else if (b->type == XP_NUMBER)
         evalEQ_DBL_BOL(b, a);
      else if (b->type == XP_BOOLEAN)
         evalEQ_BOL_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else
      throw(XPE_BADOPERANDTYPE);

   // Push (a = b) onto the stack

   // Get the next token
   getEvalToken();
   } // evalEQ

//---------------------------------------------------------------------
//
void CXPath::evalFunction(void) {
   // Boolean functions
   if      (strcmp(stoken+1, "boolean")          == 0)
      evalBoolean();
   else if (strcmp(stoken+1, "not")              == 0)
      evalNot();
   else if (strcmp(stoken+1, "true")             == 0)
      evalTrue();
   else if (strcmp(stoken+1, "false")            == 0)
      evalFalse();

   // Number functions
   else if (strcmp(stoken+1, "number")           == 0)
      evalNumber();
   else if (strcmp(stoken+1, "sum")              == 0)
      evalSum();
   else if (strcmp(stoken+1, "floor")            == 0)
      evalFloor();
   else if (strcmp(stoken+1, "ceiling")          == 0)
      evalCeiling();
   else if (strcmp(stoken+1, "round")            == 0)
      evalRound();

   // String functions
   else if (strcmp(stoken+1, "string")           == 0)
      evalString();
   else if (strcmp(stoken+1, "concat")           == 0)
      evalConcat();
   else if (strcmp(stoken+1, "starts-with")      == 0)
      evalStarts_with();
   else if (strcmp(stoken+1, "contains")         == 0)
      evalContains();
   else if (strcmp(stoken+1, "substring-before") == 0)
      evalSubstring_before();
   else if (strcmp(stoken+1, "substring-after")  == 0)
      evalSubstring_after();
   else if (strcmp(stoken+1, "substring")        == 0)
      evalSubstring();
   else if (strcmp(stoken+1, "string-length")    == 0)
      evalString_length();
   else if (strcmp(stoken+1, "normalize-space")  == 0)
      evalNormalize_space();
   else if (strcmp(stoken+1, "translate")        == 0)
      evalTranslate();

   // Regular expression functions
   else if (strcmp(stoken+1, "match")            == 0)
      evalMatch();
//? TBD
//?else if (strcmp(stoken+1, "replace")          == 0)
//?   evalReplace();
//?else if (strcmp(stoken+1, "tokenize")         == 0)
//?   evalTokenize();

   // Nodeset functions
   else if (strcmp(stoken+1, "last")             == 0)
      evalLast();
   else if (strcmp(stoken+1, "position")         == 0)
      evalPosition();
   else if (strcmp(stoken+1, "count")            == 0)
      evalCount();
   else if (strcmp(stoken+1, "id")               == 0)
      evalId();
   else if (strcmp(stoken+1, "local-name")       == 0)
      evalLocal_Name();
   else if (strcmp(stoken+1, "name")             == 0)
      evalName();
   else if (strcmp(stoken+1, "namespace-uri")    == 0)
      evalNamespace_uri();

   else
      throw(XPE_FUNCTIONUNKNOWN);

   // Get the next token
   getEvalToken();
   } // evalFunction

//---------------------------------------------------------------------
// Evaluate a >= b
//
void CXPath::evalGTE(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack
   PCXPathItem b = evalStack->Pop();

   // Pop a off the stack
   PCXPathItem a = evalStack->Pop();

   // Compare the operands according to their type
   if      (a->type == XP_NODESET)
      if      (b->type == XP_NODESET)
         evalGTE_NDS_NDS(a, b);
      else if (b->type == XP_STRING)
         evalGTE_NDS_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalGTE_NDS_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalGTE_NDS_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else if (a->type == XP_STRING)
      if      (b->type == XP_NODESET)
         evalGTE_STR_NDS(a, b);
      else if (b->type == XP_STRING)
         evalGTE_STR_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalGTE_STR_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalGTE_STR_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else if (a->type == XP_NUMBER)
      if      (b->type == XP_NODESET)
         evalGTE_DBL_NDS(a, b);
      else if (b->type == XP_STRING)
         evalGTE_DBL_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalGTE_DBL_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalGTE_DBL_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else if (a->type == XP_BOOLEAN)
      if      (b->type == XP_NODESET)
         evalGTE_BOL_NDS(a, b);
      else if (b->type == XP_STRING)
         evalGTE_BOL_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalGTE_BOL_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalGTE_BOL_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else
      throw(XPE_BADOPERANDTYPE);

   // Push (a = b) onto the stack

   // Get the next token
   getEvalToken();
   } // evalGTE

//---------------------------------------------------------------------
// Evaluate a > b
//
void CXPath::evalGT(void) {
   evalLTE();

   // Reverse the sense of the result
   evalStack->SetTOS(!evalStack->BoolAtTOS());

   // Get the next token
   getEvalToken();
   } // evalGT

//---------------------------------------------------------------------
//
void CXPath::evalLBox(void) {
   // Copy the parsed predicate into separate string objects, adding
   // each to my predicate list. Include nested predicates
   PCStrList predicates = new CStrList();
   char work[sizeString];

   while (op == XP_LBOX) {
      work[0]   = 0;
      int iPred = 1;

      getEvalToken();

      if      (op == XP_RBOX)
         iPred--;
      else if (op == XP_LBOX)
         iPred++;

      while (iPred != 0) {
         ChrCat(work, ' ', sizeString);
         Strcat(work, stoken, sizeString);
         getEvalToken();

         if      (op == XP_RBOX)
            iPred--;
         else if (op == XP_LBOX)
            iPred++;
         }

      predicates->StrInsert(work);
      getEvalToken();
      }

   // Evaluate the filter ---------------------------------------------
   if (evalStack->Empty())
      Assert(XPE_STACKEMPTY);

   PCXPathItem item = evalStack->ItemAtTOS();
   if (!item || item->type != XP_NODESET)
      throw(XPE_NODESETEXPECTED);

   // For each node in item.nodeset, apply the filtering predicates,
   // putting keepers in the rslts nodelist
   PCNodeList rslts = new CNodeList();
   PCNodeList nodes = item->nValue;

   if (predicates->GotoHead()) do {
      char exp[sizeString];
      predicates->StrItem(exp, sizeString);
      nodes = evalPredicate(nodes, exp, item->order);
      Assert(nodes);
      } while (predicates->GotoNext());

   // Collect the results of the predicate filtering
   for (int j = 0; j < nodes->Length(); j++) {
      PCNode node = nodes->Item(j);
      rslts->InsertNode(node);
      }

   // Push the resulting NodeList onto the stack
   evalStack->PushNL(rslts);
   } // evalLBox

//---------------------------------------------------------------------
// Evaluate a <= b
//
void CXPath::evalLTE(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack
   PCXPathItem b = evalStack->Pop();

   // Pop a off the stack
   PCXPathItem a = evalStack->Pop();

   // Compare the operands according to their type
   if      (a->type == XP_NODESET)
      if      (b->type == XP_NODESET)
         evalLTE_NDS_NDS(a, b);
      else if (b->type == XP_STRING)
         evalLTE_NDS_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalLTE_NDS_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalLTE_NDS_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else if (a->type == XP_STRING)
      if      (b->type == XP_NODESET)
         evalLTE_STR_NDS(a, b);
      else if (b->type == XP_STRING)
         evalLTE_STR_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalLTE_STR_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalLTE_STR_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else if (a->type == XP_NUMBER)
      if      (b->type == XP_NODESET)
         evalLTE_DBL_NDS(a, b);
      else if (b->type == XP_STRING)
         evalLTE_DBL_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalLTE_DBL_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalLTE_DBL_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else if (a->type == XP_BOOLEAN)
      if      (b->type == XP_NODESET)
         evalLTE_BOL_NDS(a, b);
      else if (b->type == XP_STRING)
         evalLTE_BOL_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalLTE_BOL_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalLTE_BOL_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
   else
      throw(XPE_BADOPERANDTYPE);
   // Push (a <= b) onto the stack

   // Get the next token
   getEvalToken();
   } // evalLTE

//---------------------------------------------------------------------
// Evaluate a < b
//
void CXPath::evalLT(void) {
   // Compare the arguments
   evalGTE();

   // Reverse the sense of the result
   evalStack->SetTOS(!evalStack->BoolAtTOS());

   // Get the next token
   getEvalToken();
   } // evalLT

//---------------------------------------------------------------------
// Evaluate a - b
//
void CXPath::evalMinus(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack and convert it to numeric
   PCXPathItem b = convertToNumber(evalStack->Pop());

   // Pop a off the stack and convert it to numeric
   PCXPathItem a  = convertToNumber(evalStack->Pop());

   // Push (a - b) onto the stack
   evalStack->PushD(a->dValue - b->dValue);

   // Get the next token
   getEvalToken();
   } // evalMinus

//---------------------------------------------------------------------
// Evaluate a mod b
//
// The mod operator returns the remainder from a truncating
// division. For example,
//   *  5 mod  2 returns 1
//   *  5 mod -2 returns 1
//   * -5 mod  2 returns -1
//   * -5 mod -2 returns -1
// NOTE: This is the same as the % operator in Java and ECMAScript.
// NOTE: This is not the same as the IEEE 754 remainder operation,
// which returns the remainder from a rounding division.
//
void CXPath::evalMod(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack and convert it to numeric
   PCXPathItem b = convertToNumber(evalStack->Pop());

   // Pop a off the stack and convert it to numeric
   PCXPathItem a  = convertToNumber(evalStack->Pop());

   if (b->dValue == 0)
      throw(XPE_DIVIDEBYZERO);

   // Push (a mod b) onto the stack (TBD)
   double mod = fmod(a->dValue, b->dValue);
   evalStack->PushD(mod);

   // Get the next token
   getEvalToken();
   } // evalMod

//---------------------------------------------------------------------
// Evaluate a * b
//
void CXPath::evalMul(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack and convert it to numeric
   PCXPathItem b = convertToNumber(evalStack->Pop());

   // Pop a off the stack and convert it to numeric
   PCXPathItem a  = convertToNumber(evalStack->Pop());

   // Push (a * b) onto the stack
   evalStack->PushD(a->dValue * b->dValue);

   // Get the next token
   getEvalToken();
   } // evalMul

//---------------------------------------------------------------------
// Evaluate not a
//
void CXPath::evalNegate(void) {
   if (evalStack->Number() < 1)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop a off the stack and convert it to boolean
   PCXPathItem a  = convertToBoolean(evalStack->Pop());

   // Push (not a) onto the stack
   evalStack->PushB(~a->bValue);

   // Get the next token
   getEvalToken();
   } // evalNegate

//---------------------------------------------------------------------
// Evaluate a <> b
//
void CXPath::evalNE(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack
   PCXPathItem b = evalStack->Pop();

   // Pop a off the stack
   PCXPathItem a = evalStack->Pop();

   // Compare the operands according to their type
   if      (a->type == XP_NODESET)
      if      (b->type == XP_NODESET)
         evalNE_NDS_NDS(a, b);
      else if (b->type == XP_STRING)
         evalNE_NDS_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalNE_NDS_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalNE_NDS_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);

   else if (a->type == XP_STRING) {
      if      (b->type == XP_NODESET)
         evalEQ_NDS_STR(b, a);
      else if (b->type == XP_STRING)
         evalEQ_STR_STR(a, b);
      else if (b->type == XP_NUMBER)
         evalEQ_STR_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalEQ_STR_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);

      // Reverse the sense of the result
      evalStack->SetTOS(!evalStack->BoolAtTOS());
      }

   else if (a->type == XP_NUMBER) {
      if      (b->type == XP_NODESET)
         evalEQ_NDS_DBL(b, a);
      else if (b->type == XP_STRING)
         evalEQ_STR_DBL(b, a);
      else if (b->type == XP_NUMBER)
         evalEQ_DBL_DBL(a, b);
      else if (b->type == XP_BOOLEAN)
         evalEQ_DBL_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);

      // Reverse the sense of the result
      evalStack->SetTOS(!evalStack->BoolAtTOS());
      }

   else if (a->type == XP_BOOLEAN) {
      if      (b->type == XP_NODESET)
         evalEQ_NDS_BOL(b, a);
      else if (b->type == XP_STRING)
         evalEQ_STR_BOL(b, a);
      else if (b->type == XP_NUMBER)
         evalEQ_DBL_BOL(b, a);
      else if (b->type == XP_BOOLEAN)
         evalEQ_BOL_BOL(a, b);
      else
         throw(XPE_BADOPERANDTYPE);
 
      // Reverse the sense of the result
      evalStack->SetTOS(!evalStack->BoolAtTOS());
      }

   else
      throw(XPE_BADOPERANDTYPE);

   // Get the next token
   getEvalToken();
   } // evalNE

//---------------------------------------------------------------------
// Push a number onto the stack
//
void CXPath::evalNmbr(void) {
   evalStack->PushD(StrToDouble(stoken+1));

   getEvalToken();
   } // evalNmbr

//---------------------------------------------------------------------
// Evaluate a or b
//
void CXPath::evalOr(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack and convert it to boolean
   PCXPathItem b = convertToBoolean(evalStack->Pop());

   // Pop a off the stack and convert it to boolean
   PCXPathItem a  = convertToBoolean(evalStack->Pop());

   // Push (a or b) onto the stack
   evalStack->PushB(a->bValue | b->bValue);

   // Get the next token
   getEvalToken();
   } // evalOr

//---------------------------------------------------------------------
//
void CXPath::evalPath(void) {
   PCXPathItem item = evalStack->Pop();
   if (item->type != XP_NODESET)
      throw(XPE_NODESETEXPECTED);

   item->node       = item->nValue->Item(0);
   item->position   = 1;    // will have to check the direction???
   item->size       = item->nValue->Length();

   ctxtStack->PushX(item);

   getEvalToken();
   } // evalPath

//---------------------------------------------------------------------
// Evaluate a + b
//
void CXPath::evalPlus(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack and convert it to numeric
   PCXPathItem b = convertToNumber(evalStack->Pop());

   // Pop a off the stack and convert it to numeric
   PCXPathItem a  = convertToNumber(evalStack->Pop());

   // Push (a + b) onto the stack
   evalStack->PushD(a->dValue + b->dValue);

   // Get the next token
   getEvalToken();
   } // evalPlus

//---------------------------------------------------------------------
//
void CXPath::evalQuotedString(void) {
   for (Pchar p = stoken; *p; p++)
      if (uchar(*p) == XP_SPACE)
         *p = ' ';
   evalStack->PushS(stoken+1);

   // Get the next token
   getEvalToken();
   } // evalQuotedString

//---------------------------------------------------------------------
//
void CXPath::evalRoot(void) {
   // Push the root node onto the context stack
   PCNode document  = TYPECAST(node->OwnerDocument(), Node);
   ctxtStack->PushN(document);

   // Get the next token
   getEvalToken();
   } // evalRoot

//---------------------------------------------------------------------
// Evaluate the step
//
void CXPath::evalStep(void) {
   Assert(op == XP_AXISNAME);

   // Save the axisName token
   char axisName[sizeName];
   Strcpy(axisName, stoken, sizeName);

   // Save the quoted string (for processing-instruction)
   getEvalToken();
   char qstring[sizeLine];
   qstring[0] = 0;
   if (op == XP_QUOTEDSTR) {
      Strcpy(qstring, stoken, sizeLine);
      getEvalToken();
      }

   // Save the nodeTest token
   if (op != XP_NODETYPE &&
       op != XP_QNAME    &&
       op != XP_ALL      &&
       op != XP_ALLNAME)
      throw(XPE_OPNOTEXPECTED);
   char nodeTest[sizeLine];
   Strcpy(nodeTest, stoken, sizeLine);

   // Validate quoted string
   if (!StrEmpty(qstring) &&
       strcmp(stoken+1, "proceessing-instruction") != 0)
      throw(XPE_PROCINSTEXPECTED);

   // Copy the parsed predicate into separate string objects, adding
   // each to my predicate list. Include nested predicates
   PCStrList predicates = new CStrList();
   char work[sizeString];
   getEvalToken();
   while (op == XP_LBOX) {
      work[0]   = 0;
      int iPred = 1;

      getEvalToken();

      if      (op == XP_RBOX)
         iPred--;
      else if (op == XP_LBOX)
         iPred++;

      while (iPred != 0) {
         ChrCat(work, ' ', sizeString);
         Strcat(work, stoken, sizeString);
         getEvalToken();

         if      (op == XP_RBOX)
            iPred--;
         else if (op == XP_LBOX)
            iPred++;
         }

      predicates->StrInsert(work);
      getEvalToken();
      }

   // Evaluate the step------------------------------------------------
   if (ctxtStack->Empty())
      Assert(XPE_STACKEMPTY);

   PCXPathItem context = ctxtStack->ItemAtTOS();
   if (!context || context->type != XP_NODESET)
      throw(XPE_NODESETEXPECTED);

   // For each node in context.nodeset, traverse the DOM, putting the
   // keepers in the rslts nodelist
   PCNodeList rslts = new CNodeList();
   PCNodeList nodes = context->nValue;

   for (int i = 0; i < nodes->Length(); i++) {
      PCNodeList finds = travelOnAxis(nodes->Item(i),
                                      axisName,
                                      nodeTest);

      // For each predicate, evaluate the predicate against the
      // per-node traversal results.
      if (finds->Length() > 0 && predicates->GotoHead()) do {
         char exp[sizeString];
         predicates->StrItem(exp, sizeString);
         finds = evalPredicate(finds, exp, down);
         Assert(finds);
         } while (predicates->GotoNext());

      // Collect the results of the predicate filtering
      for (int j = 0; j < finds->Length(); j++) {
         PCNode node = finds->Item(j);
         rslts->InsertNode(node);
         }
      }

   // Push the resulting NodeList onto the stack
   evalStack->PushNL(rslts);
   } // evalStep

//---------------------------------------------------------------------
//
//
PCNodeList CXPath::evalPredicate(RPCNodeList _nodes,
                                 Pchar       _exp,
                                 direction   _order) {
   PCNodeList rslts = new CNodeList();

   // Save the pre-predicate expression state
   int hiExp     = iExp;
   PCRecord hExp = new CRecord(exp);
   ushort hop    = op;
   char htoken[sizeLine];
   Strcpy(htoken, stoken, sizeLine);

   // Evaluate the predicate expression for each node in the CNodeList
   for (int i = 0; i < _nodes->Length(); i++) {
      PCXPathItem item;
      if (_order == down)
         item = ctxtStack->PushN(_nodes->Item(i));
      else
         item = ctxtStack->PushN(_nodes->Item(_nodes->Length()-i-1));

      { // need to keep the following in the stack context object:
         // context node
         item->node     =_nodes->Item(i);

         // context position (=i+1)
         item->position = i+1;

         // context size
         item->size     =_nodes->Length();
         }

      // Evaluate the predicate expression
      eval(_exp);

      // Is the current node a keeper?
      item = evalStack->Pop();
      if      (item->type == XP_BOOLEAN) {
         if (item->bValue)
            rslts->InsertNode(_nodes->Item(i));
         }
      else if (item->type == XP_NUMBER) {
         if (long(item->dValue) == i+1)
            rslts->InsertNode(_nodes->Item(i));
         }
      else {
         item = convertToBoolean(item);
         if (item->bValue)
            rslts->InsertNode(_nodes->Item(i));
         }

      ctxtStack->Pop();
      }

   // restore the pre-predicate expression state
   strcpy(stoken, htoken);
   op            = hop;
   iExp          = hiExp;
   exp           = hExp;

   return rslts;
   } // evalPredicate

//---------------------------------------------------------------------
// Evaluate -a
//
void CXPath::evalUnaryMinus(void) {
   if (evalStack->Number() < 1)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop a off the stack and convert it to numeric
   PCXPathItem a  = convertToNumber(evalStack->Pop());

   // Push (-a) onto the stack
   evalStack->PushD(-a->dValue);

   // Get the next token
   getEvalToken();
   } // evalUnaryMinus

//---------------------------------------------------------------------
// Evaluate a union b
//
void CXPath::evalUnion(void) {
   if (evalStack->Number() < 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Pop b off the stack

   // Pop a off the stack

   // Push (a union b) onto the stack

   // Get the next token
   getEvalToken();
   } // evalUnion

//---------------------------------------------------------------------
//
void CXPath::evalUnwind(void) {
   // Unwind the context stack
   ushort steps = StrToShort(stoken+1);

   for (ushort i = steps; i; i--)
      ctxtStack->Pop();
 
   // Get the next token
   getEvalToken();
   } // evalUnwind

//---------------------------------------------------------------------
// Intend that an XPath variable is a farSlang global.
//
void CXPath::evalVariable(void) {
   // TBD

   // Get the next token
   getEvalToken();
   } // evalVariable

//=====================================================================
// Comparison evaluators
//=====================================================================

//=====================================================================
// evalEqual methods
//=====================================================================

//---------------------------------------------------------------------
// True if any node in a is equal to any node in b (intersection not
// empty)
//
void CXPath::evalEQ_NDS_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_NODESET);

   for (int i = 0; i < _a->nValue->Length(); i++) {
      Pchar valA = stringValue(_a->nValue->Item(i));

      for (int j = 0; j < _b->nValue->Length(); j++) {
         Pchar valB = stringValue(_b->nValue->Item(j));

         if      (StrEmpty(valA) &&
                  StrEmpty(valB)) {
            delete[] valA;
            delete[] valB;
            evalStack->PushB(true);
            return;
            }
         else if (!StrEmpty(valA) &&
                  !StrEmpty(valB) &&
                  strcmp(valA, valB) == 0) {
            delete[] valA;
            delete[] valB;
            evalStack->PushB(true);
            return;
            }

         delete[] valB;
         }
 
      delete[] valA;
      }

   evalStack->PushB(false);
   } // evalEQ_NDS_NDS

//---------------------------------------------------------------------
// True if the string value of any node is equal to b
//
void CXPath::evalEQ_NDS_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_STRING);

   Pchar valB = _b->sValue;

   // Traverse the nodelist looking for a match
   for (int i = 0; i < _a->nValue->Length(); i++) {
      Pchar valA = stringValue(_a->nValue->Item(i));

      if      (StrEmpty(valA) &&
               StrEmpty(valB)) {
         delete[] valA;
         evalStack->PushB(true);
         return;
         }
      else if (!StrEmpty(valA) &&
               !StrEmpty(valB) &&
               strcmp(valA, valB) == 0) {
         delete[] valA;
         evalStack->PushB(true);
         return;
         }

      delete[] valA;
      }

   evalStack->PushB(false);
   } // evalEQ_NDS_STR

//---------------------------------------------------------------------
// True if the number value of any node in a is equal to b
//
void CXPath::evalEQ_NDS_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_NUMBER);

   double valB = _b->dValue;

   for (int i = 0; i < _a->nValue->Length(); i++) {
      double valA = numberValue(_a->nValue->Item(i));

      if (valA == valB) {
         evalStack->PushB(true);
         return;
         }
      }

   evalStack->PushB(false);
   } // evalEQ_NDS_DBL

//---------------------------------------------------------------------
// True if the boolean value of the node set in a is equal to b
//
void CXPath::evalEQ_NDS_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_BOOLEAN);

   bool valB = _b->bValue;

   if      (_a->nValue->Length() >  0 &&  valB) {
      evalStack->PushB(true);
      return;
      }
   else if (_a->nValue->Length() == 0 && !valB) {
      evalStack->PushB(true);
      return;
      }

   evalStack->PushB(false);
   } // evalEQ_NDS_BOL

//---------------------------------------------------------------------
// True if the specified strings are equal.
//
void CXPath::evalEQ_STR_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_STRING);

   evalStack->PushB(strcmp(_a->sValue, _b->sValue) == 0);
   } // evalEQ_STR_STR

//---------------------------------------------------------------------
// True if the converted string is equal to the number
//
void CXPath::evalEQ_STR_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_NUMBER);

   evalStack->PushB(StrToDouble(_a->sValue) == _b->dValue);
   } // evalEQ_STR_DBL

//---------------------------------------------------------------------
// True if the converted string is equal to the boolean
//
void CXPath::evalEQ_STR_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_BOOLEAN);

   evalStack->PushB(convertToBoolean(_a)->bValue == _b->bValue);
   } // evalEQ_STR_BOL

//---------------------------------------------------------------------
// True if the specified numbers are equal
//
void CXPath::evalEQ_DBL_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_NUMBER);

   evalStack->PushB(_a->dValue == _b->dValue);
   } // evalEQ_DBL_DBL

//---------------------------------------------------------------------
// True if the converted number is equal to the boolean
//
void CXPath::evalEQ_DBL_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_BOOLEAN);

   evalStack->PushB(convertToBoolean(_a)->bValue == _b->bValue);
   } // evalEQ_DBL_BOL

//---------------------------------------------------------------------
// True if the specified booleans are equal
//
void CXPath::evalEQ_BOL_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_BOOLEAN);
   Assert(_b->type == XP_BOOLEAN);

   evalStack->PushB(_a->bValue == _b->bValue);
   } // evalEQ_BOL_BOL

//=====================================================================
// evalNotEqual methods
//=====================================================================

//---------------------------------------------------------------------
// True if no node in a is equal to any node in b (intersection empty)
//
void CXPath::evalNE_NDS_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_NODESET);

   for (int i = 0; i < _a->nValue->Length(); i++) {
      Pchar valA = stringValue(_a->nValue->Item(i));

      for (int j = 0; j < _b->nValue->Length(); j++) {
         Pchar valB = stringValue(_b->nValue->Item(j));

         if      (!StrEmpty(valA) &&
                  StrEmpty(valB)) {
            delete[] valA;
            delete[] valB;
            evalStack->PushB(true);
            return;
            }
         else if (StrEmpty(valA) &&
                  !StrEmpty(valB)) {
            delete[] valA;
            delete[] valB;
            evalStack->PushB(true);
            return;
            }
         else if (strcmp(valA, valB) != 0) {
            delete[] valA;
            delete[] valB;
            evalStack->PushB(true);
            return;
            }

         delete[] valB;
         }
 
      delete[] valA;
      }

   evalStack->PushB(false);
   } // evalNE_NDS_NDS

//---------------------------------------------------------------------
// True if the string value of the node set is not equal to the
// specified string
//
void CXPath::evalNE_NDS_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_STRING);

   Pchar valB = _b->sValue;

   for (int i = 0; i < _a->nValue->Length(); i++) {
      Pchar valA = stringValue(_a->nValue->Item(i));

      if      (!StrEmpty(valA) &&
               StrEmpty(valB)) {
         delete[] valA;
         evalStack->PushB(true);
         return;
         }
      else if (StrEmpty(valA) &&
               !StrEmpty(valB)) {
         delete[] valA;
         evalStack->PushB(true);
         return;
         }
      else if (strcmp(valA, valB) != 0) {
         delete[] valA;
         evalStack->PushB(true);
         return;
         }

      delete[] valA;
      }

   evalStack->PushB(false);
   } // evalNE_NDS_STR

//---------------------------------------------------------------------
// True if the number value of the node set is not equal to the
// specified number
//
void CXPath::evalNE_NDS_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_NUMBER);

   double valB = _b->dValue;

   for (int i = 0; i < _a->nValue->Length(); i++) {
      double valA = numberValue(_a->nValue->Item(i));

      if (valA != valB) {
         evalStack->PushB(true);
         return;
         }
      }

   evalStack->PushB(false);
   } // evalNE_NDS_DBL

//---------------------------------------------------------------------
// True if the boolean value of the node set is not equal to the
// specified boolean
//
void CXPath::evalNE_NDS_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_BOOLEAN);

   bool valB = _b->bValue;

   if      (_a->nValue->Length() >  0 && !valB) {
      evalStack->PushB(true);
      return;
      }
   else if (_a->nValue->Length() == 0 &&  valB) {
      evalStack->PushB(true);
      return;
      }

   evalStack->PushB(false);
   } // evalNE_NDS_BOL

//=====================================================================
// evalGTE methods
//=====================================================================

//---------------------------------------------------------------------
//
void CXPath::evalGTE_NDS_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_NODESET);

   for (int i = 0; i < _a->nValue->Length(); i++) {
      Pchar valA = stringValue(_a->nValue->Item(i));

      for (int j = 0; j < _b->nValue->Length(); j++) {
         Pchar valB = stringValue(_b->nValue->Item(j));

         if      (StrEmpty(valB)) {
            delete[] valA;
            delete[] valB;
            evalStack->PushB(true);
            return;
            }
         else if (!StrEmpty(valA) &&
                  !StrEmpty(valB) &&
                  strcmp(valA, valB) >= 0) {
            delete[] valA;
            delete[] valB;
            evalStack->PushB(true);
            return;
            }

         delete[] valB;
         }
 
      delete[] valA;
      }

   evalStack->PushB(false);
   } // evalGTE_NDS_NDS

//---------------------------------------------------------------------
//
void CXPath::evalGTE_NDS_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_STRING);

   Pchar valB = _b->sValue;

   for (int i = 0; i < _a->nValue->Length(); i++) {
      Pchar valA = stringValue(_a->nValue->Item(i));

      if      (StrEmpty(valB)) {
         delete[] valA;
         evalStack->PushB(true);
         return;
         }
      else if (!StrEmpty(valA) &&
               !StrEmpty(valB) &&
               strcmp(valA, valB) >= 0) {
         delete[] valA;
         evalStack->PushB(true);
         return;
         }

      delete[] valA;
      }

   evalStack->PushB(false);
   } // evalGTE_NDS_STR

//---------------------------------------------------------------------
//
void CXPath::evalGTE_NDS_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_NUMBER);

   double valB = _b->dValue;

   for (int i = 0; i < _a->nValue->Length(); i++) {
      double valA = numberValue(_a->nValue->Item(i));

      if (valA >= valB) {
         evalStack->PushB(true);
         return;
         }
      }

   evalStack->PushB(false);
   } // evalGTE_NDS_DBL

//---------------------------------------------------------------------
//
void CXPath::evalGTE_NDS_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_BOOLEAN);

   bool valB = _b->bValue;

   if      (!valB || _a->nValue->Length() > 0) {
      evalStack->PushB(true);
      return;
      }

   evalStack->PushB(false);
   } // evalGTE_NDS_BOL

//---------------------------------------------------------------------
//
void CXPath::evalGTE_STR_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_NODESET);

   Pchar valA = _a->sValue;

   for (int i = 0; i < _b->nValue->Length(); i++) {
      Pchar valB = stringValue(_b->nValue->Item(i));

      if      (StrEmpty(valB)) {
         delete[] valB;
         evalStack->PushB(true);
         return;
         }
      else if (!StrEmpty(valA) &&
               !StrEmpty(valB) &&
               strcmp(valA, valB) >= 0) {
         delete[] valB;
         evalStack->PushB(true);
         return;
         }

      delete[] valB;
      }

   evalStack->PushB(false);
   } // evalGTE_STR_NDS

//---------------------------------------------------------------------
//
void CXPath::evalGTE_STR_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_STRING);

   evalStack->PushB(strcmp(_a->sValue, _b->sValue) >= 0);
   } // evalGTE_STR_STR

//---------------------------------------------------------------------
//
void CXPath::evalGTE_STR_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_NUMBER);

   evalStack->PushB(StrToDouble(_a->sValue) >= _b->dValue);
   } // evalGTE_STR_DBL

//---------------------------------------------------------------------
//
void CXPath::evalGTE_STR_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_BOOLEAN);

   evalStack->PushB(convertToBoolean(_a)->bValue >= _b->bValue);
   } // evalGTE_STR_BOL

//---------------------------------------------------------------------
//
void CXPath::evalGTE_DBL_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_NUMBER);

   double valA = _a->dValue;

   for (int i = 0; i < _b->nValue->Length(); i++) {
      double valB = numberValue(_b->nValue->Item(i));

      if (valA >= valB) {
         evalStack->PushB(true);
         return;
         }
      }

   evalStack->PushB(false);
   } // evalGTE_DBL_NDS

//---------------------------------------------------------------------
//
void CXPath::evalGTE_DBL_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_STRING);

   evalStack->PushB(_a->dValue >= StrToDouble(_b->sValue));
   } // evalGTE_DBL_STR

//---------------------------------------------------------------------
//
void CXPath::evalGTE_DBL_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_NUMBER);

   evalStack->PushB(_a->dValue >= _b->dValue);
   } // evalGTE_DBL_DBL

//---------------------------------------------------------------------
//
void CXPath::evalGTE_DBL_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_BOOLEAN);

   evalStack->PushB(convertToBoolean(_a)->bValue >= _b->bValue);
   } // evalGTE_DBL_BOL

//---------------------------------------------------------------------
//
void CXPath::evalGTE_BOL_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_BOOLEAN);
   Assert(_b->type == XP_NODESET);

   bool valA = _a->bValue;

   if      (valA || _b->nValue->Length() == 0) {
      evalStack->PushB(true);
      return;
      }

   evalStack->PushB(false);
   } // evalGTE_BOL_NDS

//---------------------------------------------------------------------
//
void CXPath::evalGTE_BOL_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_BOOLEAN);
   Assert(_b->type == XP_STRING);

   evalStack->PushB(_a->bValue >= convertToBoolean(_b)->bValue);
   } // evalGTE_BOL_STR

//---------------------------------------------------------------------
//
void CXPath::evalGTE_BOL_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_BOOLEAN);
   Assert(_b->type == XP_NUMBER);

   evalStack->PushB(_a->bValue >= convertToBoolean(_b)->bValue);
   } // evalGTE_BOL_DBL

//---------------------------------------------------------------------
//
void CXPath::evalGTE_BOL_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_BOOLEAN);
   Assert(_b->type == XP_BOOLEAN);

   evalStack->PushB(_a->bValue >= _b->bValue);
   } // evalGTE_BOL_BOL

//=====================================================================
// evalLTE methods
//=====================================================================

//---------------------------------------------------------------------
//
void CXPath::evalLTE_NDS_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_NODESET);

   for (int i = 0; i < _a->nValue->Length(); i++) {
      Pchar valA = stringValue(_a->nValue->Item(i));

      for (int j = 0; j < _b->nValue->Length(); j++) {
         Pchar valB = stringValue(_b->nValue->Item(j));

         if      (StrEmpty(valA)) {
            delete[] valA;
            delete[] valB;
            evalStack->PushB(true);
            return;
            }
         else if (!StrEmpty(valA) &&
                  !StrEmpty(valB) &&
                  strcmp(valA, valB) <= 0) {
            delete[] valA;
            delete[] valB;
            evalStack->PushB(true);
            return;
            }

         delete[] valB;
         }
 
      delete[] valA;
      }

   evalStack->PushB(false);
   } // evalLTE_NDS_NDS

//---------------------------------------------------------------------
//
void CXPath::evalLTE_NDS_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_STRING);

   Pchar valB = _b->sValue;

   for (int i = 0; i < _a->nValue->Length(); i++) {
      Pchar valA = stringValue(_a->nValue->Item(i));

      if      (StrEmpty(valA)) {
         delete[] valA;
         evalStack->PushB(true);
         return;
         }
      else if (!StrEmpty(valA) &&
               !StrEmpty(valB) &&
               strcmp(valA, valB) <= 0) {
         delete[] valA;
         evalStack->PushB(true);
         return;
         }

      delete[] valA;
      }

   evalStack->PushB(false);
   } // evalLTE_NDS_STR

//---------------------------------------------------------------------
//
void CXPath::evalLTE_NDS_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_NUMBER);

   double valB = _b->dValue;

   for (int i = 0; i < _a->nValue->Length(); i++) {
      double valA = numberValue(_a->nValue->Item(i));

      if (valA <= valB) {
         evalStack->PushB(true);
         return;
         }
      }

   evalStack->PushB(false);
   } // evalLTE_NDS_DBL

//---------------------------------------------------------------------
//
void CXPath::evalLTE_NDS_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NODESET);
   Assert(_b->type == XP_BOOLEAN);

   bool valB = _b->bValue;

   if      (valB || _a->nValue->Length() == 0) {
      evalStack->PushB(true);
      return;
      }

   evalStack->PushB(false);
   } // evalLTE_NDS_BOL

//---------------------------------------------------------------------
//
void CXPath::evalLTE_STR_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_NODESET);

   Pchar valA = _a->sValue;

   for (int i = 0; i < _b->nValue->Length(); i++) {
      Pchar valB = stringValue(_b->nValue->Item(i));

      if      (StrEmpty(valA)) {
         delete[] valB;
         evalStack->PushB(true);
         return;
         }
      else if (!StrEmpty(valA) &&
               !StrEmpty(valB) &&
               strcmp(valA, valB) <= 0) {
         delete[] valB;
         evalStack->PushB(true);
         return;
         }

      delete[] valB;
      }

   evalStack->PushB(false);
   } // evalLTE_STR_NDS

//---------------------------------------------------------------------
//
void CXPath::evalLTE_STR_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_STRING);

   evalStack->PushB(strcmp(_a->sValue, _b->sValue) <= 0);
   } // evalLTE_STR_STR

//---------------------------------------------------------------------
//
void CXPath::evalLTE_STR_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_NUMBER);

   evalStack->PushB(StrToDouble(_a->sValue) <= _b->dValue);
   } // evalLTE_STR_DBL

//---------------------------------------------------------------------
//
void CXPath::evalLTE_STR_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_STRING);
   Assert(_b->type == XP_BOOLEAN);

   evalStack->PushB(convertToBoolean(_a)->bValue <= _b->bValue);
   } // evalLTE_STR_BOL

//---------------------------------------------------------------------
//
void CXPath::evalLTE_DBL_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_NUMBER);

   double valA = _a->dValue;

   for (int i = 0; i < _b->nValue->Length(); i++) {
      double valB = numberValue(_b->nValue->Item(i));

      if (valA <= valB) {
         evalStack->PushB(true);
         return;
         }
      }

   evalStack->PushB(false);
   } // evalLTE_DBL_NDS

//---------------------------------------------------------------------
//
void CXPath::evalLTE_DBL_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_STRING);

   evalStack->PushB(_a->dValue <= StrToDouble(_b->sValue));
   } // evalLTE_DBL_STR

//---------------------------------------------------------------------
//
void CXPath::evalLTE_DBL_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_NUMBER);

   evalStack->PushB(_a->dValue <= _b->dValue);
   } // evalLTE_DBL_DBL

//---------------------------------------------------------------------
//
void CXPath::evalLTE_DBL_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_NUMBER);
   Assert(_b->type == XP_BOOLEAN);

   evalStack->PushB(convertToBoolean(_a)->bValue <= _b->bValue);
   } // evalLTE_DBL_BOL

//---------------------------------------------------------------------
//
void CXPath::evalLTE_BOL_NDS(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_BOOLEAN);
   Assert(_b->type == XP_NODESET);

   bool valA = _a->bValue;

   if      (!valA || _b->nValue->Length() > 0) {
      evalStack->PushB(true);
      return;
      }

   evalStack->PushB(false);
   } // evalLTE_BOL_NDS

//---------------------------------------------------------------------
//
void CXPath::evalLTE_BOL_STR(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_BOOLEAN);
   Assert(_b->type == XP_STRING);

   evalStack->PushB(_a->bValue <= convertToBoolean(_b)->bValue);
   } // evalLTE_BOL_STR

//---------------------------------------------------------------------
//
void CXPath::evalLTE_BOL_DBL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_BOOLEAN);
   Assert(_b->type == XP_NUMBER);

   evalStack->PushB(_a->bValue <= convertToBoolean(_b)->bValue);
   } // evalLTE_BOL_DBL

//---------------------------------------------------------------------
//
void CXPath::evalLTE_BOL_BOL(RPCXPathItem _a, RPCXPathItem _b) {
   Assert(_a->type == XP_BOOLEAN);
   Assert(_b->type == XP_BOOLEAN);

   evalStack->PushB(_a->bValue <= _b->bValue);
   } // evalLTE_BOL_BOL

//=====================================================================
// Core Functions
//
// check for a special item on the stack that indicates that all the
// parameters have been consumed
//
//=====================================================================

//---------------------------------------------------------------------
// Boolean functions
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Function: boolean boolean(object)
//
// The boolean function converts its argument to a boolean as follows:
//    * a number is true if and only if it is neither positive or
//      negative zero nor NaN
//    * a node-set is true if and only if it is non-empty
//    * a string is true if and only if its length is non-zero
//    * an object of a type other than the four basic types is
//      converted to a boolean in a way that is dependent on that type
//
void CXPath::evalBoolean(void) {
   // Obtain the arguments
   PCXPathItem p;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (!p)
         p = item;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 1)
      throw(XPE_WRONGNRARGUMENTS);

   if (evalStack->Number() < 2)
      Assert(XPE_STACKEMPTY);

   evalStack->PushX(convertToBoolean(evalStack->Pop()));
   } // evalBoolean

//---------------------------------------------------------------------
// Function: boolean not(boolean)
//
// The not function returns true if its argument is false, and false
// otherwise.
//
void CXPath::evalNot(void) {
   // Obtain the argument
   PCXPathItem p;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_BOOLEAN && !p)
         p = item;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 1)
      throw(XPE_WRONGNRARGUMENTS);

   evalStack->PushB(!(p->bValue));
   } // evalNot

//---------------------------------------------------------------------
// Function: boolean true()
//
// The true function returns true.
//
void CXPath::evalTrue(void) {
   // Obtain the argument
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 0)
      throw(XPE_WRONGNRARGUMENTS);

   item = new CXPathItem(bool(true));
   evalStack->PushX(item);
   } // evalTrue

//---------------------------------------------------------------------
// Function: boolean false()
//
// The false function returns false.
//
void CXPath::evalFalse(void) {
   // Obtain the argument
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 0)
      throw(XPE_WRONGNRARGUMENTS);

   item = new CXPathItem(bool(false));
   evalStack->PushX(item);
   } // evalFalse

//---------------------------------------------------------------------
// Number functions
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Function: number number(object?)
//
// The number function converts its argument to a number as follows:
//    * a string that consists of optional whitespace followed by an
//      optional minus sign followed by a Number followed by whitespace
//      is converted to the IEEE 754 number that is nearest (according
//      to the IEEE 754 round-to-nearest rule) to the mathematical
//      value represented by the string; any other string is converted
//      to NaN
//    * boolean true is converted to 1; boolean false is converted to 0
//    * a node-set is first converted to a string as if by a call to
//      the string function and then converted in the same way as a
//      string argument
//    * an object of a type other than the four basic types is
//      converted to a number in a way that is dependent on that type
//
// If the argument is omitted, it defaults to a node-set with the
// context node as its only member.
//
// NOTE: The number function should not be used for conversion of
// numeric data occurring in an element in an XML document unless the
// element is of a type that represents numeric data in a language-
// neutral format (which would typically be transformed into a
// language-specific format for presentation to a user). In addition,
// the number function cannot be used unless the language-neutral
// format used by the element is consistent with the XPath syntax for a
// Number.
//
void CXPath::evalNumber(void) {
   // Obtain the arguments
   PCXPathItem p;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (!p)
         p = item;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs > 1)
      throw(XPE_WRONGNRARGUMENTS);

   if (!p) {
      if (ctxtStack->Empty())
         throw(XPE_CONTEXTEXPECTED);
      p = ctxtStack->ItemAtTOS();
      }

   evalStack->PushX(convertToNumber(p));
   } // evalNumber

//---------------------------------------------------------------------
// Function: number sum(node-set)
//
// The sum function returns the sum, for each node in the argument
// node-set, of the result of converting the string-values of the node
// to a number.
//
void CXPath::evalSum(void) {
   // Obtain the argument
   PCNodeList p;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_NODESET)
         p = item->nValue;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 1)
      throw(XPE_WRONGNRARGUMENTS);

   ulong sum        = 0;
   PCNodeList nodes = evalStack->Pop()->nValue;
   for (int i = 0; i < nodes->Length(); i++) {
      PCNode node = nodes->Item(i);
       sum += numberValue(node);
      }

   evalStack->PushD(sum);
   } // evalSum

//---------------------------------------------------------------------
// Function: number floor(number)
//
// The floor function returns the largest (closest to positive
// infinity) number that is not greater than the argument and that is
// an integer.
//
void CXPath::evalFloor(void) {
   // Obtain the argument
   double p   = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_NUMBER)
         p = item->dValue;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 1)
      throw(XPE_WRONGNRARGUMENTS);

   evalStack->PushD(floor(p));
   } // evalFloor

//---------------------------------------------------------------------
// Function: number ceiling(number)
//
// The ceiling function returns the smallest (closest to negative
// infinity) number that is not less than the argument and that is an
// integer.
//
void CXPath::evalCeiling(void) {
   // Obtain the argument
   double p   = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_NUMBER)
         p = item->dValue;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 1)
      throw(XPE_WRONGNRARGUMENTS);

   evalStack->PushD(ceil(p));
   } // evalCeiling

//---------------------------------------------------------------------
// Function: number round(number)
//
// The round function returns the number that is closest to the
// argument and that is an integer. If there are two such numbers, then
// the one that is closest to positive infinity is returned. If the
// argument is NaN, then NaN is returned. If the argument is positive
// infinity, then positive infinity is returned. If the argument is
// negative infinity, then negative infinity is returned. If the
// argument is positive zero, then positive zero is returned. If the
// argument is negative zero, then negative zero is returned. If the
// argument is less than zero, but greater than or equal to -0.5, then
// negative zero is returned.
//
// NOTE: For these last two cases, the result of calling the round
// function is not the same as the result of adding 0.5 and then
// calling the floor function.
//
void CXPath::evalRound(void) {
   // Obtain the argument
   double p   = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_NUMBER)
         p = item->dValue;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 1)
      throw(XPE_WRONGNRARGUMENTS);

   if      (p == +0.0)
      p = +0.0;
   else if (p == -0.0)
      p = -0.0;
   else if (p < 0.0 && p >= -0.5)
      p = -0.0;
   else
      p = floor(p + 0.5);

   evalStack->PushD(p);
   } // evalRound

//---------------------------------------------------------------------
// String functions
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Function: string string(object?)
//
// The string function converts an object to a string as follows:
//    * A node-set is converted to a string by returning the string-
//      value of the node in the node-set that is first in document
//      order. If the node-set is empty, an empty string is returned.
//    * A number is converted to a string as follows
//       o NaN is converted to the string NaN
//       o positive zero is converted to the string 0
//       o negative zero is converted to the string 0
//       o positive infinity is converted to the string Infinity
//       o negative infinity is converted to the string -Infinity
//       o if the number is an integer, the number is represented in
//         decimal form as a Number with no decimal point and no
//         leading zeros, preceded by a minus sign (-) if the number is
//         negative
//       o otherwise, the number is represented in decimal form as a
//         Number including a decimal point with at least one digit
//         before the decimal point and at least one digit after the
//         decimal point, preceded by a minus sign (-) if the number is
//         negative; there must be no leading zeros before the decimal
//         point apart possibly from the one required digit immediately
//         before the decimal point; beyond the one required digit
//         after the decimal point there must be as many, but only as
//         many, more digits as are needed to uniquely distinguish the
//         number from all other IEEE 754 numeric values.
//    * The boolean false value is converted to the string false. The
//      boolean true value is converted to the string true.
//    * An object of a type other than the four basic types is
//      converted to a string in a way that is dependent on that type.
//
// If the argument is omitted, it defaults to a node-set with the
// context node as its only member.
//
// NOTE: The string function is not intended for converting numbers
// into strings for presentation to users. The format-number function
// and xsl:number element in [XSLT] provide this functionality.
//
void CXPath::evalString(void) {
   // Obtain the arguments
   PCXPathItem p;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (!p)
         p = item;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs > 1)
      throw(XPE_WRONGNRARGUMENTS);

   if (!p) {
      if (ctxtStack->Empty())
         throw(XPE_CONTEXTEXPECTED);
      p = ctxtStack->ItemAtTOS();
      }

   evalStack->PushX(convertToString(p));
   } // evalString

//---------------------------------------------------------------------
// Function: string concat(string, string, string*)
//
// The concat function returns the concatenation of its arguments.
//
void CXPath::evalConcat(void) {
   // Obtain the arguments
   PCStrList list = new CStrList();
   int nrArgs     = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_STRING)
         list ->StrInsert(item->sValue, atTop);
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs < 2)
      throw(XPE_WRONGNRARGUMENTS);

   ulong size = list->ListSize();
   Pchar str  = new char[size];
   memset(str, 0, size);
   Pchar p    = str;
   if (list->GotoHead()) do {
      Pchar q = list->StrItem();
      strcpy(p, q);
      p      += strlen(q);
      } while (list->GotoNext());

   evalStack->PushS(str);
   delete[] str;
   } // evalConcat
 
//---------------------------------------------------------------------
// Function: boolean starts-with(string, string)
//
// The starts-with function returns true if the first argument string
// starts with the second argument string, and otherwise returns false.
//
void CXPath::evalStarts_with(void) {
   // Obtain the arguments
   Pchar a    = 0;
   Pchar b    = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_STRING)
         if      (!b) {
            b = new char[strlen(item->sValue)+1];
            strcpy(b, item->sValue);
            }
         else if (!a) {
            a = new char[strlen(item->sValue)+1];
            strcpy(a, item->sValue);
            }
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Restriction: can't handle sub-strings larger than sizeString
   char work[sizeString];
   evalStack->PushB(StrPos(b, a) == 0);

   delete[] a;
   delete[] b;
   } // evalStarts_with

//---------------------------------------------------------------------
// Function: boolean contains(string, string)
//
// The contains function returns true if the first argument string
// contains the second argument string, and otherwise returns false.
//
void CXPath::evalContains(void) {
   // Obtain the arguments
   Pchar a    = 0;
   Pchar b    = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_STRING)
         if      (!b) {
            b = new char[strlen(item->sValue)+1];
            strcpy(b, item->sValue);
            }
         else if (!a) {
            a = new char[strlen(item->sValue)+1];
            strcpy(a, item->sValue);
            }
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Restriction: can't handle sub-strings larger than sizeString
   char work[sizeString];
   evalStack->PushB(StrPos(b, a) >= 0);

   delete[] a;
   delete[] b;
   } // evalContains

//---------------------------------------------------------------------
// Function: string substring-before(string, string)
//
// The substring-before function returns the substring of the first
// argument string that precedes the first occurrence of the second
// argument string in the first argument string, or the empty string if
// the first argument string does not contain the second argument
// string. For example, substring-before("1999/04/01","/") returns
// 1999.
//
void CXPath::evalSubstring_before(void) {
   // Obtain the arguments
   Pchar a    = 0;
   Pchar b    = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_STRING)
         if      (!b) {
            b = new char[strlen(item->sValue)+1];
            strcpy(b, item->sValue);
            }
         else if (!a) {
            a = new char[strlen(item->sValue)+1];
            strcpy(a, item->sValue);
            }
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Restriction: can't handle sub-strings larger than sizeString
   char work[sizeString];
   Before(b, a, work, sizeString);
   evalStack->PushS(work);

   delete[] a;
   delete[] b;
   } // evalSubstring_before

//---------------------------------------------------------------------
// Function: string substring-after(string, string)
//
// The substring-after function returns the substring of the first
// argument string that follows the first occurrence of the second
// argument string in the first argument string, or the empty string if
// the first argument string does not contain the second argument
// string. For example, substring-after("1999/04/01","/") returns
// 04/01, and substring-after("1999/04/01","19") returns 99/04/01.
//
void CXPath::evalSubstring_after(void) {
   // Obtain the arguments
   Pchar a    = 0;
   Pchar b    = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_STRING)
         if      (!b) {
            b = new char[strlen(item->sValue)+1];
            strcpy(b, item->sValue);
            }
         else if (!a) {
            a = new char[strlen(item->sValue)+1];
            strcpy(a, item->sValue);
            }
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 2)
      throw(XPE_WRONGNRARGUMENTS);

   // Restriction: can't handle sub-strings larger than sizeString
   char work[sizeString];
   After(b, a, work, sizeString);
   evalStack->PushS(work);

   delete[] a;
   delete[] b;
   } // evalSubstring_after

//---------------------------------------------------------------------
// Function: string substring(string, number, number?)
//
// The substring function returns the substring of the first argument
// starting at the position specified in the second argument with
// length specified in the third argument. For example,
// substring("12345",2,3) returns "234". If the third argument is not
// specified, it returns the substring starting at the position
// specified in the second argument and continuing to the end of the
// string. For example, substring("12345",2) returns "2345".
//
// More precisely, each character in the string (see [3.6 Strings]) is
// considered to have a numeric position: the position of the first
// character is 1, the position of the second character is 2 and so on.
//
// NOTE: This differs from Java and ECMAScript, in which the
// String.substring method treats the position of the first character
// as 0.
//
// The returned substring contains those characters for which the
// position of the character is greater than or equal to the rounded
// value of the second argument and, if the third argument is
// specified, less than the sum of the rounded value of the second
// argument and the rounded value of the third argument; the
// comparisons and addition used for the above follow the standard
// IEEE 754 rules; rounding is done as if by a call to the round
// function. The following examples illustrate various unusual cases:
//    * substring("12345", 1.5, 2.6) returns "234"
//    * substring("12345", 0, 3) returns "12"
//    * substring("12345", 0 div 0, 3) returns ""
//    * substring("12345", 1, 0 div 0) returns ""
//    * substring("12345", -42, 1 div 0) returns "12345"
//    * substring("12345", -1 div 0, 1 div 0) returns ""
//
void CXPath::evalSubstring(void) {
   // Obtain the arguments from the evaluation stack
   Pchar p      =  0;
   double begin = -1.0;
   double size  = -1.0;
   int nrArgs   = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if      (item->type == XP_NUMBER) {
         if      (size  < 0.0)
            size  = item->dValue;
         else if (begin < 0.0)
            begin = item->dValue;
         }
      else if (item->type == XP_STRING && !p) {
         p = new char[strlen(item->sValue)+1];
         strcpy(p, item->sValue);
         }
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs > 3)
      throw(XPE_WRONGNRARGUMENTS);

   // Check if there is a string to work with
   if (!p) {
      evalStack->PushS("");
      return;
      }

   // Provide a size if no third parameter
   if (nrArgs == 2)
      size = strlen(p);

   // Copy the substring
   ushort sz = _ROUND(begin) + _ROUND(size);
   ushort i;
   ushort j = 0;
   for (i = 1; p[i-1] && i < sz; i++)
      if (i >= _ROUND(begin))
         p[j++] = p[i-1];
   p[j] = 0;

   evalStack->PushS(p);
   delete[] p;
   } // evalSubstring

//---------------------------------------------------------------------
// Function: number string-length(string?)
//
// The string-length returns the number of characters in the string
// (see [3.6 Strings]). If the argument is omitted, it defaults to the
// context node converted to a string, in other words the string-value
// of the context node.
//
void CXPath::evalString_length(void) {
   // Obtain the argument
   Pchar p    = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_STRING && !p) {
         p = new char[strlen(item->sValue)+1];
         strcpy(p, item->sValue);
         }
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs > 1)
      throw(XPE_WRONGNRARGUMENTS);

   // Default to the string value of the context node
   if (!p) {
      if (ctxtStack->Empty())
         throw(XPE_CONTEXTEXPECTED);
      item = convertToString(ctxtStack->ItemAtTOS());
      p    = new char[strlen(item->sValue)+1];
      strcpy(p, item->sValue);
      }

   evalStack->PushD(strlen(p));
   delete[] p;
   } // evalString_length

//---------------------------------------------------------------------
// Function: string normalize-space(string?)
//
// The normalize-space function returns the argument string with
// whitespace normalized by stripping leading and trailing whitespace
// and replacing sequences of whitespace characters by a single space.
// Whitespace characters are the same as those allowed by the S
// production in XML. If the argument is omitted, it defaults to the
// context node converted to a string, in other words the string-value
// of the context node.
//
void CXPath::evalNormalize_space(void) {
   // Obtain the argument
   Pchar p    = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_STRING && !p) {
         p = new char[strlen(item->sValue)+1];
         strcpy(p, item->sValue);
         }
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs > 1)
      throw(XPE_WRONGNRARGUMENTS);

   // Default to the string value of the context node
   if (!p) {
      if (ctxtStack->Empty())
         throw(XPE_CONTEXTEXPECTED);
      item = convertToString(ctxtStack->ItemAtTOS());
      p    = new char[strlen(item->sValue)+1];
      strcpy(p, item->sValue);
      }

   // Clean up the string
   TrimDlm(p);
   SqueezeWS(p);

   evalStack->PushS(p);
   delete[] p;
   } // evalNormalize_space

//---------------------------------------------------------------------
// Function: string translate(string, string, string)
//
// The translate function returns the first argument string with
// occurrences of characters in the second argument string replaced by
// the character at the corresponding position in the third argument
// string. For example, translate("bar","abc","ABC") returns the string
// BAr. If there is a character in the second argument string with no
// character at a corresponding position in the third argument string
// (because the second argument string is longer than the third
// argument string), then occurrences of that character in the first
// argument string are removed. For example, translate("--aaa--",
// "abc-","ABC") returns "AAA". If a character occurs more than once in
// the second argument string, then the first occurrence determines the
// replacement character. If the third argument string is longer than
// the second argument string, then excess characters are ignored.
//
// NOTE: The translate function is not a sufficient solution for case
// conversion in all languages. A future version of XPath may provide
// additional functions for case conversion.
//
void CXPath::evalTranslate(void) {
   // Obtain the arguments
   Pchar p1     =  0;
   Pchar p2     =  0;
   Pchar p3     =  0;
   int nrArgs   = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_STRING)
         if      (p3 == 0) {
            p3 = new char[strlen(item->sValue)+1];
            strcpy(p3, item->sValue);
            }
         else if (p2 == 0) {
            p2 = new char[strlen(item->sValue)+1];
            strcpy(p2, item->sValue);
            }
         else if (p1 == 0) {
            p1 = new char[strlen(item->sValue)+1];
            strcpy(p1, item->sValue);
            }
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 3)
      throw(XPE_WRONGNRARGUMENTS);

   if (!p1 || !p2 || !p3)
      throw(XPE_WRONGNRARGUMENTS);

   // Translate the string in p1 using p2 and p3
   Pchar p     = p1;
   ushort len3 = strlen(p3);
   while (*p) {
      ushort inc = 1;
      for (ushort ofst = 0; p2[ofst]; ofst++) {
         if (*p == p2[ofst]) {
            if (ofst < len3)
               *p  = p3[ofst];
            else {
               strcpy(p, p+1);
               inc = 0;
               }
            break;
            }
         }
      p += inc;
      }

   // Return the translated string
   evalStack->PushS(p1);
   delete[] p1;
   delete[] p2;
   delete[] p3;
   } // evalTranslate

//---------------------------------------------------------------------
// Function: boolean match(string, string)
//
// The match function returns true if a substring in the first
// parameter matches the regular expression in the second parameter.
//
void CXPath::evalMatch(void) {
   // Obtain the arguments
   Pchar a    = 0;
   Pchar b    = 0;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_STRING)
         if      (!b)
            STRINIT(b, item->sValue)

         else if (!a)
            STRINIT(a, item->sValue)
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 2)
      throw(XPE_WRONGNRARGUMENTS);

   evalStack->PushB(CRegX().Match(b, a));

   delete[] a;
   delete[] b;
   } // evalMatch

//---------------------------------------------------------------------
// Nodeset functions
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Function: number last()
//
// The last function returns a number equal to the context size from
// the expression evaluation context.
//
void CXPath::evalLast(void) {
   if (ctxtStack->Empty())
      throw(XPE_CONTEXTEXPECTED);

   PCXPathItem context = ctxtStack->ItemAtTOS();
   if (!context)
      throw(XPE_CONTEXTEXPECTED);

   // Count the number of arguments
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 0)
      throw(XPE_WRONGNRARGUMENTS);

   evalStack->PushD(context->size);
   } // evalLast

//---------------------------------------------------------------------
// Function: number position()
//
// The position function returns a number equal to the context position
// from the expression evaluation context.
//
void CXPath::evalPosition(void) {
   if (ctxtStack->Empty())
      throw(XPE_CONTEXTEXPECTED);

   PCXPathItem context = ctxtStack->ItemAtTOS();
   if (!context)
      throw(XPE_CONTEXTEXPECTED);

   // Count the number of arguments
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 0)
      throw(XPE_WRONGNRARGUMENTS);

   evalStack->PushD(context->position);
   } // evalPosition

//---------------------------------------------------------------------
// Function: number count(node-set)
//
// The count function returns the number of nodes in the argument
// node-set.
//
void CXPath::evalCount(void) {
   // Count the number of arguments
   int count;
   int nrArgs = -1;
   PCXPathItem item;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type != XP_NODESET)
         throw(XPE_NODESETEXPECTED);

      count = item->nValue->Length();
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs != 1)
      throw(XPE_WRONGNRARGUMENTS);

   evalStack->PushD(count);
   } // evalCount

//---------------------------------------------------------------------
// Function: node-set id(object)
//
// The id function selects elements by their unique ID (see [5.2.1
// Unique IDs]).
//
// 1. When the argument to id is of type node-set, then the result is
//    the union of the result of applying id to the string-value of
//    each of the nodes in the argument node-set.
//
// 2. When the argument to id is of any other type, the argument is
//    converted to a string as if by a call to the string function; the
//    string is split into a whitespace-separated list of tokens
//    (whitespace is any sequence of characters matching the production
//    S); the result is a node-set containing the elements in the same
//    document as the context node that have a unique ID equal to any
//    of the tokens in the list.
//
//    * id("foo") selects the element with the unique ID foo
//    * id("foo")/child::para[position()=5] selects the fifth para
//      child of the element with unique ID foo
//
void CXPath::evalId(void) {
   if (evalStack->Empty())
      Assert(XPE_STACKEMPTY);

   Assert("not ready for primetime" == 0);

   // TBD
   if (evalStack->TypeAtTOS() == XP_NODESET) {
      }
   else {
      }
   } // evalId

//---------------------------------------------------------------------
// Function: string local-name(node-set?)
//
// The local-name function returns the local part of the expanded-name
// of the node in the argument node-set that is first in document
// order. If the argument node-set is empty or the first node has no
// expanded-name, an empty string is returned. If the argument is
// omitted, it defaults to a node-set with the context node as its only
// member.
//
void CXPath::evalLocal_Name(void) {
   // Obtain the argument
   int nrArgs = -1;
   PCXPathItem item;
   PCNodeList nodes;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_NODESET && !nodes)
         nodes = item->nValue;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs > 1)
      throw(XPE_WRONGNRARGUMENTS);

   if (!nodes) {
      if (ctxtStack->Empty())
         Assert(XPE_STACKEMPTY);

      nodes               = new CNodeList();
      PCXPathItem context = ctxtStack->ItemAtTOS();
      nodes->InsertNode(context->nValue->Item(context->position-1));
      }

   if (nodes->Length() == 0)
      evalStack->PushS("");
   else {
      Pchar p = nodes->Item(0)->NodeValue();
      evalStack->PushS(p);
      delete[] p;
      }
   } // evalLocal_Name

//---------------------------------------------------------------------
// Function: string name(node-set?)
//
// The name function returns a string containing a QName representing
// the expanded-name of the node in the argument node-set that is first
// in document order. The QName must represent the expanded-name with
// respect to the namespace declarations in effect on the node whose
// expanded-name is being represented. Typically, this will be the
// QName that occurred in the XML source. This need not be the case if
// there are namespace declarations in effect on the node that
// associate multiple prefixes with the same namespace. However, an
// implementation may include information about the original prefix in
// its representation of nodes; in this case, an implementation can
// ensure that the returned string is always the same as the QName used
// in the XML source. If the argument node-set is empty or the first
// node has no expanded-name, an empty string is returned. If the
// argument it omitted, it defaults to a node-set with the context node
// as its only member.
//
// NOTE: The string returned by the name function will be the same as
// the string returned by the local-name function except for element
// nodes and attribute nodes.
//
void CXPath::evalName(void) {
   // Obtain the argument
   int nrArgs = -1;
   PCXPathItem item;
   PCNodeList nodes;
   do {
      if (evalStack->Empty())
         Assert(XPE_STACKEMPTY);

      item = evalStack->Pop();
      nrArgs++;

      if (item->type == XP_NODESET && !nodes)
         nodes = item->nValue;
      } while (item->type != XP_LPAREN);

   // Check the number of arguments
   if (nrArgs > 1)
      throw(XPE_WRONGNRARGUMENTS);

   if (!nodes) {
      if (ctxtStack->Empty())
         Assert(XPE_STACKEMPTY);

      nodes               = new CNodeList();
      PCXPathItem context = ctxtStack->ItemAtTOS();
      nodes->InsertNode(context->nValue->Item(context->position-1));
      }

   if (nodes->Length() == 0)
      evalStack->PushS("");
   else {
      Pchar p = nodes->Item(0)->NodeValue();
      evalStack->PushS(p);
      delete[] p;
      }
   } // evalName

//---------------------------------------------------------------------
// Function: string namespace-uri(node-set?)
//
// The namespace-uri function returns the namespace URI of the
// expanded-name of the node in the argument node-set that is first in
// document order. If the argument node-set is empty, the first node
// has no expanded-name, or the namespace URI of the expanded-name is
// null, an empty string is returned. If the argument is omitted, it
// defaults to a node-set with the context node as its only member.
//
// NOTE: The string returned by the namespace-uri function will be
// empty except for element nodes and attribute nodes.
//
void CXPath::evalNamespace_uri(void) {
   if (evalStack->Empty())
      Assert(XPE_STACKEMPTY);

   if (evalStack->TypeAtTOS() != XP_NODESET)
      throw(XPE_NODESETEXPECTED);

   Assert("cant use" == "namespace_uri");
   } // evalNamespace_uri

//=====================================================================
// Conversion methods
//=====================================================================

//---------------------------------------------------------------------
// Note that the user of this method must delete[] the returned memory
// after using it.
//
Pchar CXPath::stringValue(RPCNode _node) {
   if (_node->NodeType() == ELEMENT_NODE ||
       _node->NodeType() == DOCUMENT_NODE) {
      ctxtStack->PushN(_node);

      { // Use the evaluator to collect the text() children of the
        // specified element/document node
         // Save the current expression state
         int hiExp     = iExp;
         PCRecord hExp = new CRecord(exp);
         ushort hop    = op;
         char htoken[sizeLine];
         Strcpy(htoken, stoken, sizeLine);

         // Run eval("descendant::text()") to get the element text
         char myExp[sizeLine];
         sprintf(myExp,
                 " %cdescendant %ctext()",
                 XP_AXISNAME,
                 XP_NODETYPE);
         eval(myExp);

         // restore the pre-predicate expression
         strcpy(stoken, htoken);
         op            = hop;
         iExp          = hiExp;
         exp           = hExp;
         }

      // Remove the nodeList we pushed off the context stack
      ctxtStack->Pop();

      // Get the result of the evaluation
      PCXPathItem item = evalStack->Pop();

      // Verify that the item is a nodeset
      if (item->type != XP_NODESET)
         return 0;

      // Compute the size of the buffer needed to carry the text
      long size = 0;
      for (int i = 0; i < item->nValue->Length(); i++) {
         PCNode node = item->nValue->Item(i);
         Assert(node->NodeType() == TEXT_NODE);
         size += node->Length();
         }

      // Transfer the text into the buffer
      Pchar p = new char[size+1];
      memset(p, 0, size+1);
      size    = 0;
      for (int i = 0; i < item->nValue->Length(); i++) {
         Pchar t = item->nValue->Item(i)->NodeValue();
         strcpy(p+size, t);
         size += strlen(t);
         delete[] t;
         }

      // Return the buffer address
      return p;
      }
   else // NodeValue() works for attributes, text nodes, etc.
      return _node->NodeValue();
   } // stringValue

//---------------------------------------------------------------------
//
double CXPath::numberValue(RPCNode _node) {
   Pchar  str = stringValue(_node);
   double dbl = StrToDouble(str);
   delete[] str;

   return dbl;
   } // numberValue

//---------------------------------------------------------------------
// The boolean function converts its argument to a boolean as follows:
//    * a number is true if and only if it is neither positive or
//      negative zero nor NaN
//    * a node-set is true if and only if it is non-empty
//    * a string is true if and only if its length is non-zero
//    * an object of a type other than the four basic types is
//      converted to a boolean in a way that is dependent on that type.
//
PCXPathItem CXPath::convertToBoolean(RPCXPathItem _item) {
   PCXPathItem item;

   if      (_item->type == XP_NOTYPE)
      Assert(false);
   else if (_item->type == XP_BOOLEAN)
      item = new CXPathItem(_item->bValue);
   else if (_item->type == XP_NUMBER)
      item = new CXPathItem(_item->dValue !=  0.0 &&
                            _item->dValue != -0.0);
   else if (_item->type == XP_STRING)
      item = new CXPathItem(strlen(_item->sValue) > 0);
   else if (_item->type == XP_NODESET)
      item = new CXPathItem(_item->nValue->Length() > 0);

   return _item;
   } // convertToBoolean

//---------------------------------------------------------------------
// Function: number number(object?)
//
// The number function converts its argument to a number as follows:
//    * a string that consists of optional whitespace followed by an
//      optional minus sign followed by a Number followed by whitespace
//      is converted to the IEEE 754 number that is nearest (according
//      to the IEEE 754 round-to-nearest rule) to the mathematical
//      value represented by the string; any other string is converted
//      to NaN
//    * boolean true is converted to 1; boolean false is converted to 0
//    * a node-set is first converted to a string as if by a call to
//      the string function and then converted in the same way as a
//      string argument
//    * an object of a type other than the four basic types is
//      converted to a number in a way that is dependent on that type
//
// If the argument is omitted, it defaults to a node-set with the
// context node as its only member.
//
// NOTE: The number function should not be used for conversion of
// numeric data occurring in an element in an XML document unless the
// element is of a type that represents numeric data in a language-
// neutral format (which would typically be transformed into a
// language-specific format for presentation to a user). In addition,
// the number function cannot be used unless the language-neutral
// format used by the element is consistent with the XPath syntax for a
// Number.
//
PCXPathItem CXPath::convertToNumber(RPCXPathItem _item) {
   PCXPathItem item;

   if      (_item->type == XP_NOTYPE)
      Assert(false);
   else if (_item->type == XP_BOOLEAN)
      item = new CXPathItem(_item->bValue ? 1.0 : 0.0);
   else if (_item->type == XP_NUMBER)
      item = new CXPathItem(_item->dValue);
   else if (_item->type == XP_STRING)
      item = new CXPathItem(StrToDouble(_item->sValue));
   else if (_item->type == XP_NODESET)
      item = new CXPathItem(numberValue(_item->nValue->Item(0)));

   return item;
   } // convertToNumber

//---------------------------------------------------------------------
// Function: string string(object?)
//
// The string function converts an object to a string as follows:
//    * A node-set is converted to a string by returning the string-
//      value of the node in the node-set that is first in document
//      order. If the node-set is empty, an empty string is returned.
//    * A number is converted to a string as follows
//       o NaN is converted to the string NaN
//       o positive zero is converted to the string 0
//       o negative zero is converted to the string 0
//       o positive infinity is converted to the string Infinity
//       o negative infinity is converted to the string -Infinity
//       o if the number is an integer, the number is represented in
//         decimal form as a Number with no decimal point and no
//         leading zeros, preceded by a minus sign (-) if the number is
//         negative
//       o otherwise, the number is represented in decimal form as a
//         Number including a decimal point with at least one digit
//         before the decimal point and at least one digit after the
//         decimal point, preceded by a minus sign (-) if the number is
//         negative; there must be no leading zeros before the decimal
//         point apart possibly from the one required digit immediately
//         before the decimal point; beyond the one required digit
//         after the decimal point there must be as many, but only as
//         many, more digits as are needed to uniquely distinguish the
//         number from all other IEEE 754 numeric values.
//    * The boolean false value is converted to the string false. The
//      boolean true value is converted to the string true.
//    * An object of a type other than the four basic types is
//      converted to a string in a way that is dependent on that type.
//
// If the argument is omitted, it defaults to a node-set with the
// context node as its only member.
//
// NOTE: The string function is not intended for converting numbers
// into strings for presentation to users. The format-number function
// provides this functionality.
//
PCXPathItem CXPath::convertToString(RPCXPathItem _item) {
   PCXPathItem item;

   if      (_item->type == XP_NOTYPE)
      Assert(false);                       // probably a bug in my code

   else if (_item->type == XP_BOOLEAN)
      if (_item->bValue)
         item = new CXPathItem("true");
      else
         item = new CXPathItem("false");

   else if (_item->type == XP_NUMBER)
      if (_item->dValue == 0)
         item = new CXPathItem("0");
      else {
         char work[sizeName];
         sprintf(work, "%G", _item->dValue);
         item = new CXPathItem(work);
         }

   else if (_item->type == XP_STRING)
      item = new CXPathItem(_item->sValue);

   else if (_item->type == XP_NODESET)
      if (_item->nValue->Length() == 0)
         item      = new CXPathItem("");
      else {
         Pchar str = stringValue(_item->nValue->Item(0));
         item      = new CXPathItem(str);
         delete[] str;
         }

   return item;
   } // convertToString

//=====================================================================
// Axis Traversal methods
//=====================================================================

//---------------------------------------------------------------------
//
PCNodeList CXPath::travelOnAxis(PCNode _contextNode,
                                Pchar  _axisName,
                                Pchar  _name) {
   Pchar axisName = _axisName+1;
 
   if      (strcmp(axisName, "ancestor")           == 0)
      return ancestor        (_contextNode, _name);
   else if (strcmp(axisName, "ancestor-or-self")   == 0)
      return ancestorOrSelf  (_contextNode, _name);
   else if (strcmp(axisName, "attribute")          == 0)
      return attribute       (_contextNode, _name);
   else if (strcmp(axisName, "child")              == 0)
      return child           (_contextNode, _name);
   else if (strcmp(axisName, "descendant")         == 0)
      return descendant      (_contextNode, _name);
   else if (strcmp(axisName, "descendant-or-self") == 0)
      return descendantOrSelf(_contextNode, _name);
   else if (strcmp(axisName, "following")          == 0)
      return following       (_contextNode, _name);
   else if (strcmp(axisName, "following-sibling")  == 0)
      return followingSibling(_contextNode, _name);
   else if (strcmp(axisName, "nameSpace")          == 0)
      return nameSpace       (_contextNode, _name);
   else if (strcmp(axisName, "parent")             == 0)
      return parent          (_contextNode, _name);
   else if (strcmp(axisName, "preceding")          == 0)
      return preceding       (_contextNode, _name);
   else if (strcmp(axisName, "preceding-sibling")  == 0)
      return precedingSibling(_contextNode, _name);
   else if (strcmp(axisName, "self")               == 0)
      return self            (_contextNode, _name);

   throw(XPE_AXISNAMEUNKNOWN);
   } // travelOnAxis

//---------------------------------------------------------------------
// Recurse into the DOM proceeding in document order
//
void CXPath::downInDocOrder(PCNodeList _list, PCNode _node, Pchar _name) {
   PCNode node = _node->FirstChild();

   while (node) {
      if      (node->m_type == TEXT_NODE &&
               strcmp(_name, "text()")    == 0)
         _list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(_name, "comment()") == 0)
         _list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(_name, "*")        == 0 ||
                strcmp(_name, node->text) == 0))
         _list->InsertNode(node);

      else if (strcmp(_name, "node()")    == 0)
         _list->InsertNode(node);

      if (node->m_type == ELEMENT_NODE)
         downInDocOrder(_list, node, _name);

      node = node->NextSibling();
      }
   } // downInDocOrder

//---------------------------------------------------------------------
// Recurse into the DOM proceeding in reverse document order
//
void CXPath::downInBackOrder(PCNodeList _list, PCNode _node, Pchar _name) {
   PCNode node = _node->LastChild();

   while (node) {
      if      (node->m_type == TEXT_NODE &&
               strcmp(_name, "text()")    == 0)
         _list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(_name, "comment()") == 0)
         _list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE) {
         downInBackOrder(_list, node, _name);

         if (strcmp(_name, "*")        == 0 ||
             strcmp(_name, "node()")   == 0 ||
             strcmp(_name, node->text) == 0)
            _list->InsertNode(node);
         }

      else if (strcmp(_name, "node()")    == 0)
         _list->InsertNode(node);

      node = node->PreviousSibling();
      }
   } // downInBackOrder

//---------------------------------------------------------------------
// ancestor
//
PCNodeList CXPath::ancestor(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();

   Pchar  name = _name+1;
   PCNode node = _contextNode->ParentNode();
   while (node) {
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, "node()")   == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      node = node->ParentNode();
      }

   return list;
   } // ancestor

//---------------------------------------------------------------------
// ancestor-or-self
//
PCNodeList CXPath::ancestorOrSelf(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();

   Pchar  name = _name+1;
   PCNode node = _contextNode;
   while (node) {
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, "node()")   == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      node = node->ParentNode();
      }

   return list;
   } // ancestor_or_self

//---------------------------------------------------------------------
// attribute
//
PCNodeList CXPath::attribute(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();

   Pchar  name          = _name+1;
   PCNamedNodeMap attrs = _contextNode->Attributes();
   if (!attrs)
      return list;
 
   for (int i = 0; i < attrs->Length(); i++) {
      PCNode attr = attrs->Item(i);
      if (strcmp(name, "*")        == 0 ||
          strcmp(name, "node()")   == 0 ||
          strcmp(name, attr->text) == 0)
         list->InsertNode(attr);
      }

   return list;
   } // attribute

//---------------------------------------------------------------------
// child
//
PCNodeList CXPath::child(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();

   Pchar  name     = _name+1;
   PCNode node     = _contextNode->FirstChild();

   while (node) {
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      else if (strcmp(name, "node()")    == 0)
         list->InsertNode(node);

      node = node->NextSibling();
      }

   // If extend requested and no matching child node, then add a new child
   // element
   if (extend && list->Length() == 0) {
      PCElement el = _contextNode->OwnerDocument()->CreateElement(name);
      node         = TYPECAST(el, Node);
      _contextNode->AppendChild(node);
      list->InsertNode(node);
      }

   return list;
   } // child

//---------------------------------------------------------------------
// descendant
//
PCNodeList CXPath::descendant(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();

   Pchar  name = _name+1;
   PCNode node = _contextNode->FirstChild();
   while (node) {
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      else if (strcmp(name, "node()")    == 0)
         list->InsertNode(node);

      if (node->m_type == ELEMENT_NODE)
         downInDocOrder(list, node, name);

      node = node->NextSibling();
      }

   return list;
   } // descendant

//---------------------------------------------------------------------
// descendant-or-self
//
PCNodeList CXPath::descendantOrSelf(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();

   Pchar  name = _name+1;
   PCNode node = _contextNode;
   if (node) {
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      else if (strcmp(name, "node()")    == 0)
         list->InsertNode(node);

      if (node->m_type == ELEMENT_NODE ||
          node->m_type == DOCUMENT_NODE)
         downInDocOrder(list, node, name);

      }

   return list;
   } // descendantOrSelf

//---------------------------------------------------------------------
// following
//
PCNodeList CXPath::following(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();

   Pchar  name = _name+1;
   PCNode node = _contextNode->NextSibling();
   while (node) {
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      else if (strcmp(name, "node()")    == 0)
         list->InsertNode(node);

      if (node->m_type == ELEMENT_NODE)
         downInDocOrder(list, node, name);

      node = node->NextSibling();
      }

   return list;
   } // following

//---------------------------------------------------------------------
// following-sibling
//
PCNodeList CXPath::followingSibling(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();
   Pchar  name     = _name+1;
   PCNode node     = _contextNode->NextSibling();

   while (node) {
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      else if (strcmp(name, "node()")    == 0)
         list->InsertNode(node);

      node = node->NextSibling();
      }

   return list;
   } // followingSibling

//---------------------------------------------------------------------
// namespace
// TBD
//
PCNodeList CXPath::nameSpace(PCNode _contextNode, Pchar _name) {
   _contextNode;
   _name;

   PCNodeList list = new CNodeList();

   return list;
   } // nameSpace

//---------------------------------------------------------------------
// paren
//
PCNodeList CXPath::parent(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();
   Pchar  name     = _name+1;
   PCNode node     = _contextNode->ParentNode();

   if (node)
      if      (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      else if (strcmp(name, "node()")    == 0)
         list->InsertNode(node);

   return list;
   } // parent

//---------------------------------------------------------------------
// preceding
//
PCNodeList CXPath::preceding(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();
   Pchar  name     = _name+1;
   PCNode node     = _contextNode->PreviousSibling();

   while (node) {
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE) {
         downInBackOrder(list, node, name);
         if (strcmp(name, "*")        == 0 ||
             strcmp(name, "node()")   == 0 ||
             strcmp(name, node->text) == 0)
         list->InsertNode(node);
         }

      else if (strcmp(name, "node()")    == 0)
         list->InsertNode(node);

      node = node->PreviousSibling();
      }

   return list;
   } // preceding

//---------------------------------------------------------------------
// preceding-sibling
//
PCNodeList CXPath::precedingSibling(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();
   Pchar  name     = _name+1;
   PCNode node     = _contextNode->PreviousSibling();

   while (node) {
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      else if (strcmp(name, "node()")    == 0)
         list->InsertNode(node);

      node = node->PreviousSibling();
      }

   return list;
   } // precedingSibling

//---------------------------------------------------------------------
// self
//
PCNodeList CXPath::self(PCNode _contextNode, Pchar _name) {
   PCNodeList list = new CNodeList();
   Pchar  name     = _name+1;
   PCNode node     = _contextNode;

   if (node)
      if      ((node->m_type == TEXT_NODE &&
               strcmp(name, "text()")    == 0))
         list->InsertNode(node);

      else if (node->m_type == COMMENT_NODE &&
               strcmp(name, "comment()") == 0)
         list->InsertNode(node);

      else if (node->m_type == ELEMENT_NODE &&
               (strcmp(name, "*")        == 0 ||
                strcmp(name, node->text) == 0))
         list->InsertNode(node);

      else if (strcmp(name, "node()")    == 0)
         list->InsertNode(node);

   return list;
   } // self

//=====================================================================
// Evaluator tokenizer
//=====================================================================

//---------------------------------------------------------------------
//
void CXPath::initEvalTokenizer(Pchar _postFixExp) {
   iExp = 1;
   exp  = new CRecord(_postFixExp);
   } // initEvalTokenizer

//---------------------------------------------------------------------
//
bool CXPath::getEvalToken(void) {
   op        = XP_EMPTY;
   stoken[0] = 0;

   if (iExp > exp->Number())
      return false;

   exp->GetField(iExp++, stoken, sizeLine);
   op = stoken[0] & 0xff;

   return true;
   } // getEvalToken

//=====================================================================
// Parser
//=====================================================================

//---------------------------------------------------------------------
//
Pchar CXPath::Parse(Pchar _path) {
   char work[sizeString];
   Strcpy(work, _path, sizeString);
   parse (work, work,  sizeString);

   Pchar p = 0;
   STRINIT(p, work);
   return p;
   } // Parse

//---------------------------------------------------------------------
// The expression parser converts an XPath expression from infix
// notation to postfix notation.
//
bool CXPath::parse(Pchar _inFixExp, Pchar _postFixExp, ushort _size) {
   Assert(_postFixExp);
   Assert(_size != 0);

   // Check for something to do
   if (StrEmpty(_inFixExp)) {
      memset(_postFixExp, 0, _size);
      return true;
      }

   // Prepare the input and output expression areas (they can be the
   // same area)
   STRINIT(inFixExp, _inFixExp);
   memset(_postFixExp, 0, _size);

   delete[] postFixExp;
   postFixExp = new char[sizeString];
   memset(postFixExp, 0, sizeString);

   // Initialize the tokenizer
   initParseTokenizer(inFixExp);

   // Parse the expression
   try {
      err = XPE_OK;
      do {
         getParseToken();
         expr();
         } while (!atEnd);
      }
   catch (ulong _err) {
      err = _err;
      char token[sizeLine];
      sprintf(token, "%cERR=%ld>", XP_PARSEERROR, _err);
      outputPostfix(token);
      }

   // Return the postfix expression result
   if (strlen(postFixExp) >= _size) {
      delete[] inFixExp;
      inFixExp   = 0;
      delete[] postFixExp;
      postFixExp = 0;
      return false;
      }
   else {
      strncpy(_postFixExp, postFixExp, _size);
      delete[] inFixExp;
      inFixExp   = 0;
      delete[] postFixExp;
      postFixExp = 0;
      return true;
      }
   } // parse

//---------------------------------------------------------------------
// [14] Expr ::= OrExpr
//
void CXPath::expr(void) {
   orExpr();
   } // expr

//---------------------------------------------------------------------
// [21] OrExpr ::= AndExpr | OrExpr 'or' AndExpr
//
void CXPath::orExpr(void) {
   andExpr();

   // Loop to process subsequent andExprs separated by OR-operators.
   while (op == XP_OR) {
      getParseToken();
      andExpr();

      outputPostfix(XP_OR);
      }
   } // orExpr

//---------------------------------------------------------------------
// [22] AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr
//
void CXPath::andExpr(void) {
   equalityExpr();

   // Loop to process subsequent equalityExprs separated by AND-operators.
   while (op == XP_AND) {
      getParseToken();
      equalityExpr();

      outputPostfix(XP_AND);
      }
   } // andExpr

//---------------------------------------------------------------------
// [23] EqualityExpr ::= RelationalExpr
//                       | EqualityExpr '=' RelationalExpr
//                       | EqualityExpr '!=' RelationalExpr
// note: A != B = C is allowed ? Answer from James, yes with
//    (RelationalExpr = RelationalExpr) = RelationalExpr
//    (RelationalExpr != RelationalExpr) != RelationalExpr
//    which is basically what got implemented.
//
void CXPath::equalityExpr(void) {
   relationalExpr();

   // Loop to process subsequent relationalExprs separated by eq-operators
   while (op == XP_EQUAL ||
          op == XP_NOTEQUAL) {

      uchar holdOp = op;

      getParseToken();
      relationalExpr();

      outputPostfix(holdOp);
      }
   } // equalityExpr

//---------------------------------------------------------------------
// [24] RelationalExpr ::= AdditiveExpr
//                         | RelationalExpr '<' AdditiveExpr
//                         | RelationalExpr '>' AdditiveExpr
//                         | RelationalExpr '<=' AdditiveExpr
//                         | RelationalExpr '>=' AdditiveExpr
// note: A <= B > C is allowed ? Answer from James, yes with
//    (AdditiveExpr <= AdditiveExpr) > AdditiveExpr
//    which is basically what got implemented.
//
void CXPath::relationalExpr(void) {
   additiveExpr();

   // Loop to process subsequent additiveExprs separated by eq-operators
   while (op == XP_LESSTHAN    ||
          op == XP_LESSOREQUAL ||
          op == XP_GREATERTHAN ||
          op == XP_GREATEROREQUAL) {

      uchar holdOp = op;

      getParseToken();
      additiveExpr();

      outputPostfix(holdOp);
      }
   } // relationalExpr

//---------------------------------------------------------------------
// [25] AdditiveExpr ::= MultiplicativeExpr
//                       | AdditiveExpr '+' MultiplicativeExpr
//                       | AdditiveExpr '-' MultiplicativeExpr
//
void CXPath::additiveExpr(void) {
   multiplicativeExpr();

   while (op == XP_PLUS ||
          op == XP_MINUS) {

      uchar holdOp = op;

      getParseToken();
      multiplicativeExpr();

      outputPostfix(holdOp);
      }
   } // additiveExpr

//---------------------------------------------------------------------
// [26] MultiplicativeExpr ::= UnaryExpr
//                      | MultiplicativeExpr MultiplyOperator UnaryExpr
//                      | MultiplicativeExpr 'div' UnaryExpr
//                      | MultiplicativeExpr 'mod' UnaryExpr
//
void CXPath::multiplicativeExpr(void) {
   unaryExpr();

   while (op == XP_MUL ||
          op == XP_DIV ||
          op == XP_MOD) {

      uchar holdOp = op;

      getParseToken();
      unaryExpr();

      outputPostfix(holdOp);
      }
   } // multiplicativeExpr

//---------------------------------------------------------------------
// [27] UnaryExpr ::= UnionExpr
//                    | '-' UnaryExpr
//
void CXPath::unaryExpr(void) {
   bool found = false;
   bool minus = false;

   while (op == XP_MINUS) {
      found = true;
      minus = !minus;
      getParseToken();
      }

   unionExpr();

   if (found)
      if (minus)
         outputPostfix(XP_UNARYMINUS);
   } // unaryExpr

//---------------------------------------------------------------------
// [18] UnionExpr ::= PathExpr
//                    | UnionExpr '|' PathExpr
//
void CXPath::unionExpr(void) {
   pathExpr();

   while (op == XP_UNION) {
      getParseToken();
      pathExpr();

      outputPostfix(XP_UNION);
      }
   } // unionExpr

//---------------------------------------------------------------------
// [19] pathExpr ::= LocationPath
//                   | FilterExpr
//                   | FilterExpr '/' RelativeLocationPath
//                   | FilterExpr '//' RelativeLocationpath
//
// A token is a primary Expr if its first character is one of the the
// following characters:
//
//    $, ", ', (, digit
//
// or if it is a functionName
//
void CXPath::pathExpr(void) {
   if (!filterExpr()) {
      locationPath();
      return;
      }

   if      (op == XP_PATH) {
      outputPostfix(XP_PATH);

      getParseToken();
      relativeLocationPath(1);
      }
   else if (op == XP_DBLPATH) {
      outputPostfix(XP_PATH);

      char work[32];
      sprintf(work, "%cdescendant-or-self", XP_AXISNAME);
      outputPostfix(work);
      sprintf(work, "%cnode()", XP_NODETYPE);
      outputPostfix(work);

      getParseToken();

      if (op != XP_EMPTY) {
         outputPostfix(XP_PATH);
         relativeLocationPath(2);
         }
      else
         relativeLocationPath(1);
      }
   } // pathExpr

//---------------------------------------------------------------------
// [20] FilterExpr ::=             PrimaryExpr
//                                 | FilterExpr Predicate (*)TBD
//
bool CXPath::filterExpr(void) {
   if (!primaryExpr())
      return false;

   while (op == XP_LBOX)
      predicateExpr();

   return true;
   } // filterExpr

//---------------------------------------------------------------------
// [15] PrimaryExpr ::=            VariableReference
//                                 | '(' Expr ')'
//                                 | Literal
//                                 | Number
//                                 | FunctionCall
//
bool CXPath::primaryExpr(void) {
   char token[sizeLine];

   if (op == XP_VARIABLE) {
      if (strlen(stoken) == 1)
         throw(XPE_VARNAMEEXPECTED);

      sprintf(token, "%c%s", XP_VARIABLE, stoken);
      outputPostfix(token);
      getParseToken();
      return true;
      }

   if (op == XP_LPAREN) {
      getParseToken();
      expr();

      if (op != XP_RPAREN)
         throw(XPE_RPARENEXPECTED);
      getParseToken();
      return true;
      }

   if (op == XP_QUOTEDSTR) {
      sprintf(token, "%c%s", XP_QUOTEDSTR, stoken);
      outputPostfix(token);
      getParseToken();
      return true;
      }

   if (op == XP_NUMBER) {
      sprintf(token, "%c%s", XP_NUMBER, stoken);
      outputPostfix(token);
      getParseToken();
      return true;
      }

   if (op == XP_FUNNAME) {
      functionCall();
      return true;
      }

   return false;
   } // primaryExpr

//---------------------------------------------------------------------
// [16] FunctionCall ::= FunctionName
//                        '(' ( Argument ( ',' Argument )* )? ')'
//
// emits
//    argExpr1 , argExpr2 , ... , argExprN )functionName
//
void CXPath::functionCall(void) {
   char token[sizeLine];

   sprintf(token, "%c%s", XP_FUNNAME, stoken);

   getParseToken();
   if (op != XP_LPAREN)
      throw(XPE_LPARENEXPECTED);

   // Marks the end of the list of function parameters for the
   // evaluator
   outputPostfix(XP_LPAREN);
 
   do {
      getParseToken();
      if (op != XP_RPAREN &&
          op != XP_COMMA)
         expr();
      } while (op == XP_COMMA);

   if (op != XP_RPAREN)
      throw(XPE_RPARENEXPECTED);

   outputPostfix(token);
   getParseToken();
   } // functionCall

//---------------------------------------------------------------------
// [1]  LocationPath ::=           RelativeLocationPath
//                                 | AbsoluteLocationPath
// [2]  AbsoluteLocationPath ::=   '/' RelativeLocationPath?
//                                 | AbbreviatedAbsoluteLocationPath
// [10] AbbreviatedAbsoluteLocationPath ::=
//                                 '//' RelativeLocationPath
//
void CXPath::locationPath(void) {
   if      (op == XP_PATH) {
      outputPostfix(XP_ROOT);

      getParseToken();
      relativeLocationPath(1);
      }

   else if (op == XP_DBLPATH) {
      outputPostfix(XP_ROOT);

      char token[sizeName];
      sprintf(token, "%cdescendant-or-self %cnode()", XP_AXISNAME,
                                                      XP_NODETYPE);
      outputPostfix(token);

      getParseToken();

      if (op != XP_EMPTY) {
         outputPostfix(XP_PATH);
         relativeLocationPath(2);
         }
      else
         relativeLocationPath(1);
      }

   else
      relativeLocationPath(0);
   } // locationPath

//---------------------------------------------------------------------
// [3]  RelativeLocationPath ::=   Step
//                                 | RelativeLocationPath '/' Step
//                                 | AbbreviatedRelativeLocationPath
// [11] AbbreviatedRelativeLocationPath ::=
//                                 RelativeLocationPath '//' Step
//
void CXPath::relativeLocationPath(ushort _steps) {
   if (op == XP_EMPTY) {
      if (_steps > 0) {
         char token[32];
         sprintf(token, "%c%d", XP_UNWIND, _steps);
         outputPostfix(token);
         }
      return;
      }

   step();

   while (true)
      if      (op == XP_PATH) {
         outputPostfix(XP_PATH);
         _steps++;

         getParseToken();

         if (op == XP_EMPTY)
            throw(XPE_UNEXPECTEDEND);
 
         step();
         }
      else if (op == XP_DBLPATH) {
         outputPostfix(XP_PATH);
         _steps++;

         char token[32];
         sprintf(token, "%cdescendant-or-self", XP_AXISNAME);
         outputPostfix(token);
         sprintf(token, "%cnode()", XP_NODETYPE);
         outputPostfix(token);

         getParseToken();

         if (op != XP_EMPTY) {
            outputPostfix(XP_PATH);
            _steps++;
            step();
            }
         }
      else if (_steps > 0) {
         char token[32];
         sprintf(token, "%c%d", XP_UNWIND, _steps);
         outputPostfix(token);
         return;
         }
      else
         return;
   } // relativeLocationPath

//---------------------------------------------------------------------
// [4]  Step ::=                   AxisSpecifier NodeTest Predicate*
//                                 | AbbreviatedStep
//
void CXPath::step(void) {
   // Parse the AbbreviatedStep part
   if (abbreviatedStep())
      return;

   // Parse the AxisName part
   axisName();

   // Parse the NodeTest part
   nodeTest();

   // Parse the Predicate part
   while (op == XP_LBOX)
      predicateExpr();
   } // step

//---------------------------------------------------------------------
// [12] AbbreviatedStep ::=        '.'
//                                 | '..'
//
bool CXPath::abbreviatedStep(void) {
   char token[32];

   if (op == XP_DOT) {
      sprintf(token, "%cself", XP_AXISNAME);
      outputPostfix(token);
      sprintf(token, "%cnode()", XP_NODETYPE);
      outputPostfix(token);
      getParseToken();
      return true;
      }

   if (op == XP_DBLDOT) {
      sprintf(token, "%cparent", XP_AXISNAME);
      outputPostfix(token);
      sprintf(token, "%cnode()", XP_NODETYPE);
      outputPostfix(token);
      getParseToken();
      return true;
      }

   return false;
   } // abbreviatedStep

//---------------------------------------------------------------------
// [5]  AxisSpecifier ::=          AxisName '::'
//                                 | AbbreviatedAxisSpecifier
// [6]  AxisName ::=               'ancestor'
//                                 | 'ancestor-or-self'
//                                 | 'attribute'
//                                 | 'child'
//                                 | 'descendant'
//                                 | 'descendant-or-self'
//                                 | 'following'
//                                 | 'following-sibling'
//                                 | 'namespace'
//                                 | 'parent'
//                                 | 'preceding'
//                                 | 'preceding-sibling'
//                                 | 'self'
//
void CXPath::axisName(void) {
   char token[sizeLine];

   // Parse the abbreviatedAxisSpecifier part
   if (abbreviatedAxisSpecifier())
      return;

   // Parse the AxisName part
   if (op == XP_AXISNAME) {
      sprintf(token, "%c%s", XP_AXISNAME, stoken);
      outputPostfix(token);

      getParseToken();
      if (op != XP_DBLCOLON)
         throw(XPE_DOUBLECOLONEXPECTED);

      getParseToken();
      }

   else if (op == XP_LBOX) {
      sprintf(token, "%cself",  XP_AXISNAME);
      outputPostfix(token);
      }

   else if (op != XP_EMPTY) {
      sprintf(token, "%cchild", XP_AXISNAME);
      outputPostfix(token);
      }
   } // axisName

//---------------------------------------------------------------------
// [13] AbbreviatedAxisSpecifier ::= '@'?
//
bool CXPath::abbreviatedAxisSpecifier(void) {
   char token[sizeLine];

   if (op == XP_ATTR) {
      sprintf(token, "%cattribute", XP_AXISNAME);
      outputPostfix(token);
      getParseToken();
      return true;
      }

   return false;
   } // abbreviatedAxisSpecifier

//---------------------------------------------------------------------
// [7]  NodeTest ::=               NameTest
//                                 | NodeType '(' ')'
//                                 | 'processing-instruction'
//                                   '(' Literal ')'
//
void CXPath::nodeTest(void) {
   char token[sizeLine];

   // Parse the NameTest part
   if (nameTest())
      return;

   // Parse the NodeType part
   if (nodeType())
      return;

   // Handle the QName part of the NameTest part
   if      (op == XP_QNAME) {
      sprintf(token, "%c%s", XP_QNAME, stoken);
      outputPostfix(token);
      getParseToken();
      }

   else if (op == XP_LBOX) {
      sprintf(token, "%cnode()", XP_NODETYPE);
      outputPostfix(token);
      }

   else
      throw(XPE_QNAMEEXPECTED);
   } // nodeTest

//---------------------------------------------------------------------
// [37] NameTest ::=               '*'
//                                 | NCName ':' '*'
//                                 | QName
//
bool CXPath::nameTest(void) {
   char token[sizeLine];

   if (op == XP_MUL) {
      isOp = false;
      op   = XP_ALL;

      sprintf(token, "%c*", XP_ALL);
      outputPostfix(token);

      getParseToken();
      return true;
      }

   // Parse the NCName ':*' NameTest - TBD
   if (op == XP_ALLNAME) {
      sprintf(token, "%c%s", XP_ALLNAME, stoken);
      outputPostfix(token);           // changed it to the form *NCName

      getParseToken();
      if (op != XP_COLON)
         throw(XPE_PARSEERROR);
      getParseToken();
      if (op != XP_MUL)
         throw(XPE_PARSEERROR);
      return true;
      }

   return false;
   } // nameTest

//---------------------------------------------------------------------
// [38] NodeType ::=               'comment'
//                                 | 'text'
//                                 | 'processing-instruction'
//                                 | 'node'
// (also handles the special case:
//    'processing-instruction' '(' Literal ')'
//
// emits
//    literal (processing-instruction
// or
//    (nodeType
//
bool CXPath::nodeType(void) {
   char htoken[sizeLine];

   if (op == XP_NODETYPE) {
      sprintf(htoken, "%c%s()", XP_NODETYPE, stoken);

      getParseToken();

      // Parse open parenthesis
      if (op != XP_LPAREN)
         throw(XPE_LPARENEXPECTED);
      getParseToken();

      // Parse close parenthesis
      if (op != XP_RPAREN)
         if (strcmp(htoken+1, "processing-instruction()") == 0) {
            if (op != XP_QUOTEDSTR)
               throw(XPE_LITERALEXPECTED);

            // Save the literal (quoted string)
            char token[sizeLine];
            sprintf(token, "%c%s", XP_QUOTEDSTR, stoken);
            outputPostfix(token);

            getParseToken();
            if (op != XP_RPAREN)
               throw(XPE_RPARENEXPECTED);
            }
         else
            throw(XPE_RPARENNOTEXPECTED);

      // Save the nodeType
      outputPostfix(htoken);
      getParseToken();

      return true;
      }

   return false;
   } // nodeType

//---------------------------------------------------------------------
// [8]  Predicate ::=              '[' PredicateExpr ']'
//
void CXPath::predicateExpr(void) {
   outputPostfix(XP_LBOX);

   getParseToken();
   expr();

   if (op != XP_RBOX)
      throw(XPE_RBRACKETEXPECTED);

   outputPostfix(XP_RBOX);

   getParseToken();
   } // predicateExpr

//---------------------------------------------------------------------
// Append the string preceded by a blank to the postfix buffer.
//
void CXPath::outputPostfix(Pchar _symbol) {
   ChrCat(postFixExp, spacer, sizeString);
   Strcat(postFixExp, _symbol, sizeString);
   } // outputPostfix

//---------------------------------------------------------------------
// Append the operator preceded by a blank to the postfix buffer.
//
void CXPath::outputPostfix(char _op) {
   ChrCat(postFixExp, spacer, sizeString);
   ChrCat(postFixExp, _op, sizeString);
   } // outputPostfix

//=====================================================================
// Parser tokenizer
//=====================================================================

//---------------------------------------------------------------------
// Initialize the tokenizer
//
void CXPath::initParseTokenizer(Pchar _inFixExp) {
   pInFixExp = _inFixExp;
   delete[] stoken;
   stoken    = new char[sizeString];
   atEnd     = false;
   getCh();
   } // initParseTokenizer

//---------------------------------------------------------------------
// Get a token
//
void CXPath::getParseToken(void) {
   // state of last token
   wasOp     = isOp;
   lop       = op;

   // clear the token string
   isOp      = false;
   op        = XP_NOTKNOWN;
   stoken[0] = 0;

   skipSpaces();

   if (atEnd) {
      op = XP_EMPTY;
      return;
      }

   if      ((ch >= 'A' && ch <= 'Z') ||
            (ch >= 'a' && ch <= 'z') ||
            (ch == '_'))
      getSymbol();
   else if (ch == '$')
      getVariable();
   else if (ch >= '0' && ch <= '9')
      getNumber();
   else if (ch == strQuote1 ||                              // "string"
            ch == strQuote2)                                // 'string'
      getQuotedString();
   else
      getOp();
   } // getParseToken

//---------------------------------------------------------------------
// Get a symbol
//
void CXPath::getSymbol(void) {
   op = XP_QNAME;

   int nrColons = 0;
   ChrCat(stoken, ch, sizeString);
   while (getCh() && (ch == '_' ||
                      ch == '-' ||
                      ch == '.' ||
                      ch == ':' ||
                      isalnum(ch))) {
      if (ch == ':') {
         if (++nrColons > 1)
            throw(XPE_TOOMANYCOLONS);
         if      (chkCh() == ':') {
            op = XP_AXISNAME;
            break;
            }
         else if (chkCh() == '*') {
            op = XP_ALLNAME;
            break;
            }
         }

      ChrCat(stoken, ch, sizeString);
      }

   // If there is a preceding token and the preceding token is not one
   // of @, ::, (, [, , or an Operator, then a * must be recognized as
   // a MultiplyOperator and an NCName must be recognized as an
   // OperatorName. Otherwise not.
   if (lop != XP_ATTR     &&
       lop != XP_DBLCOLON &&
       lop != XP_LPAREN   &&
       lop != XP_LBOX     &&
       lop != XP_COMMA    &&
       !wasOp)
      if      (strcmp(stoken, "and") == 0) {
         op = XP_AND;
         return;
         }
      else if (strcmp(stoken, "or")  == 0) {
         op = XP_OR;
         return;
         }
      else if (strcmp(stoken, "mod") == 0) {
         op = XP_MOD;
         return;
         }
      else if (strcmp(stoken, "div") == 0) {
         op = XP_DIV;
         return;
         }

   skipSpaces();

   // Check if the symbol is a FunctionName or NodeType
   if (ch == '(')
      if (strcmp(stoken, "comment")                == 0 ||
          strcmp(stoken, "node")                   == 0 ||
          strcmp(stoken, "text")                   == 0 ||
          strcmp(stoken, "processing-instruction") == 0)
         op = XP_NODETYPE;
      else
         op = XP_FUNNAME;
   } // getSymbol

//---------------------------------------------------------------------
// Get a variable
//
void CXPath::getVariable(void) {
   op = XP_VARIABLE;

   while (getCh() && (ch == '_' ||
                      ch == '-' ||
                      ch == '.' ||
                      isalnum(ch)))
      ChrCat(stoken, ch, sizeString);
   } // getVariable

//---------------------------------------------------------------------
// Get a number
//
void CXPath::getNumber(void) {
   op = XP_NUMBER;

   ChrCat(stoken, ch, sizeString);
   while (getCh() && (isdigit(ch) ||
                      ch == '.'))
      ChrCat(stoken, ch, sizeString);
   } // getNumber

//---------------------------------------------------------------------
// Get a quoted string.
//
void CXPath::getQuotedString(void) {
   op = XP_QUOTEDSTR;

   char quote = ch;
   while (getCh() && ch != quote) {
      if (ch == ' ')                                  // fill in spaces
         ch = XP_SPACE;
      ChrCat(stoken, ch, sizeString);
      }

   // Check for closing quote
   if (ch == 0)
      throw(XPE_UNEXPECTEDEND);

   getCh();
   } // getQuotedString

//---------------------------------------------------------------------
// Get an operator string.
// (all are one-char or two-char operators)
// [32] Operator                   OperatorName
//                                 | MultiplyOperator
//                                 | '/' | '//' | '|' | '+' | '-' | '='
//                                 | '!=' | '<' | '<=' | '>' | '>='
//
void CXPath::getOp(void) {
   ChrCat(stoken, ch, sizeString);

   switch (ch) {
      case '|':
         getCh();
         op   = XP_UNION;
         isOp = true;
         break;
      case '!':
         getCh();
         if (ch != '=')
            throw(XPE_EQUALSIGNEXPECTED);
         ChrCat(stoken, ch, sizeString);
         getCh();
         op   = XP_NOTEQUAL;
         isOp = true;
         break;
      case '=':
         getCh();
         op   = XP_EQUAL;
         isOp = true;
         break;
      case '<':
         getCh();
         if (ch == '=') {
            ChrCat(stoken, ch, sizeString);
            getCh();
            op = XP_LESSOREQUAL;
            }
         else
            op = XP_LESSTHAN;
         isOp = true;
         break;
      case '>':
         getCh();
         if (ch == '=') {
            ChrCat(stoken, ch, sizeString);
            getCh();
            op = XP_GREATEROREQUAL;
            }
         else
            op = XP_GREATERTHAN;
         isOp = true;
         break;
      case '+':
         getCh();
         op   = XP_PLUS;
         isOp = true;
         break;
      case '-':
         getCh();
         op   = XP_MINUS;
         isOp = true;
         break;
      case '*':
         getCh();
         op   = XP_MUL;
         isOp = true;
         break;
      case '/':
         getCh();
         if (ch == '/') {
            ChrCat(stoken, ch, sizeString);
            getCh();
            op = XP_DBLPATH;
            }
         else
            op = XP_PATH;
         isOp = true;
         break;
      case '(':
         getCh();
         op = XP_LPAREN;
         break;
      case ')':
         getCh();
         op = XP_RPAREN;
         break;
      case '[':
         getCh();
         op = XP_LBOX;
         break;
      case ']':
         getCh();
         op = XP_RBOX;
         break;
      case '.':
         getCh();
         if      (ch == '.') {
            ChrCat(stoken, ch, sizeString);
            getCh();
            op = XP_DBLDOT;
            }
         else if (isdigit(ch))
            getNumber();
         else
            op = XP_DOT;
         break;
      case '@':
         getCh();
         op = XP_ATTR;
         break;
      case ',':
         getCh();
         op = XP_COMMA;
         break;
      case ':':
         getCh();
         if (ch == ':') {
            ChrCat(stoken, ch, sizeString);
            getCh();
            op = XP_DBLCOLON;
            }
         else
            op = XP_COLON;
         break;
      default:
         throw(XPE_UNKNOWNOPERATOR);
      }
   } // getOp

//---------------------------------------------------------------------
// Get the next character
//
bool CXPath::getCh(void) {
   ch = 0;

   if (atEnd)
      return false;

   if (*pInFixExp == 0) {
      atEnd = true;
      return false;
      }

   ch = *pInFixExp++;

   // Convert standard five XML entities to their character values
   if (ch == '&')
      ch = getEntity(pInFixExp);

   return true;
   } // getCh

//---------------------------------------------------------------------
// Check the next non-blank character
//
char CXPath::chkCh(void) {
   if (atEnd)
      return 0;

   if (*pInFixExp == 0)
      return 0;

   Pchar p = pInFixExp;
   while (*p == spacer)
      p++;

   char ch = *p;
   if (ch == '&')
      ch = getEntity(p);

   return ch;
   } // chkCh

//---------------------------------------------------------------------
// Skip white space
//
void CXPath::skipSpaces(void) {
   while (ch <= spacer && getCh());
   } // skipSpaces

//---------------------------------------------------------------------
// This method only handles the following entities:
//
// entities of the form &#nn; and the following named entities.
//    &lt;    less-than
//    &gt;    greater-than
//    &amp;   ampersand
//    &apos;  apostrophe
//    &quot;  quote
//
char CXPath::getEntity(RPchar _p) {
   char work[sizeName];
   char name[sizeName];

   name[0] = 0;
   Pchar q = _p;
   while (*q && *q != ';') {
      ChrCat(name, *q, sizeName);
      q++;
      }

   // Is the entity complete?
   if (*q == 0)
      throw(XPE_BADENTITY);

   char ch = 0;

   // Convert number form
   if      (name[0] == '#') {
      // TBD - This code handles the form &#nn; not &#nn;#nn;
      strcpy(name, name+1);
      ushort nr = StrToShort(name);
      ch        = nr;
      }

   // Check for named entity
   else if (strcmp(name, "lt")   == 0)
      ch = '<';
   else if (strcmp(name, "gt")   == 0)
      ch = '>';
   else if (strcmp(name, "amp")  == 0)
      ch = '&';
   else if (strcmp(name, "apos") == 0)
      ch = '\'';
   else if (strcmp(name, "quot") == 0)
      ch = '"';

   // I don't recognize the name
   else
      throw(XPE_UNKNOWNENTITY);

   _p = q+1;

   return ch;
   } // getEntity

//=====================================================================
// CXPathItem Implementation
//=====================================================================

//---------------------------------------------------------------------
//
CXPathItem::CXPathItem(void):
   CItem   (),
   order   (down),
   position(0),
   size    (0),
   type    (XP_NOTYPE),
   bValue  (false),
   dValue  (0.0),
   sValue  (0),
   nValue  (0) {
   } // CXPathItem

//---------------------------------------------------------------------
//
CXPathItem::CXPathItem(bool _value):
   CItem   (),
   order   (down),
   position(0),
   size    (0),
   type    (XP_BOOLEAN),
   bValue  (_value),
   dValue  (0.0),
   sValue  (0),
   nValue  (0) {
   } // CXPathItem

//---------------------------------------------------------------------
//
CXPathItem::CXPathItem(double _value):
   CItem   (),
   order   (down),
   position(0),
   size    (0),
   type    (XP_NUMBER),
   bValue  (false),
   dValue  (_value),
   sValue  (0),
   nValue  (0) {
   } // CXPathItem

//---------------------------------------------------------------------
//
CXPathItem::CXPathItem(Pchar _value):
   CItem   (),
   order   (down),
   position(0),
   size    (0),
   type    (XP_STRING),
   bValue  (false),
   dValue  (0.0),
   sValue  (0),
   nValue  (0) {

   STRINIT(sValue, _value);
   } // CXPathItem

//---------------------------------------------------------------------
//
CXPathItem::CXPathItem(RPCNodeList _value):
   CItem   (),
   order   (down),
   position(0),
   size    (_value->Length()),
   type    (XP_NODESET),
   bValue  (false),
   dValue  (0.0),
   sValue  (0),
   nValue  (_value) {

   if (size) {
      node     = nValue->Item(0);
      position = 1;
      }
   } // CXPathItem

//---------------------------------------------------------------------
//
CXPathItem::CXPathItem(RPCNode _value):
   CItem   (),
   node    (_value),
   order   (down),
   position(1),
   size    (1),
   type    (XP_NODESET),
   bValue  (false),
   dValue  (0.0),
   sValue  (0) {

   nValue = new CNodeList();
   nValue->InsertNode(_value);
   } // CXPathItem

//---------------------------------------------------------------------
//
CXPathItem::CXPathItem(RPCXPathItem _value):
   CItem   (),
   node    (_value->node),
   order   (down),
   position(_value->position),
   size    (_value->size),
   type    (_value->type),
   bValue  (_value->bValue),
   dValue  (_value->dValue),
   sValue  (0),
   nValue  (0) {

   STRINIT(sValue, _value->sValue);

   if (_value->nValue)
      nValue = new CNodeList(_value->nValue);
   } // CXPathItem

//---------------------------------------------------------------------
//
CXPathItem::~CXPathItem() {
   delete[] sValue;
   sValue = 0;
   } // ~CXPathItem

//---------------------------------------------------------------------
//
RTTI_CPP(XPathItem, Item)

//---------------------------------------------------------------------
//
void CXPathItem::clear(void) {
   if (type != XP_BOOLEAN)
      bValue = false;
   if (type != XP_NUMBER)
      dValue = 0.0;
   if (type != XP_STRING) {
      delete[] sValue;
      sValue = 0;
      }
   if (type != XP_NODESET)
      nValue = 0;
   } // clear

//---------------------------------------------------------------------
//
void CXPathItem::Set(bool _value) {
   type   = XP_BOOLEAN;
   bValue = _value;
   } // Set

//---------------------------------------------------------------------
//
void CXPathItem::Set(double _value) {
   type   = XP_NUMBER;
   dValue = _value;
   } // Set

//---------------------------------------------------------------------
//
void CXPathItem::Set(Pchar _value) {
   type   = XP_STRING;
   STRINIT(sValue, _value);
   } // Set

//---------------------------------------------------------------------
//
void CXPathItem::Set(RPCNodeList _value) {
   type   = XP_NODESET;
   nValue = _value;
   } // Set

//---------------------------------------------------------------------
//
ushort CXPathItem::Type(void) {
   return type;
   } // Type

//---------------------------------------------------------------------
//
bool CXPathItem::Boolean(void) {
   Assert(type == XP_BOOLEAN);

   return bValue;
   } // Boolean

//---------------------------------------------------------------------
// Caller must delete the memory when done
//
Pdouble CXPathItem::Number(void) {
   Assert(type == XP_NUMBER);

   Pdouble p = new double;
   *p        = dValue;
   return p;
   } // Number

//---------------------------------------------------------------------
// Caller must delete the memory when done
//
Pchar CXPathItem::String(void) {
   Assert(type == XP_STRING);

   Pchar p = 0;
   STRINIT(p, sValue);
   return p;
   } // String

//---------------------------------------------------------------------
//
PCNodeList CXPathItem::NodeSet(void) {
   Assert(type == XP_NODESET);

   return nValue;
   } // NodeSet

//=====================================================================
// CXPathStack Implementation
//=====================================================================

//---------------------------------------------------------------------
//
CXPathStack::CXPathStack(void):
   CList() {
   } // CXPathStack

//---------------------------------------------------------------------
//
CXPathStack::~CXPathStack() {
   } // ~CXPathStack

//---------------------------------------------------------------------
//
RTTI_CPP(XPathStack, List)

//---------------------------------------------------------------------
//
void CXPathStack::Init(void) {
   Flush();
   } // Init

//---------------------------------------------------------------------
//
void CXPathStack::Dup(void) {
   if (!GotoHead())
      throw(XPE_UNDERRUN);

   PCItem      it    = Item();
   PCXPathItem item  = TYPECAST(it, XPathItem);
   PCXPathItem newit = new CXPathItem(item);
   PushX(newit);
   } // Dup

//---------------------------------------------------------------------
//
ushort CXPathStack::TypeAtTOS(void) {
   if (!GotoHead())
      throw(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   return item->type;
   } // TypeAtTOS

//---------------------------------------------------------------------
//
bool CXPathStack::BoolAtTOS(void) {
   if (!GotoHead())
      throw(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   return item->bValue;
   } // BoolAtTOS

//---------------------------------------------------------------------
//
double CXPathStack::DblAtTOS(void) {
   if (!GotoHead())
      throw(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   return item->dValue;
   } // DblAtTOS

//---------------------------------------------------------------------
//
Pchar CXPathStack::StrAtTOS(void) {
   if (!GotoHead())
      throw(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   return item->sValue;
   } // StrAtTOS

//---------------------------------------------------------------------
//
PCNodeList CXPathStack::NodesAtTOS(void) {
   if (!GotoHead())
      throw(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   return item->nValue;
   } // NodesAtTOS

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::ItemAtTOS(void) {
   if (!GotoHead())
      throw(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   return item;
   } // ItemAtTOS

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::PushV(void) {
   PCXPathItem item = new CXPathItem();
   PCItem it        = TYPECAST(item, Item);
   Insert(it, atTop);

   return item;
   } // PushV

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::PushL(void) {
   PCXPathItem item = new CXPathItem();
   item->type       = XP_LPAREN;
   PCItem it        = TYPECAST(item, Item);
   Insert(it, atTop);

   return item;
   } // PushL

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::PushX(RPCXPathItem _value) {
   PCItem item = TYPECAST(_value, Item);
   Insert(item, atTop);

   return _value;
   } // PushX

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::PushB(bool _value) {
   PCXPathItem item = new CXPathItem(_value);
   PCItem it        = TYPECAST(item, Item);
   Insert(it, atTop);

   return item;
   } // PushB

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::PushD(double _value) {
   PCXPathItem item = new CXPathItem(_value);
   PCItem it        = TYPECAST(item, Item);
   Insert(it, atTop);

   return item;
   } // PushD

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::PushS(Pchar _value) {
   PCXPathItem item = new CXPathItem(_value);
   PCItem it        = TYPECAST(item, Item);
   Insert(it, atTop);

   return item;
   } // PushS

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::PushNL(RPCNodeList _value) {
   PCXPathItem item = new CXPathItem(_value);
   PCItem it        = TYPECAST(item, Item);
   Insert(it, atTop);

   return item;
   } // PushNL

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::PushN(RPCNode _value) {
   PCXPathItem item = new CXPathItem(_value);
   PCItem it        = TYPECAST(item, Item);
   Insert(it, atTop);

   return item;
   } // PushN

//---------------------------------------------------------------------
//
void CXPathStack::SetTOS(bool _value) {
   if (!GotoHead())
      Assert(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   item->type       = XP_BOOLEAN;
   item->bValue     = _value;
   } // SetTOS

//---------------------------------------------------------------------
//
void CXPathStack::SetTOS(double _value) {
   if (!GotoHead())
      Assert(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   item->type       = XP_NUMBER;
   item->dValue     = _value;
   } // SetTOS

//---------------------------------------------------------------------
//
void CXPathStack::SetTOS(Pchar _value) {
   if (!GotoHead())
      Assert(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   item->type       = XP_STRING;
   STRINIT(item->sValue, _value);
   } // SetTOS

//---------------------------------------------------------------------
//
void CXPathStack::SetTOS(RPCNodeList _value) {
   if (!GotoHead())
      Assert(XPE_UNDERRUN);

   PCItem      it   = Item();
   PCXPathItem item = TYPECAST(it, XPathItem);

   item->type       = XP_NODESET;
   item->nValue     = _value;
   } // SetTOS

//---------------------------------------------------------------------
//
PCXPathItem CXPathStack::Pop(void) {
   if (!GotoHead())
      Assert(XPE_UNDERRUN);

   PCItem item = Remove(atTop, false);
   return TYPECAST(item, XPathItem);
   } // Pop