mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-08-14 21:09:11 +02:00
More progress with XPath:
src/xpath.pp: + Implemented sum() and normalize-space() core functions. * Rewrote comparison code, nodesets are now handled correctly (to the extent of the tests which I could find). * starts-with() and contains() return True when second argument is an empty string - that differs from Pos() behavior. * NaNs are propagated through mod operator, floor() and ceiling(). * Fixed memory leak caused by not releasing arguments after function calls. * string-value of a nodeset is the value of its first node, not all nodes glued together. tests/xpathts.pp: + Added 120 tests, most coming from OASIS Prototype XSLT/XPath 1.0 conformance test suite. + Tests can now take an input xml data as a string. git-svn-id: trunk@13046 -
This commit is contained in:
parent
58f4f7829c
commit
b02aededf8
@ -483,7 +483,7 @@ function EvaluateXPathExpression(const AExpressionString: DOMString;
|
||||
|
||||
implementation
|
||||
|
||||
uses Math;
|
||||
uses Math, xmlutils;
|
||||
|
||||
{ Helper functions }
|
||||
|
||||
@ -635,6 +635,8 @@ begin
|
||||
for i := 0 to FArgs.Count - 1 do
|
||||
Args.Add(TXPathExprNode(FArgs[i]).Evaluate(AContext, AEnvironment));
|
||||
Result := Fn(AContext, Args);
|
||||
for i := 0 to FArgs.Count - 1 do
|
||||
TXPathVariable(Args[i]).Release;
|
||||
finally
|
||||
Args.Free;
|
||||
end;
|
||||
@ -703,7 +705,9 @@ begin
|
||||
NumberResult := Op1 * Op2;
|
||||
opDivide:
|
||||
NumberResult := Op1 / Op2;
|
||||
opMod:
|
||||
opMod: if IsNan(Op1) or IsNan(Op2) then
|
||||
NumberResult := NaN
|
||||
else
|
||||
NumberResult := Trunc(Op1) mod Trunc(Op2);
|
||||
end;
|
||||
finally
|
||||
@ -715,6 +719,92 @@ begin
|
||||
Result := TXPathNumberVariable.Create(NumberResult);
|
||||
end;
|
||||
|
||||
const
|
||||
reverse: array[TXPathCompareOp] of TXPathCompareOp = (
|
||||
opEqual, opNotEqual,
|
||||
opGreaterEqual, //opLess
|
||||
opGreater, //opLessEqual
|
||||
opLessEqual, //opGreater
|
||||
opLess //opGreaterEqual
|
||||
);
|
||||
|
||||
function CmpNumbers(const n1, n2: Extended; op: TXPathCompareOp): Boolean;
|
||||
begin
|
||||
result := (op = opNotEqual);
|
||||
if IsNan(n1) or IsNan(n2) then
|
||||
Exit; // NaNs are not equal
|
||||
case op of
|
||||
// TODO: should CompareValue() be used here?
|
||||
opLess: result := n1 < n2;
|
||||
opLessEqual: result := n1 <= n2;
|
||||
opGreater: result := n1 > n2;
|
||||
opGreaterEqual: result := n1 >= n2;
|
||||
else
|
||||
if IsInfinite(n1) or IsInfinite(n2) then
|
||||
result := n1 = n2
|
||||
else
|
||||
result := SameValue(n1, n2);
|
||||
result := result xor (op = opNotEqual);
|
||||
end;
|
||||
end;
|
||||
|
||||
function CmpStrings(const s1, s2: DOMString; op: TXPathCompareOp): Boolean;
|
||||
begin
|
||||
case op of
|
||||
opEqual: result := s1 = s2;
|
||||
opNotEqual: result := s1 <> s2;
|
||||
else
|
||||
result := CmpNumbers(StrToNumber(s1), StrToNumber(s2), op);
|
||||
end;
|
||||
end;
|
||||
|
||||
function CmpNodesetWithString(ns: TNodeSet; const s: DOMString; op: TXPathCompareOp): Boolean;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
Result := True;
|
||||
for i := 0 to ns.Count - 1 do
|
||||
begin
|
||||
if CmpStrings(NodeToText(TDOMNode(ns[i])), s, op) then
|
||||
exit;
|
||||
end;
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
function CmpNodesetWithNumber(ns: TNodeSet; const n: Extended; op: TXPathCompareOp): Boolean;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
Result := True;
|
||||
for i := 0 to ns.Count - 1 do
|
||||
begin
|
||||
if CmpNumbers(StrToNumber(NodeToText(TDOMNode(ns[i]))), n, op) then
|
||||
exit;
|
||||
end;
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
function CmpNodesetWithBoolean(ns: TNodeSet; b: Boolean; op: TXPathCompareOp): Boolean;
|
||||
begin
|
||||
// TODO: handles only equality
|
||||
result := ((ns.Count <> 0) = b) xor (op = opNotEqual);
|
||||
end;
|
||||
|
||||
function CmpNodesets(ns1, ns2: TNodeSet; op: TXPathCompareOp): Boolean;
|
||||
var
|
||||
i, j: Integer;
|
||||
s: DOMString;
|
||||
begin
|
||||
Result := True;
|
||||
for i := 0 to ns1.Count - 1 do
|
||||
begin
|
||||
s := NodeToText(TDOMNode(ns1[i]));
|
||||
for j := 0 to ns2.Count - 1 do
|
||||
if CmpStrings(s, NodeToText(TDOMNode(ns2[j])), op) then
|
||||
exit;
|
||||
end;
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
constructor TXPathCompareNode.Create(AOperator: TXPathCompareOp;
|
||||
AOperand1, AOperand2: TXPathExprNode);
|
||||
@ -729,102 +819,55 @@ 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
|
||||
begin
|
||||
NodeSet1 := Op1.AsNodeSet;
|
||||
if Op2.InheritsFrom(TXPathNodeSetVariable) then
|
||||
begin
|
||||
NodeSet2 := Op2.AsNodeSet;
|
||||
for i := 0 to NodeSet1.Count - 1 do
|
||||
begin
|
||||
s := NodeToText(TDOMNode(NodeSet1[i]));
|
||||
for j := 0 to NodeSet2.Count - 1 do
|
||||
if s = NodeToText(TDOMNode(NodeSet2[j])) then
|
||||
begin
|
||||
Result := True;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
end else
|
||||
begin
|
||||
s := Op2.AsText;
|
||||
for i := 0 to NodeSet1.Count - 1 do
|
||||
begin
|
||||
if NodeToText(TDOMNode(NodeSet1[i])) = s then
|
||||
begin
|
||||
Result := True;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
Result := False;
|
||||
end else if Op2.InheritsFrom(TXPathNodeSetVariable) then
|
||||
begin
|
||||
s := Op1.AsText;
|
||||
for i := 0 to NodeSet2.Count - 1 do
|
||||
if s = NodeToText(TDOMNode(NodeSet2[i])) then
|
||||
begin
|
||||
Result := True;
|
||||
exit;
|
||||
end;
|
||||
Result := False;
|
||||
end else if Op1.InheritsFrom(TXPathBooleanVariable) or
|
||||
Op2.InheritsFrom(TXPathBooleanVariable) then
|
||||
Result := Op1.AsBoolean = Op2.AsBoolean
|
||||
else if Op1.InheritsFrom(TXPathNumberVariable) or
|
||||
Op2.InheritsFrom(TXPathNumberVariable) then
|
||||
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;
|
||||
|
||||
nsnum: Integer;
|
||||
begin
|
||||
Op1 := FOperand1.Evaluate(AContext, AEnvironment);
|
||||
try
|
||||
Op2 := FOperand2.Evaluate(AContext, AEnvironment);
|
||||
try
|
||||
case FOperator of
|
||||
opEqual:
|
||||
BoolResult := EvalEqual;
|
||||
opNotEqual:
|
||||
BoolResult := not EvalEqual;
|
||||
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;
|
||||
nsnum := 0;
|
||||
if Op1 is TXPathNodeSetVariable then
|
||||
Inc(nsnum);
|
||||
if Op2 is TXPathNodeSetVariable then
|
||||
Inc(nsnum);
|
||||
case nsnum of
|
||||
0: begin // neither op is a nodeset
|
||||
if (FOperator in [opEqual, opNotEqual]) then
|
||||
begin
|
||||
if ((Op1 is TXPathBooleanVariable) or (Op2 is TXPathBooleanVariable)) then
|
||||
BoolResult := (Op1.AsBoolean = Op2.AsBoolean) xor (FOperator = opNotEqual)
|
||||
else if (Op1 is TXPathNumberVariable) or (Op2 is TXPathNumberVariable) then
|
||||
BoolResult := CmpNumbers(Op1.AsNumber, Op2.AsNumber, FOperator)
|
||||
else
|
||||
BoolResult := (Op1.AsText = Op2.AsText) xor (FOperator = opNotEqual);
|
||||
end
|
||||
else
|
||||
BoolResult := CmpNumbers(Op1.AsNumber, Op2.AsNumber, FOperator);
|
||||
end;
|
||||
|
||||
1: begin
|
||||
if Op1 is TXPathNodeSetVariable then
|
||||
begin
|
||||
if Op2 is TXPathNumberVariable then
|
||||
BoolResult := CmpNodesetWithNumber(Op1.AsNodeSet, Op2.AsNumber, FOperator)
|
||||
else if Op2 is TXPathStringVariable then
|
||||
BoolResult := CmpNodesetWithString(Op1.AsNodeSet, Op2.AsText, FOperator)
|
||||
else
|
||||
BoolResult := CmpNodesetWithBoolean(Op1.AsNodeSet, Op2.AsBoolean, FOperator);
|
||||
end
|
||||
else // Op2 is nodeset
|
||||
begin
|
||||
if Op1 is TXPathNumberVariable then
|
||||
BoolResult := CmpNodesetWithNumber(Op2.AsNodeSet, Op1.AsNumber, reverse[FOperator])
|
||||
else if Op1 is TXPathStringVariable then
|
||||
BoolResult := CmpNodesetWithString(Op2.AsNodeSet, Op1.AsText, reverse[FOperator])
|
||||
else
|
||||
BoolResult := CmpNodesetWithBoolean(Op2.AsNodeSet, Op1.AsBoolean, reverse[FOperator]);
|
||||
end;
|
||||
end;
|
||||
else // both ops are nodesets
|
||||
BoolResult := CmpNodesets(Op1.AsNodeSet, Op2.AsNodeSet, FOperator);
|
||||
end;
|
||||
finally
|
||||
Op2.Release;
|
||||
@ -1357,21 +1400,11 @@ begin
|
||||
end;
|
||||
|
||||
function TXPathNodeSetVariable.AsText: DOMString;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
if FValue.Count = 0 then
|
||||
SetLength(Result, 0)
|
||||
Result := ''
|
||||
else
|
||||
begin
|
||||
Result := '';
|
||||
for i := 0 to FValue.Count - 1 do
|
||||
begin
|
||||
if i > 0 then
|
||||
Result := Result + LineEnding;
|
||||
Result := Result + NodeToText(TDOMNode(FValue[i]));
|
||||
end;
|
||||
end;
|
||||
Result := NodeToText(TDOMNode(FValue.First));
|
||||
end;
|
||||
|
||||
function TXPathNodeSetVariable.AsBoolean: Boolean;
|
||||
@ -1803,6 +1836,7 @@ begin
|
||||
if NextToken = tkString then
|
||||
begin
|
||||
// TODO: Handle processing-instruction('name') constructs
|
||||
CurStep.NodeTestString := CurTokenString;
|
||||
NextToken;
|
||||
end;
|
||||
if CurToken <> tkRightBracket then
|
||||
@ -2434,23 +2468,33 @@ end;
|
||||
function TXPathEnvironment.xpStartsWith(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
||||
var
|
||||
s1, s2: DOMString;
|
||||
res: Boolean;
|
||||
begin
|
||||
if Args.Count <> 2 then
|
||||
EvaluationError(SEvalInvalidArgCount);
|
||||
s1 := TXPathVariable(Args[0]).AsText;
|
||||
s2 := TXPathVariable(Args[1]).AsText;
|
||||
Result := TXPathBooleanVariable.Create(Pos(s2, s1) = 1);
|
||||
if s2 = '' then
|
||||
res := True
|
||||
else
|
||||
res := Pos(s2, s1) = 1;
|
||||
Result := TXPathBooleanVariable.Create(res);
|
||||
end;
|
||||
|
||||
function TXPathEnvironment.xpContains(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
||||
var
|
||||
s1, s2: DOMString;
|
||||
res: Boolean;
|
||||
begin
|
||||
if Args.Count <> 2 then
|
||||
EvaluationError(SEvalInvalidArgCount);
|
||||
s1 := TXPathVariable(Args[0]).AsText;
|
||||
s2 := TXPathVariable(Args[1]).AsText;
|
||||
Result := TXPathBooleanVariable.Create(Pos(s2, s1) <> 0);
|
||||
if s2 = '' then
|
||||
res := True
|
||||
else
|
||||
res := Pos(s2, s1) <> 0;
|
||||
Result := TXPathBooleanVariable.Create(res);
|
||||
end;
|
||||
|
||||
function TXPathEnvironment.xpSubstringBefore(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
||||
@ -2525,11 +2569,27 @@ begin
|
||||
end;
|
||||
|
||||
function TXPathEnvironment.xpNormalizeSpace(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
||||
var
|
||||
s: DOMString;
|
||||
p: DOMPChar;
|
||||
i: Integer;
|
||||
begin
|
||||
if Args.Count > 1 then
|
||||
EvaluationError(SEvalInvalidArgCount);
|
||||
// TODO: xmlutils.NormalizeSpace is not appropriate because it handles only #32 chars
|
||||
EvaluationError(SEvalFunctionNotImplementedYet, ['normalize-space']); // !!!
|
||||
if Args.Count = 0 then
|
||||
s := NodeToText(Context.ContextNode)
|
||||
else
|
||||
s := TXPathVariable(Args[0]).AsText;
|
||||
UniqueString(s);
|
||||
p := DOMPChar(s);
|
||||
for i := 1 to Length(s) do
|
||||
begin
|
||||
if (p^ = #10) or (p^ = #13) or (p^ = #9) then
|
||||
p^ := #32;
|
||||
Inc(p);
|
||||
end;
|
||||
NormalizeSpaces(s);
|
||||
Result := TXPathStringVariable.Create(s);
|
||||
end;
|
||||
|
||||
function TXPathEnvironment.xpTranslate(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
||||
@ -2589,24 +2649,42 @@ begin
|
||||
end;
|
||||
|
||||
function TXPathEnvironment.xpSum(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
||||
var
|
||||
i: Integer;
|
||||
ns: TNodeSet;
|
||||
sum: Extended;
|
||||
begin
|
||||
if Args.Count <> 1 then
|
||||
EvaluationError(SEvalInvalidArgCount);
|
||||
EvaluationError(SEvalFunctionNotImplementedYet, ['sum']); // !!!
|
||||
ns := TXPathVariable(Args[0]).AsNodeSet;
|
||||
sum := 0.0;
|
||||
for i := 0 to ns.Count-1 do
|
||||
sum := sum + StrToNumber(NodeToText(TDOMNode(ns[i])));
|
||||
Result := TXPathNumberVariable.Create(sum);
|
||||
end;
|
||||
|
||||
function TXPathEnvironment.xpFloor(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
||||
var
|
||||
n: Extended;
|
||||
begin
|
||||
if Args.Count <> 1 then
|
||||
EvaluationError(SEvalInvalidArgCount);
|
||||
Result := TXPathNumberVariable.Create(Floor(TXPathVariable(Args[0]).AsNumber));
|
||||
n := TXPathVariable(Args[0]).AsNumber;
|
||||
if not IsNan(n) then
|
||||
n := floor(n);
|
||||
Result := TXPathNumberVariable.Create(n);
|
||||
end;
|
||||
|
||||
function TXPathEnvironment.xpCeiling(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
||||
var
|
||||
n: Extended;
|
||||
begin
|
||||
if Args.Count <> 1 then
|
||||
EvaluationError(SEvalInvalidArgCount);
|
||||
Result := TXPathNumberVariable.Create(Ceil(TXPathVariable(Args[0]).AsNumber));
|
||||
n := TXPathVariable(Args[0]).AsNumber;
|
||||
if not IsNan(n) then
|
||||
n := ceil(n);
|
||||
Result := TXPathNumberVariable.Create(n);
|
||||
end;
|
||||
|
||||
function TXPathEnvironment.xpRound(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
**********************************************************************}
|
||||
program xpathts;
|
||||
{$mode objfpc}{$h+}
|
||||
{$mode delphi}{$h+}
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Math,
|
||||
@ -25,6 +25,7 @@ type
|
||||
TResultType = (rtString, rtNumber, rtBool, rtNodeset);
|
||||
|
||||
TTestRec = record
|
||||
data: string; // UTF-8 encoded
|
||||
expr: DOMString;
|
||||
case rt: TResultType of
|
||||
rtString: (s: DOMPChar); // cannot use DOMString here
|
||||
@ -32,6 +33,7 @@ type
|
||||
rtBool: (b: Boolean);
|
||||
end;
|
||||
|
||||
{$warnings off}
|
||||
const
|
||||
BaseTests: array[0..4] of TTestRec = (
|
||||
(expr: '1'; rt: rtNumber; n: 1),
|
||||
@ -41,7 +43,7 @@ const
|
||||
(expr: '(1+2)*(3+4)'; rt: rtNumber; n: 21)
|
||||
);
|
||||
|
||||
CompareTests: array[0..45] of TTestRec = (
|
||||
CompareTests: array[0..46] of TTestRec = (
|
||||
(expr: '0<0'; rt: rtBool; b: False),
|
||||
(expr: '0<=0'; rt: rtBool; b: True),
|
||||
(expr: '0>0'; rt: rtBool; b: False),
|
||||
@ -58,6 +60,7 @@ const
|
||||
(expr: '1<=1'; rt: rtBool; b: True),
|
||||
(expr: '1>1'; rt: rtBool; b: False),
|
||||
(expr: '1>=1'; rt: rtBool; b: True),
|
||||
(expr: '0>-0'; rt: rtBool; b: False),
|
||||
(expr: '"0"<1'; rt: rtBool; b: True),
|
||||
(expr: '"0"<=1'; rt: rtBool; b: True),
|
||||
(expr: '"0">1'; rt: rtBool; b: False),
|
||||
@ -90,6 +93,141 @@ const
|
||||
(expr: '"a" < "0.0"'; rt: rtBool; b: False)
|
||||
);
|
||||
|
||||
nscmp = '<doc>'#10+
|
||||
'<j l="12" w="33">first</j>'#10+
|
||||
'<j l="17" w="45">second</j>'#10+
|
||||
'<j l="16" w="78">third</j>'#10+
|
||||
'<j l="12" w="33">fourth</j>'#10+
|
||||
'</doc>';
|
||||
|
||||
nscmp2 = '<doc>'#10+
|
||||
'<j l="12" w="45">first</j>'#10+
|
||||
'<j l="17" w="45">second</j>'#10+
|
||||
'<j l="16" w="78">third</j>'#10+
|
||||
'<j l="12" w="33">fourth</j>'#10+
|
||||
'</doc>';
|
||||
|
||||
simple = '<doc>test</doc>';
|
||||
|
||||
bool58 = '<doc>'#10+
|
||||
'<av>'#10+
|
||||
' <a>'#10+
|
||||
' <b>b</b>'#10+
|
||||
' <c>c</c>'#10+
|
||||
' <d>d</d>'#10+
|
||||
' <e>e</e>'#10+
|
||||
' </a>'#10+
|
||||
' <v>'#10+
|
||||
' <w>w</w>'#10+
|
||||
' <x>x</x>'#10+
|
||||
' <y>y</y>'#10+
|
||||
' <z>z</z>'#10+
|
||||
' </v>'#10+
|
||||
' <a>'#10+
|
||||
' <b>fe</b>'#10+
|
||||
' <c>fi</c>'#10+
|
||||
' <d>fo</d>'#10+
|
||||
' <e>fu</e>'#10+
|
||||
' </a>'#10+
|
||||
' <v>'#10+
|
||||
' <w>fee</w>'#10+
|
||||
' <x>fii</x>'#10+
|
||||
' <y>foo</y>'#10+
|
||||
' <z>fom</z>'#10+
|
||||
' </v>'#10+
|
||||
' <j>foo</j>'#10+
|
||||
' <j>foo</j>'#10+
|
||||
' <j>foo</j>'#10+
|
||||
' <j>foo</j>'#10+
|
||||
'</av>'#10+
|
||||
'</doc>';
|
||||
|
||||
bool84='<doc>'#10+
|
||||
'<avj>'#10+
|
||||
' <good>'#10+
|
||||
' <b>12</b>'#10+
|
||||
' <c>34</c>'#10+
|
||||
' <d>56</d>'#10+
|
||||
' <e>78</e>'#10+
|
||||
' </good>'#10+
|
||||
'</avj>'#10+
|
||||
'</doc>';
|
||||
|
||||
bool85='<doc>'#10+
|
||||
'<avj>'#10+
|
||||
' <bool>'#10+
|
||||
' <b>true</b>'#10+
|
||||
' <c></c>'#10+
|
||||
' <d>false?</d>'#10+
|
||||
' <e>1</e>'#10+
|
||||
' <f>0</f>'#10+
|
||||
' </bool>'#10+
|
||||
'</avj>'#10+
|
||||
'</doc>';
|
||||
|
||||
str04='<doc>'#10+
|
||||
'<a>Testing this</a>'#10+
|
||||
'<b>and this too</b>'#10+
|
||||
'</doc>';
|
||||
|
||||
NodesetCompareTests: array[0..38] of TTestRec = (
|
||||
{ same nodeset }
|
||||
(data: nscmp; expr: 'j[@l="12"] = j[@w="33"]'; rt: rtBool; b: True), // #70
|
||||
{ disjoint nodesets }
|
||||
(data: nscmp; expr: 'j[@l="12"] = j[@l="17"]'; rt: rtBool; b: False), // #71
|
||||
{ both have one common node }
|
||||
(data: nscmp2; expr: 'j[@l="12"] = j[@w="45"]'; rt: rtBool; b: True), // #72
|
||||
{ same nodeset - unequal }
|
||||
(data: nscmp; expr: 'j[@l="12"] != j[@w="33"]'; rt: rtBool; b: True), // #73
|
||||
{ disjoint - unequal }
|
||||
(data: nscmp; expr: 'j[@l="12"] != j[@l="17"]'; rt: rtBool; b: True), // #74
|
||||
{ one common node - unequal }
|
||||
(data: nscmp2; expr: 'j[@l="12"] != j[@w="45"]'; rt: rtBool; b: True), // #75
|
||||
{ single common node - unequal }
|
||||
(data: nscmp2; expr: 'j[@l="16"] != j[@w="78"]'; rt: rtBool; b: False),// #76
|
||||
{ nodeset vs. string }
|
||||
(data: bool58; expr: '/doc/av//*="foo"'; rt: rtBool; b: True), // #58.1
|
||||
(data: bool58; expr: 'not(/doc/av//*!="foo")'; rt: rtBool; b: False), // #58.2
|
||||
(data: bool58; expr: '/doc/av//j="foo"'; rt: rtBool; b: True), // #58.3
|
||||
(data: bool58; expr: 'not(/doc/av//j!="foo")'; rt: rtBool; b: True), // #58.4
|
||||
{ empty nodeset vs. string. Data differs, but that doesn't matter }
|
||||
(data: bool58; expr: '/doc/avj//k="foo"'; rt: rtBool; b: False), // #59.1
|
||||
(data: bool58; expr: 'not(/doc/avj//k="foo")'; rt: rtBool; b: True), // #59.2
|
||||
(data: bool58; expr: '/doc/avj//k!="foo"'; rt: rtBool; b: False), // #59.3
|
||||
(data: bool58; expr: 'not(/doc/avj//k!="foo")'; rt: rtBool; b: True), // #59.4
|
||||
{ nodeset vs. number }
|
||||
(data: bool84; expr: '/doc/avj/good/*=34'; rt: rtBool; b: True), // #84.1
|
||||
(data: bool84; expr: 'not(/doc/avj/good/*=34)'; rt: rtBool; b: False), // #84.2
|
||||
(data: bool84; expr: '/doc/avj/good/*!=34'; rt: rtBool; b: True), // #84.3
|
||||
(data: bool84; expr: 'not(/doc/avj/good/*!=34)'; rt: rtBool; b: False),// #84.4
|
||||
{ same with reversed order of operands }
|
||||
(data: bool84; expr: '34=/doc/avj/good/*'; rt: rtBool; b: True), // #84.5
|
||||
(data: bool84; expr: 'not(34=/doc/avj/good/*)'; rt: rtBool; b: False), // #84.6
|
||||
(data: bool84; expr: '34!=/doc/avj/good/*'; rt: rtBool; b: True), // #84.7
|
||||
(data: bool84; expr: 'not(34!=/doc/avj/good/*)'; rt: rtBool; b: False),// #84.8
|
||||
{ nodeset vs. boolean }
|
||||
(data: bool85; expr: '/doc/avj/bool/*=true()'; rt: rtBool; b: True), // #85.1
|
||||
(data: bool85; expr: 'not(/doc/avj/bool/*=true())'; rt: rtBool; b: False), // #85.2
|
||||
(data: bool85; expr: '/doc/avj/bool/*!=true()'; rt: rtBool; b: False), // #85.3
|
||||
(data: bool85; expr: 'not(/doc/avj/bool/*!=true())'; rt: rtBool; b: True), // #85.4
|
||||
{ same with reversed order of operands }
|
||||
(data: bool85; expr: 'true()=/doc/avj/bool/*'; rt: rtBool; b: True), // #85.5
|
||||
(data: bool85; expr: 'not(true()=/doc/avj/bool/*)'; rt: rtBool; b: False), // #85.6
|
||||
(data: bool85; expr: 'true()!=/doc/avj/bool/*'; rt: rtBool; b: False), // #85.7
|
||||
(data: bool85; expr: 'not(true()!=/doc/avj/bool/*)'; rt: rtBool; b: True), // #85.8
|
||||
{ empty nodeset vs. boolean }
|
||||
(data: bool85; expr: '/doc/avj/none/*=true()'; rt: rtBool; b: False), // #86.1
|
||||
(data: bool85; expr: 'not(/doc/avj/none/*=true())'; rt: rtBool; b: True), // #86.2
|
||||
(data: bool85; expr: '/doc/avj/none/*!=true()'; rt: rtBool; b: True), // #86.3
|
||||
(data: bool85; expr: 'not(/doc/avj/none/*!=true())'; rt: rtBool; b: False),// #86.4
|
||||
{ same with reversed order of operands }
|
||||
(data: bool85; expr: 'true()=/doc/avj/none/*'; rt: rtBool; b: False), // #86.5
|
||||
(data: bool85; expr: 'not(true()=/doc/avj/none/*)'; rt: rtBool; b: True), // #86.6
|
||||
(data: bool85; expr: 'true()!=/doc/avj/none/*'; rt: rtBool; b: True), // #86.7
|
||||
(data: bool85; expr: 'not(true()!=/doc/avj/none/*)'; rt: rtBool; b: False) // #86.8
|
||||
|
||||
);
|
||||
|
||||
EqualityTests: array[0..25] of TTestRec = (
|
||||
(expr: '1=1'; rt: rtBool; b: True),
|
||||
(expr: '1!=1'; rt: rtBool; b: False),
|
||||
@ -125,7 +263,42 @@ const
|
||||
(expr: '"a"!=0.0'; rt: rtBool; b: True)
|
||||
);
|
||||
|
||||
FloatTests: array[0..60] of TTestRec = (
|
||||
math88='<doc>'+
|
||||
'<n0>0</n0>'+
|
||||
'<n1>1</n1>'+
|
||||
'<n2>2</n2>'+
|
||||
'<n3>3</n3>'+
|
||||
'<n4>4</n4>'+
|
||||
'<n5>5</n5>'+
|
||||
'<n6>6</n6>'+
|
||||
'<n7>2</n7>'+
|
||||
'<n8>6</n8>'+
|
||||
'<n9>10</n9>'+
|
||||
'<n10>3</n10>'+
|
||||
'</doc>';
|
||||
|
||||
math85='<doc>'+
|
||||
'<n0>0</n0>'+
|
||||
'<n1>1</n1>'+
|
||||
'<n2>2</n2>'+
|
||||
'<n3>3</n3>'+
|
||||
'<n4>4</n4>'+
|
||||
'<e>five</e>'+
|
||||
'</doc>';
|
||||
|
||||
math80='<doc>'+
|
||||
'<n1 attrib="10">5</n1>'+
|
||||
'<n2 attrib="4">2</n2>'+
|
||||
'<div attrib="-5">-5</div>'+
|
||||
'<mod attrib="-2">2</mod>'+
|
||||
'</doc>';
|
||||
|
||||
math69='<doc>'+
|
||||
'<n-1 attrib="9">3</n-1>'+
|
||||
'<n-2 attrib="1">7</n-2>'+
|
||||
'</doc>';
|
||||
|
||||
FloatTests: array[0..70] of TTestRec = (
|
||||
(expr: '1'; rt: rtNumber; n: 1),
|
||||
(expr: '123'; rt: rtNumber; n: 123),
|
||||
(expr: '1.23'; rt: rtNumber; n: 1.23),
|
||||
@ -189,13 +362,39 @@ const
|
||||
(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: '2 mod number("xxx")'; rt: rtNumber; n: NaN),
|
||||
(expr: 'number("xxx") mod 3'; rt: rtNumber; n: NaN),
|
||||
(expr: '8 mod 3 = 2'; rt: rtBool; b: True),
|
||||
(data: math88; expr: '(n1*n2*n3*n4*n5*n6)div n7 div n8 div n9 div n10'; rt: rtNumber; n: 2),
|
||||
(data: math85; expr: '((((((n3+5)*(3)+(((n2)+2)*(n1 - 6)))-(n4 - n2))+(-(4-6)))))'; rt: rtNumber; n: 4),
|
||||
(data: math80; expr: 'div mod mod'; rt: rtNumber; n: -1),
|
||||
(data: math69; expr: '-(n-2/@attrib) - -(n-1/@attrib)'; rt: rtNumber; n: 8),
|
||||
(data: math69; expr: '-n-2/@attrib --n-1/@attrib'; rt: rtNumber; n: 8),
|
||||
(data: math69; expr: '-n-2 --n-1'; rt: rtNumber; n: -4),
|
||||
|
||||
// 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)
|
||||
(expr: '75-50-25 and count(5)'; rt: rtBool; b: False),
|
||||
(expr: '"1" and "0"'; rt: rtBool; b: True),
|
||||
(expr: '0 or ""'; rt: rtBool; b: False)
|
||||
);
|
||||
|
||||
FunctionTests: array[0..36] of TTestRec = (
|
||||
math95='<doc>'+
|
||||
'<e>1</e>'+
|
||||
'<e>2</e>'+
|
||||
'<e>3</e>'+
|
||||
'<e>4</e>'+
|
||||
'<e>five</e>'+
|
||||
'</doc>';
|
||||
|
||||
math96='<doc>'+
|
||||
'<e>17</e>'+
|
||||
'<e>-5</e>'+
|
||||
'<e>8</e>'+
|
||||
'<e>-37</e>'+
|
||||
'</doc>';
|
||||
|
||||
FunctionTests: array[0..45] of TTestRec = (
|
||||
// last()
|
||||
// position()
|
||||
// count()
|
||||
@ -211,13 +410,16 @@ const
|
||||
(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
|
||||
}
|
||||
|
||||
(data: simple; expr: 'boolean(/doc)'; rt: rtBool; b: True), // #40
|
||||
(data: simple; expr: 'boolean(foo)'; rt: rtBool; b: False), // #41
|
||||
|
||||
(expr: 'true()'; rt: rtBool; b: True),
|
||||
(expr: 'false()'; rt: rtBool; b: False),
|
||||
(expr: 'not(true())'; rt: rtBool; b: False),
|
||||
(expr: 'not(false())'; rt: rtBool; b: True),
|
||||
(expr: 'not("")'; rt: rtBool; b: True),
|
||||
{
|
||||
not()
|
||||
lang() -- involves nodes
|
||||
}
|
||||
|
||||
@ -226,9 +428,9 @@ const
|
||||
(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
|
||||
}
|
||||
|
||||
(data: math95; expr: 'sum(e)'; rt: rtNumber; n: NaN),
|
||||
(data: math96; expr: 'sum(e)'; rt: rtNumber; n: -17),
|
||||
|
||||
(expr: 'floor(0.1)'; rt: rtNumber; n: 0),
|
||||
(expr: 'floor(-0.1)'; rt: rtNumber; n: -1),
|
||||
@ -236,6 +438,7 @@ const
|
||||
(expr: 'floor(0)'; rt: rtNumber; n: 0),
|
||||
(expr: 'floor(5.2)'; rt: rtNumber; n: 5),
|
||||
(expr: 'floor(-5.2)'; rt: rtNumber; n: -6),
|
||||
(expr: 'floor("NaN")'; rt: rtNumber; n: NaN),
|
||||
|
||||
(expr: 'ceiling(0.1)'; rt: rtNumber; n: 1),
|
||||
(expr: 'ceiling(-0.1)'; rt: rtNumber; n: 0),
|
||||
@ -243,6 +446,7 @@ const
|
||||
(expr: 'ceiling(0)'; rt: rtNumber; n: 0),
|
||||
(expr: 'ceiling(5.2)'; rt: rtNumber; n: 6),
|
||||
(expr: 'ceiling(-5.2)'; rt: rtNumber; n: -5),
|
||||
(expr: 'ceiling("NaN")'; rt: rtNumber; n: NaN),
|
||||
|
||||
(expr: 'round(0.1)'; rt: rtNumber; n: 0),
|
||||
(expr: 'round(5.2)'; rt: rtNumber; n: 5),
|
||||
@ -257,7 +461,31 @@ const
|
||||
(expr: 'round(-1 div 0)'; rt: rtNumber; n: -Infinity)
|
||||
);
|
||||
|
||||
StringTests: array[0..43] of TTestRec = (
|
||||
str14 ='<doc>'#10+
|
||||
' <av>'#10+
|
||||
' <a>'#10+
|
||||
' <b>b</b>'#10+
|
||||
' <c>c</c>'#10+
|
||||
' <d>d</d>'#10+
|
||||
' <e>e</e>'#10+
|
||||
' </a>'#10+
|
||||
' <v>'#10+
|
||||
' <w>w</w>'#10+
|
||||
' <x>x</x>'#10+
|
||||
' <y>y</y>'#10+
|
||||
' <z>z</z>'#10+
|
||||
' </v>'#10+
|
||||
' </av>'#10+
|
||||
'</doc>';
|
||||
|
||||
out14 =#10+
|
||||
' b'#10+
|
||||
' c'#10+
|
||||
' d'#10+
|
||||
' e'#10+
|
||||
' ';
|
||||
|
||||
StringTests: array[0..59] 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'),
|
||||
@ -267,19 +495,32 @@ const
|
||||
(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
|
||||
(data: str14; expr: 'string(av//*)'; rt: rtString; s: out14),
|
||||
|
||||
(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: 'concat("cd", 34)'; rt: rtString; s: 'cd34'), // #101
|
||||
(expr: 'concat(false(), "ly")'; rt: rtString; s: 'falsely'), // #104
|
||||
|
||||
(expr: 'starts-with("tititoto","titi")'; rt: rtBool; b: True),
|
||||
(expr: 'starts-with("tititoto","to")'; rt: rtBool; b: False),
|
||||
(expr: 'starts-with("ab", "abc")'; rt: rtBool; b: False),
|
||||
(expr: 'starts-with("abc", "")'; rt: rtBool; b: True), // xalan/string/string48
|
||||
(expr: 'starts-with("", "")'; rt: rtBool; b: True), // #49
|
||||
|
||||
|
||||
|
||||
(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: 'contains("ab", "abc")'; rt: rtBool; b: False), // #59
|
||||
(expr: 'contains("abc", "bcd")'; rt: rtBool; b: False), // #60
|
||||
(expr: 'contains("abc", "")'; rt: rtBool; b: True), // #61
|
||||
(expr: 'contains("", "")'; rt: rtBool; b: True), // #62
|
||||
// 'contains(concat(.,'BC'),concat('A','B','C'))' == true
|
||||
|
||||
(expr: 'substring("12345",2,3)'; rt: rtString; s: '234'),
|
||||
(expr: 'substring("12345",2)'; rt: rtString; s: '2345'),
|
||||
@ -310,12 +551,17 @@ const
|
||||
|
||||
(expr: 'string-length("")'; rt: rtNumber; n: 0),
|
||||
(expr: 'string-length("titi")'; rt: rtNumber; n: 4),
|
||||
{
|
||||
normalize-space()
|
||||
}
|
||||
(data: simple; expr: 'string-length(.)'; rt: rtNumber; n: 4), // #02 modified
|
||||
(data: str04; expr: 'string-length(/)'; rt: rtNumber; n:27), // #04.1 modified
|
||||
(data: str04; expr: 'string-length(/doc/a)'; rt: rtNumber; n: 12), // #04.2
|
||||
(data: str04; expr: 'string-length()'; rt: rtNumber; n: 27),
|
||||
(expr: 'normalize-space("'#9#10#13' ab cd'#10#13#9'ef'#9#10#13' ")'; rt: rtString; s: 'ab cd ef'),
|
||||
|
||||
(expr: 'translate("bar", "abc", "ABC")'; rt: rtString; s: 'BAr'),
|
||||
(expr: 'translate("--aaa--","abc-","ABC")'; rt: rtString; s: 'AAA')
|
||||
(expr: 'translate("--aaa--","abc-","ABC")'; rt: rtString; s: 'AAA'),
|
||||
(expr: 'translate("ddaaadddd","abcd","ABCxy")'; rt: rtString; s: 'xxAAAxxxx') // #96
|
||||
);
|
||||
{$warnings on}
|
||||
|
||||
var
|
||||
FailCount: Integer = 0;
|
||||
@ -360,18 +606,40 @@ begin
|
||||
Inc(FailCount);
|
||||
end;
|
||||
|
||||
function ParseString(const data: string): TXMLDocument;
|
||||
var
|
||||
parser: TDOMParser;
|
||||
src: TXMLInputSource;
|
||||
begin
|
||||
parser := TDOMParser.Create;
|
||||
try
|
||||
parser.Options.PreserveWhitespace := True;
|
||||
src := TXMLInputSource.Create(data);
|
||||
try
|
||||
parser.Parse(src, Result);
|
||||
finally
|
||||
src.Free;
|
||||
end;
|
||||
finally
|
||||
parser.Free;
|
||||
end;
|
||||
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
|
||||
for i := 0 to High(tests) do
|
||||
begin
|
||||
if tests[i].data <> '' then
|
||||
doc := ParseString(tests[i].data)
|
||||
else
|
||||
doc := TXMLDocument.Create;
|
||||
try
|
||||
try
|
||||
rslt := EvaluateXPathExpression(tests[i].expr, doc);
|
||||
rslt := EvaluateXPathExpression(tests[i].expr, doc.DocumentElement);
|
||||
try
|
||||
CheckResult(tests[i], rslt);
|
||||
finally
|
||||
@ -383,16 +651,17 @@ begin
|
||||
SysUtils.ShowException(ExceptObject, ExceptAddr);
|
||||
Inc(FailCount);
|
||||
end;
|
||||
finally
|
||||
doc.Free;
|
||||
end;
|
||||
finally
|
||||
doc.Free;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
begin
|
||||
DecimalSeparator := '.';
|
||||
DoSuite(BaseTests);
|
||||
DoSuite(CompareTests);
|
||||
DoSuite(NodesetCompareTests);
|
||||
DoSuite(EqualityTests);
|
||||
DoSuite(FloatTests);
|
||||
DoSuite(FunctionTests);
|
||||
|
Loading…
Reference in New Issue
Block a user