* 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 -
This commit is contained in:
michael 2009-03-23 08:37:51 +00:00
parent 40247d2d87
commit f641281a7e
3 changed files with 565 additions and 88 deletions

1
.gitattributes vendored
View File

@ -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

View File

@ -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,6 +835,42 @@ begin
Result := TXPathBooleanVariable.Create(BoolResult);
end;
constructor TXPathBooleanOpNode.Create(AOperator: TXPathBooleanOp;
AOperand1, AOperand2: TXPathExprNode);
begin
inherited Create;
FOperator := AOperator;
FOperand1 := AOperand1;
FOperand2 := AOperand2;
end;
function TXPathBooleanOpNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
var
res: Boolean;
Op1, Op2: TXPathVariable;
begin
{ 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
@ -820,22 +879,16 @@ begin
FOperand2 := AOperand2;
end;
destructor TXPathUnionNode.Destroy;
begin
FOperand1.Free;
FOperand2.Free;
inherited Destroy;
end;
function TXPathUnionNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
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;

View File

@ -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, sergei_gorelkin@mail.ru
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.