Forráskód Böngészése

* Patch from Sergei Gorelkin:
+ added TXPathBinaryNode as a common ancestor for binary operations;
+ TXPathBooleanOpNode now handles only 'and' and 'or' operators,
the purpose is to not evaluate the second argument if the result can
be determined by the first argument;
* Comparison operations moved to TXPathCompareNode and fixed
to support INFs and NANs correctly;
* Fixed TranslateWideString() that was not deleting characters;
* Fixed 'substring-after' function so its result is empty when argument
string does not contain the pattern;
* Fixed 'round' funcion so it complies to the specs;
* Completed implementation of 'substring' function (but surrogate pairs
are not handled yet);
* Mask exInvalidOp and exZeroDivide FPU exceptions while evaluating
expressions, this ensures correct calculations with respect to INFs
and NANs.
+ Added testsuite for xpath

git-svn-id: trunk@12961 -

michael 16 éve
szülő
commit
f641281a7e
3 módosított fájl, 564 hozzáadás és 87 törlés
  1. 1 0
      .gitattributes
  2. 160 87
      packages/fcl-xml/src/xpath.pp
  3. 403 0
      packages/fcl-xml/tests/xpathts.pp

+ 1 - 0
.gitattributes

@@ -1681,6 +1681,7 @@ packages/fcl-xml/tests/domunit.pp svneol=native#text/plain
 packages/fcl-xml/tests/template.xml svneol=native#text/plain
 packages/fcl-xml/tests/testgen.pp svneol=native#text/plain
 packages/fcl-xml/tests/xmlts.pp svneol=native#text/plain
+packages/fcl-xml/tests/xpathts.pp svneol=native#text/plain
 packages/fftw/Makefile svneol=native#text/plain
 packages/fftw/Makefile.fpc svneol=native#text/plain
 packages/fftw/examples/example.pas svneol=native#text/plain

+ 160 - 87
packages/fcl-xml/src/xpath.pp

@@ -149,37 +149,55 @@ type
       AEnvironment: TXPathEnvironment): TXPathVariable; override;
   end;
 
+  // common ancestor for binary operations
+
+  TXPathBinaryNode = class(TXPathExprNode)
+  protected
+    FOperand1, FOperand2: TXPathExprNode;
+  public
+    destructor Destroy; override;   
+  end;
 
   // Node for (binary) mathematical operation
 
   TXPathMathOp = (opAdd, opSubtract, opMultiply, opDivide, opMod);
 
-  TXPathMathOpNode = class(TXPathExprNode)
+  TXPathMathOpNode = class(TXPathBinaryNode)
   private
-    FOperand1, FOperand2: TXPathExprNode;
     FOperator: TXPathMathOp;
   public
     constructor Create(AOperator: TXPathMathOp;
       AOperand1, AOperand2: TXPathExprNode);
-    destructor Destroy; override;
+    function Evaluate(AContext: TXPathContext;
+      AEnvironment: TXPathEnvironment): TXPathVariable; override;
+  end;
+
+  // Node for comparison operations
+
+  TXPathCompareOp = (opEqual, opNotEqual, opLess, opLessEqual, opGreater,
+    opGreaterEqual);
+
+  TXPathCompareNode = class(TXPathBinaryNode)
+  private
+    FOperator: TXPathCompareOp;
+  public
+    constructor Create(AOperator: TXPathCompareOp;
+      AOperand1, AOperand2: TXPathExprNode);
     function Evaluate(AContext: TXPathContext;
       AEnvironment: TXPathEnvironment): TXPathVariable; override;
   end;
 
 
-  // Node for boolean operations
+  // Node for boolean operations (and, or)
 
-  TXPathBooleanOp = (opEqual, opNotEqual, opLess, opLessEqual, opGreater,
-    opGreaterEqual, opOr, opAnd);
+  TXPathBooleanOp = (opOr, opAnd);
 
-  TXPathBooleanOpNode = class(TXPathExprNode)
+  TXPathBooleanOpNode = class(TXPathBinaryNode)
   private
-    FOperand1, FOperand2: TXPathExprNode;
     FOperator: TXPathBooleanOp;
   public
     constructor Create(AOperator: TXPathBooleanOp;
       AOperand1, AOperand2: TXPathExprNode);
-    destructor Destroy; override;
     function Evaluate(AContext: TXPathContext;
       AEnvironment: TXPathEnvironment): TXPathVariable; override;
   end;
@@ -187,12 +205,9 @@ type
 
   // Node for unions (see [18])
 
-  TXPathUnionNode = class(TXPathExprNode)
-  private
-    FOperand1, FOperand2: TXPathExprNode;
+  TXPathUnionNode = class(TXPathBinaryNode)
   public
     constructor Create(AOperand1, AOperand2: TXPathExprNode);
-    destructor Destroy; override;
     function Evaluate(AContext: TXPathContext;
       AEnvironment: TXPathEnvironment): TXPathVariable; override;
   end;
@@ -509,29 +524,33 @@ end;
 
 procedure TranslateWideString(var S: DOMString; const SrcPat, DstPat: DOMString);
 var
-  I, J, K, L: Integer;
-  P: DOMPChar;
+  I, J, L: Integer;
+  P, Start: DOMPChar;
 begin
   UniqueString(S);
   L := Length(DstPat);
   P := DOMPChar(S);
   if Length(SrcPat) > L then  // may remove some chars
   begin
-    K := 0;
+    Start := P;
     for I := 1 to Length(S) do
     begin
       J := Pos(S[I], SrcPat);
       if J > 0 then
       begin
         if J <= L then
+        begin
           P^ := DstPat[J];
+          Inc(P);
+        end;
       end
       else
+      begin
         P^ := S[I];
-      Inc(P);
-      Inc(K);
+        Inc(P);
+      end;
     end;
-    SetLength(S, K);
+    SetLength(S, P-Start);
   end
   else  // no char removal possible
     for I := 1 to Length(S) do
@@ -647,6 +666,12 @@ begin
   end;
 end;
 
+destructor TXPathBinaryNode.Destroy;
+begin
+  FOperand1.Free;
+  FOperand2.Free;
+  inherited Destroy;
+end;
 
 constructor TXPathMathOpNode.Create(AOperator: TXPathMathOp;
   AOperand1, AOperand2: TXPathExprNode);
@@ -657,13 +682,6 @@ begin
   FOperand2 := AOperand2;
 end;
 
-destructor TXPathMathOpNode.Destroy;
-begin
-  FOperand1.Free;
-  FOperand2.Free;
-  inherited Destroy;
-end;
-
 function TXPathMathOpNode.Evaluate(AContext: TXPathContext;
   AEnvironment: TXPathEnvironment): TXPathVariable;
 var
@@ -684,10 +702,7 @@ begin
         opMultiply:
           NumberResult := Op1 * Op2;
         opDivide:
-          if IsZero(Op2) then
-            NumberResult := NaN
-          else
-            NumberResult := Op1 / Op2;
+          NumberResult := Op1 / Op2;
         opMod:
           NumberResult := Trunc(Op1) mod Trunc(Op2);
       end;
@@ -701,7 +716,7 @@ begin
 end;
 
 
-constructor TXPathBooleanOpNode.Create(AOperator: TXPathBooleanOp;
+constructor TXPathCompareNode.Create(AOperator: TXPathCompareOp;
   AOperand1, AOperand2: TXPathExprNode);
 begin
   inherited Create;
@@ -710,23 +725,20 @@ begin
   FOperand2 := AOperand2;
 end;
 
-destructor TXPathBooleanOpNode.Destroy;
-begin
-  FOperand1.Free;
-  FOperand2.Free;
-  inherited Destroy;
-end;
-
-function TXPathBooleanOpNode.Evaluate(AContext: TXPathContext;
+function TXPathCompareNode.Evaluate(AContext: TXPathContext;
   AEnvironment: TXPathEnvironment): TXPathVariable;
 var
   Op1, Op2: TXPathVariable;
+  e1, e2: Extended;
+  BoolResult: Boolean;
+
 
   function EvalEqual: Boolean;
   var
     i, j: Integer;
     NodeSet1, NodeSet2: TNodeSet;
     s: DOMString;
+    e1, e2: Extended;
   begin
     // !!!: Doesn't handle nodesets yet!
     if Op1.InheritsFrom(TXPathNodeSetVariable) then
@@ -773,13 +785,20 @@ var
       Result := Op1.AsBoolean = Op2.AsBoolean
     else if Op1.InheritsFrom(TXPathNumberVariable) or
       Op2.InheritsFrom(TXPathNumberVariable) then
-      Result := Op1.AsNumber = Op2.AsNumber
+    begin
+      e1 := Op1.AsNumber;
+      e2 := Op2.AsNumber;
+      if IsNan(e1) or IsNan(e2) then
+        Result := False
+      else if IsInfinite(e1) or IsInfinite(e2) then
+        Result := e1 = e2
+      else
+        Result := SameValue(e1, e2);
+    end
     else
       Result := Op1.AsText = Op2.AsText; // !!!: Attention with Unicode!
   end;
 
-var
-  BoolResult: Boolean;
 begin
   Op1 := FOperand1.Evaluate(AContext, AEnvironment);
   try
@@ -790,18 +809,22 @@ begin
           BoolResult := EvalEqual;
         opNotEqual:
           BoolResult := not EvalEqual;
-        opLess:
-          BoolResult := Op1.AsNumber < Op2.AsNumber;
-        opLessEqual:
-          BoolResult := Op1.AsNumber <= Op2.AsNumber;
-        opGreater:
-          BoolResult := Op1.AsNumber > Op2.AsNumber;
-        opGreaterEqual:
-          BoolResult := Op1.AsNumber >= Op2.AsNumber;
-        opOr:
-          BoolResult := Op1.AsBoolean or Op2.AsBoolean;
-        opAnd:
-          BoolResult := Op1.AsBoolean and Op2.AsBoolean;
+      else
+        e1 := Op1.AsNumber;
+        e2 := Op2.AsNumber;
+        if IsNan(e1) or IsNan(e2) then
+          BoolResult := False
+        else
+        case FOperator of
+          opLess:
+            BoolResult := e1 < e2;
+          opLessEqual:
+            BoolResult := e1 <= e2;
+          opGreater:
+            BoolResult := e1 > e2;
+          opGreaterEqual:
+            BoolResult := e1 >= e2;
+        end;
       end;
     finally
       Op2.Release;
@@ -812,19 +835,48 @@ begin
   Result := TXPathBooleanVariable.Create(BoolResult);
 end;
 
-
-constructor TXPathUnionNode.Create(AOperand1, AOperand2: TXPathExprNode);
+constructor TXPathBooleanOpNode.Create(AOperator: TXPathBooleanOp;
+  AOperand1, AOperand2: TXPathExprNode);
 begin
   inherited Create;
+  FOperator := AOperator;
   FOperand1 := AOperand1;
   FOperand2 := AOperand2;
 end;
 
-destructor TXPathUnionNode.Destroy;
+function TXPathBooleanOpNode.Evaluate(AContext: TXPathContext;
+  AEnvironment: TXPathEnvironment): TXPathVariable;
+var
+  res: Boolean;
+  Op1, Op2: TXPathVariable;
 begin
-  FOperand1.Free;
-  FOperand2.Free;
-  inherited Destroy;
+  { don't evaluate second arg if result is determined by first one }
+  Op1 := FOperand1.Evaluate(AContext, AEnvironment);
+  try
+    res := Op1.AsBoolean;
+  finally
+    Op1.Release;
+  end;
+  if not (((FOperator = opAnd) and (not res)) or ((FOperator = opOr) and res)) then
+  begin
+    Op2 := FOperand2.Evaluate(AContext, AEnvironment);
+    try
+      case FOperator of
+        opAnd: res := res and Op2.AsBoolean;
+        opOr:  res := res or Op2.AsBoolean;
+      end;
+    finally
+      Op2.Release;
+    end;
+  end;
+  Result := TXPathBooleanVariable.Create(res);
+end;
+
+constructor TXPathUnionNode.Create(AOperand1, AOperand2: TXPathExprNode);
+begin
+  inherited Create;
+  FOperand1 := AOperand1;
+  FOperand2 := AOperand2;
 end;
 
 function TXPathUnionNode.Evaluate(AContext: TXPathContext;
@@ -833,9 +885,10 @@ var
   Op1Result, Op2Result: TXPathVariable;
   NodeSet, NodeSet2: TNodeSet;
   CurNode: Pointer;
-  i, j: Integer;
-  DoAdd: Boolean;
+  i: Integer;
 begin
+{ TODO: result must be sorted by document order, i.e. 'a|b' yields the
+  same nodeset as 'b|a' }
   Op1Result := FOperand1.Evaluate(AContext, AEnvironment);
   try
     Op2Result := FOperand2.Evaluate(AContext, AEnvironment);
@@ -844,15 +897,8 @@ begin
       NodeSet2 := Op2Result.AsNodeSet;
       for i := 0 to NodeSet2.Count - 1 do
       begin
-        DoAdd := True;
         CurNode := NodeSet2[i];
-        for j := 0 to NodeSet.Count - 1 do
-          if NodeSet[j] = CurNode then
-          begin
-            DoAdd := False;
-            break;
-          end;
-        if DoAdd then
+        if NodeSet.IndexOf(CurNode) < 0 then
           NodeSet.Add(CurNode);
       end;
     finally
@@ -1384,11 +1430,7 @@ end;
 
 function TXPathNumberVariable.AsBoolean: Boolean;
 begin
-  // !!!: What about NaNs and so on?
-  if FValue = 0 then
-    Result := False
-  else
-    Result := True;
+  Result := not (IsNan(FValue) or IsZero(FValue));
 end;
 
 function TXPathNumberVariable.AsNumber: Extended;
@@ -1398,6 +1440,7 @@ end;
 
 function TXPathNumberVariable.AsText: DOMString;
 begin
+// TODO: Decimal separator!!!
   Result := FloatToStr(FValue);
 end;
 
@@ -1868,6 +1911,7 @@ begin
       end;
   else
     Error(SParserInvalidPrimExpr);
+    Result := nil; // satisfy compiler
   end;
   NextToken;
 end;
@@ -1956,7 +2000,7 @@ end;
 
 function TXPathScanner.ParseEqualityExpr: TXPathExprNode;  // [23]
 var
-  op: TXPathBooleanOp;
+  op: TXPathCompareOp;
 begin
   Result := ParseRelationalExpr;
   repeat
@@ -1967,13 +2011,13 @@ begin
       Break;
     end;
     NextToken;
-    Result := TXPathBooleanOpNode.Create(op, Result, ParseRelationalExpr);
+    Result := TXPathCompareNode.Create(op, Result, ParseRelationalExpr);
   until False;
 end;
 
 function TXPathScanner.ParseRelationalExpr: TXPathExprNode;  // [24]
 var
-  op: TXPathBooleanOp;
+  op: TXPathCompareOp;
 begin
   Result := ParseAdditiveExpr;
   repeat
@@ -1986,7 +2030,7 @@ begin
       Break;
     end;
     NextToken;
-    Result := TXPathBooleanOpNode.Create(op, Result, ParseAdditiveExpr);
+    Result := TXPathCompareNode.Create(op, Result, ParseAdditiveExpr);
   until False;
 end;
 
@@ -2423,28 +2467,48 @@ end;
 function TXPathEnvironment.xpSubstringAfter(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
 var
   s, substr: DOMString;
+  i: Integer;
 begin
   if Args.Count <> 2 then
     EvaluationError(SEvalInvalidArgCount);
   s := TXPathVariable(Args[0]).AsText;
   substr := TXPathVariable(Args[1]).AsText;
-  Result := TXPathStringVariable.Create(Copy(s, Pos(substr, s) + Length(substr), MaxInt));
+  i := Pos(substr, s);
+  if i <> 0 then
+    Result := TXPathStringVariable.Create(Copy(s, i + Length(substr), MaxInt))
+  else
+    Result := TXPathStringVariable.Create('');
 end;
 
 function TXPathEnvironment.xpSubstring(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
 var
   s: DOMString;
-  n1, n2: Integer;
+  i, n1, n2: Integer;
+  e1, e2: Extended;
+  empty: Boolean;
 begin
   if (Args.Count < 2) or (Args.Count > 3) then
     EvaluationError(SEvalInvalidArgCount);
   s := TXPathVariable(Args[0]).AsText;
-  n1 := round(TXPathVariable(Args[1]).AsNumber);
+  e1 := TXPathVariable(Args[1]).AsNumber;
+  n1 := 1;  // satisfy compiler
+  n2 := MaxInt;
+  empty := IsNaN(e1) or IsInfinite(e1);
+  if not empty then
+    n1 := floor(0.5 + e1);
   if Args.Count = 3 then
-    n2 := round(TXPathVariable(Args[2]).AsNumber)
-  else
-    n2 := MaxInt;
-  Result := TXPathStringVariable.Create(Copy(s, n1, n2));
+  begin
+    e2 := TXPathVariable(Args[2]).AsNumber;
+    if IsNaN(e2) or (IsInfinite(e2) and (e2 < 0)) then
+      empty := True
+    else if not IsInfinite(e2) then
+      n2 := floor(0.5 + e2);
+  end;
+  i := Max(n1, 1);
+  n2 := n2 + n1 - i;
+  if empty then
+    n2 := -1;
+  Result := TXPathStringVariable.Create(Copy(s, i, n2));
 end;
 
 function TXPathEnvironment.xpStringLength(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
@@ -2546,10 +2610,15 @@ begin
 end;
 
 function TXPathEnvironment.xpRound(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
+var
+  num: Extended;
 begin
   if Args.Count <> 1 then
     EvaluationError(SEvalInvalidArgCount);
-  Result := TXPathNumberVariable.Create(round(TXPathVariable(Args[0]).AsNumber));
+  num := TXPathVariable(Args[0]).AsNumber;
+  if not (IsNan(num) or IsInfinite(num)) then
+    num := floor(0.5 + num);
+  Result := TXPathNumberVariable.Create(num);
 end;
 
 
@@ -2587,14 +2656,18 @@ function TXPathExpression.Evaluate(AContextNode: TDOMNode;
   AEnvironment: TXPathEnvironment): TXPathVariable;
 var
   Context: TXPathContext;
+  mask: TFPUExceptionMask;
 begin
   if Assigned(FRootNode) then
   begin
+    mask := GetExceptionMask;
+    SetExceptionMask(mask + [exInvalidOp, exZeroDivide]);
     Context := TXPathContext.Create(AContextNode, 1, 1);
     try
       Result := FRootNode.Evaluate(Context, AEnvironment);
     finally
       Context.Free;
+      SetExceptionMask(mask);
     end;
   end else
     Result := nil;

+ 403 - 0
packages/fcl-xml/tests/xpathts.pp

@@ -0,0 +1,403 @@
+{**********************************************************************
+
+    This file is part of the Free Component Library (FCL)
+
+    Test suite for the xpath.pp unit.
+    Largely based on expressions from libxml2 source tree.
+    Copyright (c) 2009 by Sergei Gorelkin, [email protected]
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+program xpathts;
+{$mode objfpc}{$h+}
+
+uses
+  Classes, SysUtils, Math,
+  dom, xmlread, xmlwrite, xpath;
+
+type
+  TResultType = (rtString, rtNumber, rtBool, rtNodeset);
+
+  TTestRec = record
+    expr: DOMString;
+  case rt: TResultType of
+    rtString: (s: DOMPChar);   // cannot use DOMString here
+    rtNumber: (n: Extended);
+    rtBool:   (b: Boolean);
+  end;
+
+const
+  BaseTests: array[0..4] of TTestRec = (
+    (expr: '1';           rt: rtNumber; n: 1),
+    (expr: '1+2';         rt: rtNumber; n: 3),
+    (expr: '2*3';         rt: rtNumber; n: 6),
+    (expr: '1+2*3+4';     rt: rtNumber; n: 11),
+    (expr: '(1+2)*(3+4)'; rt: rtNumber; n: 21)
+  );
+
+  CompareTests: array[0..45] of TTestRec = (
+    (expr: '0<0';        rt: rtBool; b: False),
+    (expr: '0<=0';       rt: rtBool; b: True),
+    (expr: '0>0';        rt: rtBool; b: False),
+    (expr: '0>=0';       rt: rtBool; b: True),
+    (expr: '0<1';        rt: rtBool; b: True),
+    (expr: '0<=1';       rt: rtBool; b: True),
+    (expr: '0>1';        rt: rtBool; b: False),
+    (expr: '0>=1';       rt: rtBool; b: False),
+    (expr: '1<0';        rt: rtBool; b: False),
+    (expr: '1<=0';       rt: rtBool; b: False),
+    (expr: '1>0';        rt: rtBool; b: True),
+    (expr: '1>=0';       rt: rtBool; b: True),
+    (expr: '1<1';        rt: rtBool; b: False),
+    (expr: '1<=1';       rt: rtBool; b: True),
+    (expr: '1>1';        rt: rtBool; b: False),
+    (expr: '1>=1';       rt: rtBool; b: True),
+    (expr: '"0"<1';      rt: rtBool; b: True),
+    (expr: '"0"<=1';     rt: rtBool; b: True),
+    (expr: '"0">1';      rt: rtBool; b: False),
+    (expr: '"0">=1';     rt: rtBool; b: False),
+    (expr: '0<"1.2"';    rt: rtBool; b: True),
+    (expr: '0<="1.2"';   rt: rtBool; b: True),
+    (expr: '0>"1.2"';    rt: rtBool; b: False),
+    (expr: '0>="1.2"';   rt: rtBool; b: False),
+    (expr: '0<"-0.2"';   rt: rtBool; b: False),
+    (expr: '0<="-0.2"';  rt: rtBool; b: False),
+    (expr: '0>"-0.2"';   rt: rtBool; b: True),
+    (expr: '0>="-0.2"';  rt: rtBool; b: True),
+    (expr: 'false()<1';  rt: rtBool; b: True),
+    (expr: 'false()<=1'; rt: rtBool; b: True),
+    (expr: '0>true()';   rt: rtBool; b: False),
+    (expr: '0>=true()';  rt: rtBool; b: False),
+    (expr: '"a" > "a"';  rt: rtBool; b: False),
+    (expr: '"a" > "b"';  rt: rtBool; b: False),
+    (expr: '"b" > "a"';  rt: rtBool; b: False),
+    (expr: '"a" < "a"';  rt: rtBool; b: False),
+    (expr: '"a" < "b"';  rt: rtBool; b: False),
+    (expr: '"b" < "a"';  rt: rtBool; b: False),
+    (expr: '"a" >= "a"'; rt: rtBool; b: False),
+    (expr: '"a" >= "b"'; rt: rtBool; b: False),
+    (expr: '"b" >= "a"'; rt: rtBool; b: False),
+    (expr: '"a" <= "a"'; rt: rtBool; b: False),
+    (expr: '"a" <= "b"'; rt: rtBool; b: False),
+    (expr: '"b" <= "a"'; rt: rtBool; b: False),
+    (expr: '"a" > "0.0"'; rt: rtBool; b: False),
+    (expr: '"a" < "0.0"'; rt: rtBool; b: False)
+  );
+
+  EqualityTests: array[0..25] of TTestRec = (
+    (expr: '1=1';  rt: rtBool; b: True),
+    (expr: '1!=1'; rt: rtBool; b: False),
+    (expr: '1=0';  rt: rtBool; b: False),
+    (expr: '1!=0'; rt: rtBool; b: True),
+
+    (expr: 'true()=true()';   rt: rtBool; b: True),
+    (expr: 'true()!=true()';  rt: rtBool; b: False),
+    (expr: 'true()=false()';  rt: rtBool; b: False),
+    (expr: 'false()!=true()'; rt: rtBool; b: True),
+
+    (expr: '"test"="test"';   rt: rtBool; b: True),
+    (expr: '"test"!="test"';  rt: rtBool; b: False),
+    (expr: '"test2"="test"';  rt: rtBool; b: False),
+    (expr: '"test2"!="test"'; rt: rtBool; b: True),
+
+    (expr: 'false()=0';   rt: rtBool; b: True),
+    (expr: 'false()!=0';  rt: rtBool; b: False),
+    (expr: 'false()=1';   rt: rtBool; b: False),
+
+    (expr: 'false()!=1';  rt: rtBool; b: True),
+    (expr: '0=true()';    rt: rtBool; b: False),
+    (expr: '0!=true()';   rt: rtBool; b: True),
+    (expr: '1=true()';    rt: rtBool; b: True),
+    (expr: '1!=true()';   rt: rtBool; b: False),
+
+    (expr: 'true()="test"';   rt: rtBool; b: True),
+    (expr: 'false()="test"';  rt: rtBool; b: False),
+    (expr: '"test"!=true()';  rt: rtBool; b: False),
+    (expr: '"test"!=false()'; rt: rtBool; b: True),
+
+    (expr: '"a"=0.0'; rt: rtBool; b: False),
+    (expr: '"a"!=0.0'; rt: rtBool; b: True)
+  );
+
+  FloatTests: array[0..60] of TTestRec = (
+    (expr: '1';        rt: rtNumber; n: 1),
+    (expr: '123';      rt: rtNumber; n: 123),
+    (expr: '1.23';     rt: rtNumber; n: 1.23),
+    (expr: '0.123';    rt: rtNumber; n: 0.123),
+    (expr: '4.';       rt: rtNumber; n: 4),
+    (expr: '.4';       rt: rtNumber; n: 0.4),
+    //(expr: '1.23e3';   rt: rtNumber; n: 1230),
+    //(expr: '1.23e-3';  rt: rtNumber; n: 0.00123),
+    (expr: '1 div 0';  rt: rtNumber; n: Infinity),
+    (expr: '-1 div 0'; rt: rtNumber; n: -Infinity),
+    (expr: '0 div 0';  rt: rtNumber; n: NaN),
+    (expr: '1 div -0'; rt: rtNumber; n: -Infinity),
+
+    (expr: '(1 div 0) > 0';  rt: rtBool; b: True),
+    (expr: '(1 div 0) < 0';  rt: rtBool; b: False),
+    (expr: '(-1 div 0) > 0'; rt: rtBool; b: False),
+    (expr: '(-1 div 0) < 0'; rt: rtBool; b: True),
+    (expr: '(0 div 0) > 0';  rt: rtBool; b: False),
+    (expr: '(0 div 0) < 0';  rt: rtBool; b: False),
+    (expr: '(1 div -0) > 0'; rt: rtBool; b: False),
+    (expr: '(1 div -0) < 0'; rt: rtBool; b: True),
+    (expr: '0 div 0 = 0 div 0';  rt: rtBool; b: False),
+    (expr: '0 div 0 != 0 div 0'; rt: rtBool; b: True),
+    (expr: '0 div 0 > 0 div 0';  rt: rtBool; b: False),
+    (expr: '0 div 0 < 0 div 0';  rt: rtBool; b: False),
+    (expr: '0 div 0 >= 0 div 0'; rt: rtBool; b: False),
+    (expr: '0 div 0 <= 0 div 0'; rt: rtBool; b: False),
+    (expr: '1 div 0 = -1 div 0'; rt: rtBool; b: False),
+    (expr: '1 div 0 != -1 div 0'; rt: rtBool; b: True),
+    (expr: '1 div 0 > -1 div 0';  rt: rtBool; b: True),
+    (expr: '1 div 0 < -1 div 0';  rt: rtBool; b: False),
+    (expr: '1 div 0 >= -1 div 0'; rt: rtBool; b: True),
+    (expr: '1 div 0 <= -1 div 0'; rt: rtBool; b: False),
+    (expr: '1 div 0 = 1 div 0';   rt: rtBool; b: True),
+    (expr: '1 div 0 != 1 div 0';  rt: rtBool; b: False),
+    (expr: '1 div 0 > 1 div 0';   rt: rtBool; b: False),
+    (expr: '1 div 0 < 1 div 0';   rt: rtBool; b: False),
+    (expr: '1 div 0 >= -1 div 0'; rt: rtBool; b: True),
+    (expr: '1 div 0 <= -1 div 0'; rt: rtBool; b: False),
+    (expr: '-2 div 0 = -1 div 0'; rt: rtBool; b: True),
+
+    (expr: '1 div floor(0.1)';    rt: rtNumber; n: Infinity),
+    (expr: '1 div floor(-0.1)';   rt: rtNumber; n: -1),
+    (expr: '1 div floor(-0)';     rt: rtNumber; n: -Infinity),
+    (expr: '1 div floor(0)';      rt: rtNumber; n: Infinity),
+    (expr: '1 div ceiling(0.1)';  rt: rtNumber; n: 1),
+    (expr: '1 div ceiling(-0.1)'; rt: rtNumber; n: -Infinity),
+    (expr: '1 div ceiling(-0)';   rt: rtNumber; n: -Infinity),
+    (expr: '1 div ceiling(0)';    rt: rtNumber; n: Infinity),
+    (expr: '1 div round(0.1)';    rt: rtNumber; n: Infinity),
+    (expr: '1 div round(-0.1)';   rt: rtNumber; n: -Infinity),
+    (expr: '1 div round(-0)';     rt: rtNumber; n: -Infinity),
+    (expr: '1 div round(0)';      rt: rtNumber; n: Infinity),
+    (expr: '1 div number("f")';   rt: rtNumber; n: NaN),
+    (expr: 'number("f") div 1';   rt: rtNumber; n: NaN),
+    (expr: '1 div (1 div 0)';     rt: rtNumber; n: 0),
+    (expr: '(1 div 0) div 1';     rt: rtNumber; n: Infinity),
+    (expr: '-(1 div 0) div 1';    rt: rtNumber; n: -Infinity),
+
+    (expr: '5 mod 2';     rt: rtNumber; n: 1),
+    (expr: '5 mod -2';    rt: rtNumber; n: 1),
+    (expr: '-5 mod 2';    rt: rtNumber; n: -1),
+    (expr: '-5 mod -2';   rt: rtNumber; n: -1),
+    (expr: '8 mod 3 = 2'; rt: rtBool; b: True),
+   // test boolean operator short-circuting; "count(5)" acts as an error
+    (expr: '10+30*20 or count(5)';  rt: rtBool; b: True),
+    (expr: '75-50-25 and count(5)'; rt: rtBool; b: False)
+  );
+
+  FunctionTests: array[0..36] of TTestRec = (
+  // last()
+  // position()
+  // count()
+  // id()
+  // local-name()
+  // namespace-uri()
+  // name()
+
+    (expr: 'boolean(0)';        rt: rtBool; b: False),
+    (expr: 'boolean(-0)';       rt: rtBool; b: False),
+    (expr: 'boolean(1 div 0)';  rt: rtBool; b: True),
+    (expr: 'boolean(-1 div 0)'; rt: rtBool; b: True),
+    (expr: 'boolean(0 div 0)';  rt: rtBool; b: False),
+    (expr: 'boolean("")';       rt: rtBool; b: False),
+    (expr: 'boolean("abc")';    rt: rtBool; b: True),
+    {
+     boolean(node-set) -- TODO
+    }
+    (expr: 'true()';  rt: rtBool; b: True),
+    (expr: 'false()'; rt: rtBool; b: False),
+    {
+     not()
+     lang() -- involves nodes
+    }
+
+    (expr: 'number("1.5")';   rt: rtNumber; n: 1.5),
+    (expr: 'number("abc")';   rt: rtNumber; n: NaN),
+    (expr: '-number("abc")';  rt: rtNumber; n: NaN),
+    (expr: 'number(true())';  rt: rtNumber; n: 1.0),
+    (expr: 'number(false())'; rt: rtNumber; n: 0),
+    {
+     sum() -- involves nodes
+    }
+
+    (expr: 'floor(0.1)';     rt: rtNumber; n: 0),
+    (expr: 'floor(-0.1)';    rt: rtNumber; n: -1),
+    (expr: 'floor(-0)';      rt: rtNumber; n: 0),
+    (expr: 'floor(0)';       rt: rtNumber; n: 0),
+    (expr: 'floor(5.2)';     rt: rtNumber; n: 5),
+    (expr: 'floor(-5.2)';    rt: rtNumber; n: -6),
+
+    (expr: 'ceiling(0.1)';   rt: rtNumber; n: 1),
+    (expr: 'ceiling(-0.1)';  rt: rtNumber; n: 0),
+    (expr: 'ceiling(-0)';    rt: rtNumber; n: 0),
+    (expr: 'ceiling(0)';     rt: rtNumber; n: 0),
+    (expr: 'ceiling(5.2)';   rt: rtNumber; n: 6),
+    (expr: 'ceiling(-5.2)';  rt: rtNumber; n: -5),
+
+    (expr: 'round(0.1)';     rt: rtNumber; n: 0),
+    (expr: 'round(5.2)';     rt: rtNumber; n: 5),
+    (expr: 'round(5.5)';     rt: rtNumber; n: 6),
+    (expr: 'round(5.6)';     rt: rtNumber; n: 6),
+    (expr: 'round(-0.1)';    rt: rtNumber; n: 0),
+    (expr: 'round(-5.2)';    rt: rtNumber; n: -5),
+    (expr: 'round(-5.5)';    rt: rtNumber; n: -5),
+    (expr: 'round(-5.6)';    rt: rtNumber; n: -6),
+    (expr: 'round("NaN")';   rt: rtNumber; n: NaN),
+    (expr: 'round(1 div 0)'; rt: rtNumber; n: Infinity),
+    (expr: 'round(-1 div 0)'; rt: rtNumber; n: -Infinity)
+  );
+
+  StringTests: array[0..43] of TTestRec = (
+    (expr: 'string(5)';       rt: rtString; s: '5'),
+    (expr: 'string(0.5)';     rt: rtString; s: '0.5'),
+    (expr: 'string(-0.5)';    rt: rtString; s: '-0.5'),
+    (expr: 'string(true())';  rt: rtString; s: 'true'),
+    (expr: 'string(false())'; rt: rtString; s: 'false'),
+    (expr: 'string(0 div 0)'; rt: rtString; s: 'NaN'),
+    (expr: 'string(1 div 0)'; rt: rtString; s: 'Infinity'),
+    (expr: 'string(-1 div 0)'; rt: rtString; s: '-Infinity'),
+    // maybe other checks for correct numeric formats
+
+    (expr: 'concat("titi","toto")'; rt: rtString; s: 'tititoto'),
+    (expr: 'concat("titi","toto","tata")'; rt: rtString; s: 'tititototata'),
+    (expr: 'concat("titi",''toto'')'; rt: rtString; s: 'tititoto'),
+    (expr: 'concat("titi",''toto'',"tata","last")'; rt: rtString; s: 'tititototatalast'),
+
+    (expr: 'starts-with("tititoto","titi")'; rt: rtBool; b: True),
+    (expr: 'starts-with("tititoto","to")';   rt: rtBool; b: False),
+
+    (expr: 'contains("tititototata","titi")'; rt: rtBool; b: True),
+    (expr: 'contains("tititototata","toto")'; rt: rtBool; b: True),
+    (expr: 'contains("tititototata","tata")'; rt: rtBool; b: True),
+    (expr: 'contains("tititototata","tita")'; rt: rtBool; b: False),
+
+    (expr: 'substring("12345",2,3)'; rt: rtString; s: '234'),
+    (expr: 'substring("12345",2)';   rt: rtString; s: '2345'),
+    (expr: 'substring("12345",-4)';  rt: rtString; s: '12345'),
+    (expr: 'substring("12345",3.4)'; rt: rtString; s: '345'),
+    (expr: 'substring("12345",3.6)'; rt: rtString; s: '45'),
+
+    (expr: 'substring("12345",1.5,2.6)'; rt: rtString; s: '234'),
+    (expr: 'substring("12345",2.2,2.2)'; rt: rtString; s: '23'),
+    (expr: 'substring("12345",0,3)';     rt: rtString; s: '12'),
+    (expr: 'substring("12345",-8,10)';   rt: rtString; s: '1'),
+    (expr: 'substring("12345",4,-10)';   rt: rtString; s: ''),
+
+    (expr: 'substring("12345",0 div 0, 3)'; rt: rtString; s: ''),
+    (expr: 'substring("12345",1, 0 div 0)'; rt: rtString; s: ''),
+    (expr: 'substring("12345",1 div 0, 3)'; rt: rtString; s: ''),
+    (expr: 'substring("12345",3,-1 div 0)'; rt: rtString; s: ''),
+    (expr: 'substring("12345",-42, 1 div 0)'; rt: rtString; s: '12345'),
+
+    (expr: 'substring("12345",-1 div 0, 1 div 0)'; rt: rtString; s: ''),
+    (expr: 'substring("12345",-1 div 0,5)';        rt: rtString; s: ''),
+
+    (expr: 'substring-before("1999/04/01","/")'; rt: rtString; s: '1999'),
+    (expr: 'substring-before("1999/04/01","a")'; rt: rtString; s: ''),
+    (expr: 'substring-after("1999/04/01","/")'; rt: rtString; s: '04/01'),
+    (expr: 'substring-after("1999/04/01","19")'; rt: rtString; s: '99/04/01'),
+    (expr: 'substring-after("1999/04/01","a")'; rt: rtString; s: ''),
+
+    (expr: 'string-length("")';     rt: rtNumber; n: 0),
+    (expr: 'string-length("titi")'; rt: rtNumber; n: 4),
+    {
+     normalize-space()
+    }
+    (expr: 'translate("bar", "abc", "ABC")'; rt: rtString; s: 'BAr'),
+    (expr: 'translate("--aaa--","abc-","ABC")'; rt: rtString; s: 'AAA')
+  );
+
+var
+  FailCount: Integer = 0;  
+
+procedure CheckResult(const t: TTestRec; r: TXPathVariable);
+begin
+  case t.rt of
+    rtBool:
+    begin
+      if (r is TXPathBooleanVariable) and (r.AsBoolean = t.b) then
+        Exit;
+      writeln;
+      writeln('Failed: ', t.expr);
+      writeln('Expected: ', t.b, ' got: ', r.AsBoolean);
+    end;
+
+    rtNumber:
+    begin
+      if (r is TXPathNumberVariable) then
+      begin
+        if IsNan(t.n) and IsNan(r.AsNumber) then
+          Exit;
+        if IsInfinite(t.n) and (t.n = r.AsNumber) then
+          Exit;
+        if SameValue(r.AsNumber, t.n) then
+          Exit;
+      end;
+      writeln;
+      writeln('Failed: ', t.expr);
+      writeln('Expected: ', t.n, ' got: ', r.AsNumber);
+    end;
+
+    rtString:
+    begin
+      if (r is TXPathStringVariable) and (r.AsText = DOMString(t.s)) then
+        Exit;
+      writeln;  
+      writeln('Failed: ', t.expr);
+      writeln('Expected: ', DOMString(t.s), ' got: ', r.AsText);
+    end;
+  end;
+  Inc(FailCount);
+end;
+
+procedure DoSuite(const tests: array of TTestRec);
+var
+  i: Integer;
+  doc: TXMLDocument;
+  rslt: TXPathVariable;
+begin
+  doc := TXMLDocument.Create;
+  try
+    for i := 0 to High(tests) do
+    begin
+      try
+        rslt := EvaluateXPathExpression(tests[i].expr, doc);
+        try
+          CheckResult(tests[i], rslt);
+        finally
+          rslt.Free;
+        end;
+      except
+        writeln;
+        writeln('Failed: ', tests[i].expr);
+        SysUtils.ShowException(ExceptObject, ExceptAddr);
+        Inc(FailCount);
+      end;
+    end;
+  finally
+    doc.Free;
+  end;    
+end;
+
+begin
+  DecimalSeparator := '.';
+  DoSuite(BaseTests);
+  DoSuite(CompareTests);
+  DoSuite(EqualityTests);
+  DoSuite(FloatTests);
+  DoSuite(FunctionTests);
+  DoSuite(StringTests);
+
+  writeln;
+  writeln('Total failed tests: ', FailCount);
+end.