lazarus/components/lazutils/laz2_xpath.pas
2016-06-18 16:40:04 +00:00

2982 lines
77 KiB
ObjectPascal

{
**********************************************************************
This file is part of LazUtils.
It is based on the xpath unit of the Free Component Library.
See the file COPYING.FPC, included in this distribution,
for details about the license.
**********************************************************************
Implementation of the XML Path Language (XPath) for Free Pascal
Copyright (c) 2000 - 2003 by
Areca Systems GmbH / Sebastian Guenther, sg@freepascal.org
}
{$mode objfpc}
{$H+}
unit laz2_xpath;
interface
uses
Math, SysUtils, Classes, LazUtilsStrConsts,
laz2_DOM, laz2_xmlutils;
type
TXPathContext = class;
TXPathEnvironment = class;
TXPathVariable = class;
{ XPath lexical scanner }
TXPathToken = ( // [28] - [38]
tkInvalid,
tkEndOfStream,
tkIdentifier,
tkNSNameTest, // NCName:*
tkString,
tkNumber,
tkVariable, // $QName
tkLeftBracket, // "("
tkRightBracket, // ")"
tkAsterisk, // "*"
tkPlus, // "+"
tkComma, // ","
tkMinus, // "-"
tkDot, // "."
tkDotDot, // ".."
tkSlash, // "/"
tkSlashSlash, // "//"
tkColonColon, // "::"
tkLess, // "<"
tkLessEqual, // "<="
tkEqual, // "="
tkNotEqual, // "!="
tkGreater, // ">"
tkGreaterEqual, // ">="
tkAt, // "@"
tkLeftSquareBracket, // "["
tkRightSquareBracket, // "]"
tkPipe // "|"
);
TXPathKeyword = (
// axis names
xkNone, xkAncestor, xkAncestorOrSelf, xkAttribute, xkChild,
xkDescendant, xkDescendantOrSelf, xkFollowing, xkFollowingSibling,
xkNamespace, xkParent, xkPreceding, xkPrecedingSibling, xkSelf,
// node tests
xkComment, xkText, xkProcessingInstruction, xkNode,
// operators
xkAnd, xkOr, xkDiv, xkMod,
// standard functions
xkLast, xkPosition, xkCount, xkId, xkLocalName, xkNamespaceUri,
xkName, xkString, xkConcat, xkStartsWith, xkContains,
xkSubstringBefore, xkSubstringAfter, xkSubstring,
xkStringLength, xkNormalizeSpace, xkTranslate, xkBoolean,
xkNot, xkTrue, xkFalse, xkLang, xkNumber, xkSum, xkFloor,
xkCeiling, xkRound
);
{ XPath expression parse tree }
TXPathExprNode = class
protected
function EvalPredicate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): Boolean;
public
function Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable; virtual; abstract;
end;
TXPathNodeArray = array of TXPathExprNode;
TXPathConstantNode = class(TXPathExprNode)
private
FValue: TXPathVariable;
public
constructor Create(AValue: TXPathVariable);
destructor Destroy; override;
function Evaluate({%H-}AContext: TXPathContext;
{%H-}AEnvironment: TXPathEnvironment): TXPathVariable; override;
end;
TXPathVariableNode = class(TXPathExprNode)
private
FName: DOMString;
public
constructor Create(const AName: DOMString);
function Evaluate({%H-}AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable; override;
end;
TXPathFunctionNode = class(TXPathExprNode)
private
FName: DOMString;
FArgs: TXPathNodeArray;
public
constructor Create(const AName: DOMString; const Args: TXPathNodeArray);
destructor Destroy; override;
function Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable; override;
end;
TXPathNegationNode = class(TXPathExprNode)
private
FOperand: TXPathExprNode;
public
constructor Create(AOperand: TXPathExprNode);
destructor Destroy; override;
function Evaluate(AContext: TXPathContext;
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(TXPathBinaryNode)
private
FOperator: TXPathMathOp;
public
constructor Create(AOperator: TXPathMathOp;
AOperand1, AOperand2: TXPathExprNode);
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 (and, or)
TXPathBooleanOp = (opOr, opAnd);
TXPathBooleanOpNode = class(TXPathBinaryNode)
private
FOperator: TXPathBooleanOp;
public
constructor Create(AOperator: TXPathBooleanOp;
AOperand1, AOperand2: TXPathExprNode);
function Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable; override;
end;
// Node for unions (see [18])
TXPathUnionNode = class(TXPathBinaryNode)
public
constructor Create(AOperand1, AOperand2: TXPathExprNode);
function Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable; override;
end;
TNodeSet = TFPList;
// Filter node (for [20])
TXPathFilterNode = class(TXPathExprNode)
private
FLeft: TXPathExprNode;
FPredicates: TXPathNodeArray;
procedure ApplyPredicates(Nodes: TNodeSet; AEnvironment: TXPathEnvironment);
public
constructor Create(AExpr: TXPathExprNode);
destructor Destroy; override;
function Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable; override;
end;
// Node for location paths
TAxis = (axisInvalid, axisAncestor, axisAncestorOrSelf, axisAttribute,
axisChild, axisDescendant, axisDescendantOrSelf, axisFollowing,
axisFollowingSibling, axisNamespace, axisParent, axisPreceding,
axisPrecedingSibling, axisSelf, axisRoot);
TNodeTestType = (ntAnyPrincipal, ntName, ntTextNode,
ntCommentNode, ntPINode, ntAnyNode);
TStep = class(TXPathFilterNode)
private
procedure SelectNodes(ANode: TDOMNode; out ResultNodes: TNodeSet);
public
Axis: TAxis;
NodeTestType: TNodeTestType;
NodeTestString: DOMString;
NSTestString: DOMString;
constructor Create(aAxis: TAxis; aTest: TNodeTestType);
function Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable; override;
end;
{ Exceptions }
EXPathEvaluationError = class(Exception);
procedure EvaluationError(const Msg: String);
procedure EvaluationError(const Msg: String; const Args: array of const);
type
{ XPath variables and results classes }
TXPathVariable = class
protected
FRefCount: Integer;
procedure Error(const Msg: String; const Args: array of const);
public
class function TypeName: String; virtual; abstract;
procedure Release;
function AsNodeSet: TNodeSet; virtual;
function AsBoolean: Boolean; virtual; abstract;
function AsNumber: Extended; virtual; abstract;
function AsText: DOMString; virtual; abstract;
end;
TXPathNodeSetVariable = class(TXPathVariable)
private
FValue: TNodeSet;
public
constructor Create(AValue: TNodeSet);
destructor Destroy; override;
class function TypeName: String; override;
function AsNodeSet: TNodeSet; override;
function AsText: DOMString; override;
function AsBoolean: Boolean; override;
function AsNumber: Extended; override;
property Value: TNodeSet read FValue;
end;
TXPathBooleanVariable = class(TXPathVariable)
private
FValue: Boolean;
public
constructor Create(AValue: Boolean);
class function TypeName: String; override;
function AsBoolean: Boolean; override;
function AsNumber: Extended; override;
function AsText: DOMString; override;
property Value: Boolean read FValue;
end;
TXPathNumberVariable = class(TXPathVariable)
private
FValue: Extended;
public
constructor Create(AValue: Extended);
class function TypeName: String; override;
function AsBoolean: Boolean; override;
function AsNumber: Extended; override;
function AsText: DOMString; override;
property Value: Extended read FValue;
end;
TXPathStringVariable = class(TXPathVariable)
private
FValue: DOMString;
public
constructor Create(const AValue: DOMString);
class function TypeName: String; override;
function AsBoolean: Boolean; override;
function AsNumber: Extended; override;
function AsText: DOMString; override;
property Value: DOMString read FValue;
end;
TXPathNSResolver = class
protected
FNode: TDOMNode;
public
constructor Create(aNode: TDOMNode);
function LookupNamespaceURI(const aPrefix: DOMString): DOMString; virtual;
end;
{ XPath lexical scanner }
TXPathScanner = class
private
FExpressionString, FCurData: DOMPChar;
FCurToken: TXPathToken;
FCurTokenString: DOMString;
FTokenStart: DOMPChar;
FTokenLength: Integer;
FPrefixLength: Integer;
FTokenId: TXPathKeyword;
FResolver: TXPathNSResolver;
procedure Error(const Msg: String);
procedure ParsePredicates(var Dest: TXPathNodeArray);
function ParseStep: TStep; // [4]
function ParseNodeTest(axis: TAxis): TStep; // [7]
function ParsePrimaryExpr: TXPathExprNode; // [15]
function ParseFunctionCall: TXPathExprNode; // [16]
function ParseUnionExpr: TXPathExprNode; // [18]
function ParsePathExpr: TXPathExprNode; // [19]
function ParseFilterExpr: TXPathExprNode; // [20]
function ParseOrExpr: TXPathExprNode; // [21]
function ParseAndExpr: TXPathExprNode; // [22]
function ParseEqualityExpr: TXPathExprNode; // [23]
function ParseRelationalExpr: TXPathExprNode; // [24]
function ParseAdditiveExpr: TXPathExprNode; // [25]
function ParseMultiplicativeExpr: TXPathExprNode; // [26]
function ParseUnaryExpr: TXPathExprNode; // [27]
function GetToken: TXPathToken;
function ScanQName: Boolean;
public
constructor Create(const AExpressionString: DOMString);
function NextToken: TXPathToken;
function PeekToken: TXPathToken;
function SkipToken(tok: TXPathToken): Boolean;
property CurToken: TXPathToken read FCurToken;
property CurTokenString: DOMString read FCurTokenString;
end;
{ XPath context }
TXPathContext = class
public
ContextNode: TDOMNode;
ContextPosition: Integer;
ContextSize: Integer;
constructor Create(AContextNode: TDOMNode;
AContextPosition, AContextSize: Integer);
end;
{ XPath environments (not defined in XPath standard: an environment contains
the variables and functions, which are part of the context in the official
standard). }
TXPathVarList = TFPList;
TXPathFunction = function(Context: TXPathContext; Args: TXPathVarList):
TXPathVariable of object;
TXPathEnvironment = class
private
FFunctions: TFPList;
FVariables: TFPList;
function GetFunctionCount: Integer;
function GetVariableCount: Integer;
function GetFunction(Index: Integer): TXPathFunction;
function GetFunction(const AName: String): TXPathFunction;
function GetVariable(Index: Integer): TXPathVariable;
function GetVariable(const AName: String): TXPathVariable;
protected
// XPath Core Function Library:
function xpLast(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpPosition(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpCount({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpId(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpLocalName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpNamespaceURI(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpString(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpConcat({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpStartsWith({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpContains({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpSubstringBefore({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpSubstringAfter({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpSubstring({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpStringLength(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpNormalizeSpace(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpTranslate({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpBoolean({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpNot({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpTrue({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpFalse({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpLang(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpNumber(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpSum({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpFloor({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpCeiling({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
function xpRound({%H-}Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
public
constructor Create;
destructor Destroy; override;
function GetFunctionIndex(const AName: String): Integer;
function GetVariableIndex(const AName: String): Integer;
procedure AddFunction(const AName: String; AFunction: TXPathFunction);
procedure AddVariable(const AName: String; AVariable: TXPathVariable);
procedure RemoveFunction(Index: Integer);
procedure RemoveFunction(const AName: String);
procedure RemoveVariable(Index: Integer);
procedure RemoveVariable(const AName: String);
property FunctionCount: Integer read GetFunctionCount;
property VariableCount: Integer read GetVariableCount;
property Functions[Index: Integer]: TXPathFunction read GetFunction;
property FunctionsByName[const AName: String]: TXPathFunction
read GetFunction;
property Variables[Index: Integer]: TXPathVariable read GetVariable;
property VariablesByName[const AName: String]: TXPathVariable read GetVariable;
end;
{ XPath expressions }
TXPathExpression = class
private
FRootNode: TXPathExprNode;
public
{ CompleteExpresion specifies wether the parser should check for gargabe
after the recognised part. True => Throw exception if there is garbage }
constructor Create(AScanner: TXPathScanner; CompleteExpression: Boolean;
AResolver: TXPathNSResolver = nil);
destructor Destroy; override;
function Evaluate(AContextNode: TDOMNode): TXPathVariable;
function Evaluate(AContextNode: TDOMNode;
AEnvironment: TXPathEnvironment): TXPathVariable;
end;
function EvaluateXPathExpression(const AExpressionString: DOMString;
AContextNode: TDOMNode; AResolver: TXPathNSResolver = nil): TXPathVariable;
// ===================================================================
// ===================================================================
implementation
const
XPathKeywords: array [TXPathKeyword] of DOMPChar = (
'',
#08'ancestor',
#16'ancestor-or-self',
#09'attribute',
#05'child',
#10'descendant',
#18'descendant-or-self',
#09'following',
#17'following-sibling',
#09'namespace',
#06'parent',
#09'preceding',
#17'preceding-sibling',
#04'self',
#07'comment',
#04'text',
#22'processing-instruction',
#04'node',
#03'and',
#02'or',
#03'div',
#03'mod',
#04'last',
#08'position',
#05'count',
#02'id',
#10'local-name',
#13'namespace-uri',
#04'name',
#06'string',
#06'concat',
#11'starts-with',
#08'contains',
#16'substring-before',
#15'substring-after',
#09'substring',
#13'string-length',
#15'normalize-space',
#09'translate',
#07'boolean',
#03'not',
#04'true',
#05'false',
#04'lang',
#06'number',
#03'sum',
#05'floor',
#07'ceiling',
#05'round'
);
{ The following code is not very maintainable because it was hand-ported from
C code generated by gperf. Unless a tool like gperf is ported or modified to
generate Pascal, modifying it will be painful.
The good side is that one shouldn't ever need to modify it. }
MaxHash = 55;
KeywordIndex: array[0..MaxHash-1] of TXPathKeyword = (
xkNone, xkNone,
xkId,
xkNone, xkNone, xkNone,
xkString,
xkSum,
xkParent,
xkSubstring,
xkNone,
xkComment,
xkName,
xkStringLength,
xkNumber,
xkSubstringAfter,
xkSubstringBefore,
xkNamespace,
xkFloor,
xkNormalizeSpace,
xkSelf,
xkNamespaceUri,
xkPreceding,
xkOr,
xkPosition,
xkText,
xkProcessingInstruction,
xkConcat,
xkLast,
xkContains,
xkPrecedingSibling,
xkAncestor,
xkFalse,
xkLocalName,
xkCount,
xkLang,
xkFollowing,
xkDescendant,
xkNode,
xkAncestorOrSelf,
xkBoolean,
xkNot,
xkStartsWith,
xkAnd,
xkFollowingSibling,
xkDescendantOrSelf,
xkChild,
xkTrue,
xkCeiling,
xkMod,
xkDiv,
xkRound,
xkNone,
xkAttribute,
xkTranslate
);
AssoValues: array[97..122] of Byte = (
10, 31, 0, 13, 30, 11, 55, 55, 0, 41,
55, 10, 16, 4, 21, 2, 55, 17, 0, 14,
34, 29, 34, 55, 7, 55
);
function LookupXPathKeyword(p: DOMPChar; Len: Integer): TXPathKeyword;
var
hash: Integer;
p1: DOMPChar;
begin
result := xkNone;
hash := Len;
if Len >= 1 then
begin
if (p^ >= 'a') and (p^ <= 'y') then
Inc(hash, AssoValues[ord(p^)])
else
Exit;
if Len > 2 then
if (p[2] >= 'a') and (p[2] <= 'y') then
Inc(hash, AssoValues[ord(p[2])+1])
else
Exit;
end;
if (hash >= 0) and (hash <= MaxHash) then
begin
p1 := XPathKeywords[KeywordIndex[hash]];
if (ord(p1^) = Len) and
CompareMem(p, p1+1, Len*sizeof(DOMChar)) then
Result := KeywordIndex[hash];
end;
end;
const
AxisNameKeywords = [xkAncestor..xkSelf];
AxisNameMap: array[xkAncestor..xkSelf] of TAxis = (
axisAncestor, axisAncestorOrSelf, axisAttribute, axisChild,
axisDescendant, axisDescendantOrSelf, axisFollowing,
axisFollowingSibling, axisNamespace, axisParent, axisPreceding,
axisPrecedingSibling, axisSelf
);
NodeTestKeywords = [xkComment..xkNode];
NodeTestMap: array[xkComment..xkNode] of TNodeTestType = (
ntCommentNode, ntTextNode, ntPINode, ntAnyNode
);
{ Helper functions }
function NodeToText(Node: TDOMNode): DOMString;
var
Child: TDOMNode;
begin
case Node.NodeType of
DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE{, ELEMENT_NODE}:
begin
SetLength(Result, 0);
Child := Node.FirstChild;
while Assigned(Child) do
begin
if Result <> '' then
Result := Result + LineEnding;
Result := Result + NodeToText(Child);
Child := Child.NextSibling;
end;
end;
ELEMENT_NODE:
Result := Node.TextContent;
ATTRIBUTE_NODE, PROCESSING_INSTRUCTION_NODE, COMMENT_NODE, TEXT_NODE,
CDATA_SECTION_NODE, ENTITY_REFERENCE_NODE:
Result := Node.NodeValue;
else Result := '';
end;
// !!!: What to do with 'namespace nodes'?
end;
function StrToNumber(const s: DOMString): Extended;
var
Code: Integer;
begin
Val(s, Result, Code);
{$push}
{$r-,q-}
if Code <> 0 then
Result := NaN;
{$pop}
end;
function GetNodeLanguage(aNode: TDOMNode): DOMString;
var
Attr: TDomAttr;
begin
Result := '';
if aNode = nil then
Exit;
case aNode.NodeType of
ELEMENT_NODE: begin
Attr := TDomElement(aNode).GetAttributeNode('xml:lang');
if Assigned(Attr) then
Result := Attr.Value
else
Result := GetNodeLanguage(aNode.ParentNode);
end;
TEXT_NODE, CDATA_SECTION_NODE, ENTITY_REFERENCE_NODE,
PROCESSING_INSTRUCTION_NODE, COMMENT_NODE:
Result := GetNodeLanguage(aNode.ParentNode);
ATTRIBUTE_NODE:
Result := GetNodeLanguage(TDOMAttr(aNode).OwnerElement);
end;
end;
procedure AddNodes(var Dst: TXPathNodeArray; const Src: array of TXPathExprNode;
var Count: Integer);
var
L: Integer;
begin
if Count > 0 then
begin
L := Length(Dst);
SetLength(Dst, L + Count);
Move(Src[0], Dst[L], Count*sizeof(TObject));
Count := 0;
end;
end;
{ XPath parse tree classes }
function TXPathExprNode.EvalPredicate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): Boolean;
var
resvar: TXPathVariable;
begin
resvar := Evaluate(AContext, AEnvironment);
try
if resvar.InheritsFrom(TXPathNumberVariable) then
Result := resvar.AsNumber = AContext.ContextPosition // TODO: trunc/round?
else
Result := resvar.AsBoolean;
finally
resvar.Release;
end;
end;
constructor TXPathConstantNode.Create(AValue: TXPathVariable);
begin
inherited Create;
FValue := AValue;
end;
destructor TXPathConstantNode.Destroy;
begin
FValue.Release;
inherited Destroy;
end;
function TXPathConstantNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
begin
Result := FValue;
Inc(Result.FRefCount);
end;
constructor TXPathVariableNode.Create(const AName: DOMString);
begin
inherited Create;
FName := AName;
end;
function TXPathVariableNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
begin
Result := AEnvironment.VariablesByName[FName];
if not Assigned(Result) then
EvaluationError(lrsEvalUnknownVariable, [FName]);
end;
constructor TXPathFunctionNode.Create(const AName: DOMString; const Args: TXPathNodeArray);
begin
inherited Create;
FName := AName;
FArgs := Args;
end;
destructor TXPathFunctionNode.Destroy;
var
i: Integer;
begin
for i := Low(FArgs) to High(FArgs) do
FArgs[i].Free;
inherited Destroy;
end;
function TXPathFunctionNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
var
Fn: TXPathFunction;
Args: TXPathVarList;
i: Integer;
begin
Fn := AEnvironment.FunctionsByName[FName];
if not Assigned(Fn) then
EvaluationError(lrsEvalUnknownFunction, [FName]);
Args := TXPathVarList.Create;
try
for i := Low(FArgs) to High(FArgs) do
Args.Add(FArgs[i].Evaluate(AContext, AEnvironment));
Result := Fn(AContext, Args);
for i := Low(FArgs) to High(FArgs) do
TXPathVariable(Args[i]).Release;
finally
Args.Free;
end;
end;
constructor TXPathNegationNode.Create(AOperand: TXPathExprNode);
begin
inherited Create;
FOperand := AOperand;
end;
destructor TXPathNegationNode.Destroy;
begin
FOperand.Free;
inherited Destroy;
end;
function TXPathNegationNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
var
OpResult: TXPathVariable;
begin
OpResult := FOperand.Evaluate(AContext, AEnvironment);
try
Result := TXPathNumberVariable.Create(-OpResult.AsNumber);
finally
OpResult.Release;
end;
end;
destructor TXPathBinaryNode.Destroy;
begin
FOperand1.Free;
FOperand2.Free;
inherited Destroy;
end;
constructor TXPathMathOpNode.Create(AOperator: TXPathMathOp;
AOperand1, AOperand2: TXPathExprNode);
begin
inherited Create;
FOperator := AOperator;
FOperand1 := AOperand1;
FOperand2 := AOperand2;
end;
function TXPathMathOpNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
var
Op1Result, Op2Result: TXPathVariable;
Op1, Op2, NumberResult: Extended;
begin
Op1Result := FOperand1.Evaluate(AContext, AEnvironment);
try
Op2Result := FOperand2.Evaluate(AContext, AEnvironment);
try
Op1 := Op1Result.AsNumber;
Op2 := Op2Result.AsNumber;
case FOperator of
opAdd:
NumberResult := Op1 + Op2;
opSubtract:
NumberResult := Op1 - Op2;
opMultiply:
NumberResult := Op1 * Op2;
opDivide:
NumberResult := Op1 / Op2;
opMod: if IsNan(Op1) or IsNan(Op2) then
{$push}
{$r-,q-}
NumberResult := NaN
{$pop}
else
NumberResult := Trunc(Op1) mod Trunc(Op2);
end;
finally
Op2Result.Release;
end;
finally
Op1Result.Release;
end;
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);
begin
inherited Create;
FOperator := AOperator;
FOperand1 := AOperand1;
FOperand2 := AOperand2;
end;
function TXPathCompareNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
var
Op1, Op2: TXPathVariable;
BoolResult: Boolean;
nsnum: Integer;
begin
Op1 := FOperand1.Evaluate(AContext, AEnvironment);
try
Op2 := FOperand2.Evaluate(AContext, AEnvironment);
try
nsnum := ord(Op1 is TXPathNodeSetVariable) or
(ord(Op2 is TXPathNodeSetVariable) shl 1);
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: // Op1 is nodeset
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);
2: // Op2 is nodeset
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]);
else // both ops are nodesets
BoolResult := CmpNodesets(Op1.AsNodeSet, Op2.AsNodeSet, FOperator);
end;
finally
Op2.Release;
end;
finally
Op1.Release;
end;
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
inherited Create;
FOperand1 := AOperand1;
FOperand2 := AOperand2;
end;
function TXPathUnionNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
var
Op1Result, Op2Result: TXPathVariable;
NodeSet, NodeSet2: TNodeSet;
CurNode: Pointer;
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);
try
NodeSet := Op1Result.AsNodeSet;
NodeSet2 := Op2Result.AsNodeSet;
for i := 0 to NodeSet2.Count - 1 do
begin
CurNode := NodeSet2[i];
if NodeSet.IndexOf(CurNode) < 0 then
NodeSet.Add(CurNode);
end;
finally
Op2Result.Release;
end;
finally
Result := Op1Result;
end;
end;
constructor TXPathFilterNode.Create(AExpr: TXPathExprNode);
begin
inherited Create;
FLeft := AExpr;
end;
destructor TXPathFilterNode.Destroy;
var
i: Integer;
begin
FLeft.Free;
for i := 0 to High(FPredicates) do
FPredicates[i].Free;
inherited Destroy;
end;
function TXPathFilterNode.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
var
NodeSet: TNodeSet;
begin
Result := FLeft.Evaluate(AContext, AEnvironment);
NodeSet := Result.AsNodeSet;
ApplyPredicates(NodeSet, AEnvironment);
end;
constructor TStep.Create(aAxis: TAxis; aTest: TNodeTestType);
begin
Axis := aAxis;
NodeTestType := aTest;
end;
procedure TStep.SelectNodes(ANode: TDOMNode; out ResultNodes: TNodeSet);
var
Node, Node2: TDOMNode;
Attr: TDOMNamedNodeMap;
i: Integer;
procedure DoNodeTest(Node: TDOMNode);
begin
case NodeTestType of
ntAnyPrincipal:
// !!!: Probably this isn't ready for namespace support yet
if (Axis <> axisAttribute) and
(Node.NodeType <> ELEMENT_NODE) then
exit;
ntName:
if NSTestString <> '' then
begin
if Node.namespaceURI <> NSTestString then
exit;
if (NodeTestString <> '') and (Node.localName <> NodeTestString) then
exit;
end
else if Node.NodeName <> NodeTestString then
exit;
ntTextNode:
if not Node.InheritsFrom(TDOMText) then
exit;
ntCommentNode:
if Node.NodeType <> COMMENT_NODE then
exit;
ntPINode:
if (Node.NodeType <> PROCESSING_INSTRUCTION_NODE) or
((NodeTestString <> '') and (Node.nodeName <> NodeTestString)) then
exit;
end;
if ResultNodes.IndexOf(Node) < 0 then
ResultNodes.Add(Node);
end;
procedure AddDescendants(CurNode: TDOMNode);
var
Child: TDOMNode;
begin
Child := CurNode.FirstChild;
while Assigned(Child) do
begin
DoNodeTest(Child);
AddDescendants(Child);
Child := Child.NextSibling;
end;
end;
procedure AddDescendantsReverse(CurNode: TDOMNode);
var
Child: TDOMNode;
begin
Child := CurNode.LastChild;
while Assigned(Child) do
begin
AddDescendantsReverse(Child);
DoNodeTest(Child);
Child := Child.PreviousSibling;
end;
end;
begin
ResultNodes := TNodeSet.Create;
case Axis of
axisAncestor:
begin
// TODO: same check needed for XPATH_NAMESPACE_NODE
if ANode.nodeType = ATTRIBUTE_NODE then
Node := TDOMAttr(ANode).ownerElement
else
Node := ANode.ParentNode;
while Assigned(Node) do
begin
DoNodeTest(Node);
Node := Node.ParentNode;
end;
end;
axisAncestorOrSelf:
begin
DoNodeTest(ANode);
// TODO: same check needed for XPATH_NAMESPACE_NODE
if ANode.nodeType = ATTRIBUTE_NODE then
Node := TDOMAttr(ANode).ownerElement
else
Node := ANode.ParentNode;
while Assigned(Node) do
begin
DoNodeTest(Node);
Node := Node.ParentNode;
end;
end;
axisAttribute:
begin
Attr := ANode.Attributes;
if Assigned(Attr) then
for i := 0 to Attr.Length - 1 do
DoNodeTest(Attr[i]);
end;
axisChild:
begin
Node := ANode.FirstChild;
while Assigned(Node) do
begin
DoNodeTest(Node);
Node := Node.NextSibling;
end;
end;
axisDescendant:
AddDescendants(ANode);
axisDescendantOrSelf:
begin
DoNodeTest(ANode);
AddDescendants(ANode);
end;
axisFollowing:
begin
Node := ANode;
repeat
Node2 := Node.NextSibling;
while Assigned(Node2) do
begin
DoNodeTest(Node2);
AddDescendants(Node2);
Node2 := Node2.NextSibling;
end;
Node := Node.ParentNode;
until not Assigned(Node);
end;
axisFollowingSibling:
begin
Node := ANode.NextSibling;
while Assigned(Node) do
begin
DoNodeTest(Node);
Node := Node.NextSibling;
end;
end;
{axisNamespace: !!!: Not supported yet}
axisParent:
if ANode.NodeType=ATTRIBUTE_NODE then
begin
if Assigned(TDOMAttr(ANode).OwnerElement) then
DoNodeTest(TDOMAttr(ANode).OwnerElement);
end
else if Assigned(ANode.ParentNode) then
DoNodeTest(ANode.ParentNode);
axisPreceding:
begin
Node := ANode;
repeat
Node2 := Node.PreviousSibling;
while Assigned(Node2) do
begin
AddDescendantsReverse(Node2);
DoNodeTest(Node2);
Node2 := Node2.PreviousSibling;
end;
Node := Node.ParentNode;
until not Assigned(Node);
end;
axisPrecedingSibling:
begin
Node := ANode.PreviousSibling;
while Assigned(Node) do
begin
DoNodeTest(Node);
Node := Node.PreviousSibling;
end;
end;
axisSelf:
DoNodeTest(ANode);
axisRoot:
if ANode.nodeType = DOCUMENT_NODE then
ResultNodes.Add(ANode)
else
ResultNodes.Add(ANode.ownerDocument);
end;
end;
{ Filter the nodes of this step using the predicates: The current
node set is filtered, nodes not passing the filter are replaced
by nil. After one filter has been applied, Nodes is packed, and
the next filter will be processed. }
procedure TXPathFilterNode.ApplyPredicates(Nodes: TNodeSet; AEnvironment: TXPathEnvironment);
var
i, j: Integer;
NewContext: TXPathContext;
begin
for i := 0 to High(FPredicates) do
begin
NewContext := TXPathContext.Create(nil, 0, Nodes.Count);
try
for j := 0 to Nodes.Count - 1 do
begin
NewContext.ContextPosition := j+1;
NewContext.ContextNode := TDOMNode(Nodes[j]);
if not FPredicates[i].EvalPredicate(NewContext, AEnvironment) then
Nodes[j] := nil;
end;
Nodes.Pack;
finally
NewContext.Free;
end;
end;
end;
function TStep.Evaluate(AContext: TXPathContext;
AEnvironment: TXPathEnvironment): TXPathVariable;
var
ResultNodeSet: TNodeSet;
LeftResult: TXPathVariable;
i: Integer;
procedure EvaluateStep(AContextNode: TDOMNode);
var
StepNodes: TFPList;
Node: TDOMNode;
i: Integer;
begin
SelectNodes(AContextNode, StepNodes);
try
ApplyPredicates(StepNodes, AEnvironment);
if Axis in [axisAncestor, axisAncestorOrSelf,
axisPreceding, axisPrecedingSibling] then
for i := StepNodes.Count - 1 downto 0 do
begin
Node := TDOMNode(StepNodes[i]);
if ResultNodeSet.IndexOf(Node) < 0 then
ResultNodeSet.Add(Node);
end
else for i := 0 to StepNodes.Count - 1 do
begin
Node := TDOMNode(StepNodes[i]);
if ResultNodeSet.IndexOf(Node) < 0 then
ResultNodeSet.Add(Node);
end;
finally
StepNodes.Free;
end;
end;
begin
ResultNodeSet := TNodeSet.Create;
try
if Assigned(FLeft) then
begin
LeftResult := FLeft.Evaluate(AContext, AEnvironment);
try
with LeftResult.AsNodeSet do
for i := 0 to Count-1 do
EvaluateStep(TDOMNode(Items[i]));
finally
LeftResult.Release;
end;
end
else
EvaluateStep(AContext.ContextNode);
except
ResultNodeSet.Free;
raise;
end;
Result := TXPathNodeSetVariable.Create(ResultNodeSet);
end;
{ Exceptions }
procedure EvaluationError(const Msg: String);
begin
raise EXPathEvaluationError.Create(Msg) at get_caller_addr(get_frame);
end;
procedure EvaluationError(const Msg: String; const Args: array of const);
begin
raise EXPathEvaluationError.CreateFmt(Msg, Args)
at get_caller_addr(get_frame);
end;
{ TXPathVariable and derived classes}
procedure TXPathVariable.Release;
begin
if FRefCount <= 0 then
Free
else
Dec(FRefCount);
end;
function TXPathVariable.AsNodeSet: TNodeSet;
begin
Error(lrsVarNoConversion, [TypeName, TXPathNodeSetVariable.TypeName]);
Result := nil;
end;
procedure TXPathVariable.Error(const Msg: String; const Args: array of const);
begin
raise Exception.CreateFmt(Msg, Args) at get_caller_addr(get_frame);
end;
constructor TXPathNodeSetVariable.Create(AValue: TNodeSet);
begin
inherited Create;
FValue := AValue;
end;
destructor TXPathNodeSetVariable.Destroy;
begin
FValue.Free;
inherited Destroy;
end;
class function TXPathNodeSetVariable.TypeName: String;
begin
Result := lrsNodeSet;
end;
function TXPathNodeSetVariable.AsNodeSet: TNodeSet;
begin
Result := FValue;
end;
function TXPathNodeSetVariable.AsText: DOMString;
begin
if FValue.Count = 0 then
Result := ''
else
Result := NodeToText(TDOMNode(FValue.First));
end;
function TXPathNodeSetVariable.AsBoolean: Boolean;
begin
Result := FValue.Count <> 0;
end;
function TXPathNodeSetVariable.AsNumber: Extended;
begin
Result := StrToNumber(AsText);
end;
constructor TXPathBooleanVariable.Create(AValue: Boolean);
begin
inherited Create;
FValue := AValue;
end;
class function TXPathBooleanVariable.TypeName: String;
begin
Result := lrsBoolean;
end;
function TXPathBooleanVariable.AsBoolean: Boolean;
begin
Result := FValue;
end;
function TXPathBooleanVariable.AsNumber: Extended;
begin
if FValue then
Result := 1
else
Result := 0;
end;
function TXPathBooleanVariable.AsText: DOMString;
begin
if FValue then
Result := 'true' // Do not localize!
else
Result := 'false'; // Do not localize!
end;
constructor TXPathNumberVariable.Create(AValue: Extended);
begin
inherited Create;
FValue := AValue;
end;
class function TXPathNumberVariable.TypeName: String;
begin
Result := lrsNumber;
end;
function TXPathNumberVariable.AsBoolean: Boolean;
begin
Result := not (IsNan(FValue) or IsZero(FValue));
end;
function TXPathNumberVariable.AsNumber: Extended;
begin
Result := FValue;
end;
function TXPathNumberVariable.AsText: DOMString;
var
frec: TFloatRec;
i, nd, reqlen: Integer;
P: DOMPChar;
begin
FloatToDecimal(frec, FValue, fvExtended, 17, 9999);
if frec.Exponent = -32768 then
begin
Result := 'NaN'; // do not localize
Exit;
end
else if frec.Exponent = 32767 then
begin
if frec.Negative then
Result := '-Infinity' // do not localize
else
Result := 'Infinity'; // do not localize
Exit;
end
else if frec.Digits[0] = #0 then
begin
Result := '0';
Exit;
end
else
begin
nd := StrLen(@frec.Digits[0]);
reqlen := nd + ord(frec.Negative); // maybe minus sign
if frec.Exponent > nd then
Inc(reqlen, frec.Exponent - nd) // add this much zeroes at the right
else if frec.Exponent < nd then
begin
Inc(reqlen); // decimal point
if frec.Exponent <= 0 then
Inc(reqlen, 1 - frec.Exponent); // zeroes at the left + one more for the int part
end;
SetLength(Result, reqlen);
P := DOMPChar(Result);
if frec.Negative then
begin
P^ := '-';
Inc(P);
end;
if frec.Exponent <= 0 then // value less than 1, put zeroes at left
begin
for i := 0 to 1-frec.Exponent do
P[i] := '0';
P[1] := '.';
for i := 0 to nd-1 do
P[i+2-frec.Exponent] := DOMChar(ord(frec.Digits[i]));
end
else if frec.Exponent > nd then // large integer, put zeroes at right
begin
for i := 0 to nd-1 do
P[i] := DOMChar(ord(frec.Digits[i]));
for i := nd to reqlen-1-ord(frec.Negative) do
P[i] := '0';
end
else // 0 < exponent <= digits, insert decimal point into middle
begin
for i := 0 to frec.Exponent-1 do
P[i] := DOMChar(ord(frec.Digits[i]));
if frec.Exponent < nd then
begin
P[frec.Exponent] := '.';
for i := frec.Exponent to nd-1 do
P[i+1] := DOMChar(ord(frec.Digits[i]));
end;
end;
end;
end;
constructor TXPathStringVariable.Create(const AValue: DOMString);
begin
inherited Create;
FValue := AValue;
end;
class function TXPathStringVariable.TypeName: String;
begin
Result := lrsString;
end;
function TXPathStringVariable.AsBoolean: Boolean;
begin
Result := Length(FValue) > 0;
end;
function TXPathStringVariable.AsNumber: Extended;
begin
Result := StrToNumber(FValue);
end;
function TXPathStringVariable.AsText: DOMString;
begin
Result := FValue;
end;
{ XPath lexical scanner }
constructor TXPathScanner.Create(const AExpressionString: DOMString);
begin
inherited Create;
FExpressionString := DOMPChar(AExpressionString);
FCurData := FExpressionString;
NextToken;
end;
function TXPathScanner.PeekToken: TXPathToken;
var
save: DOMPChar;
begin
save := FCurData;
Result := GetToken;
FCurData := save;
end;
function TXPathScanner.NextToken: TXPathToken;
begin
Result := GetToken;
FCurToken := Result;
if Result in [tkIdentifier, tkNSNameTest, tkNumber, tkString, tkVariable] then
SetString(FCurTokenString, FTokenStart, FTokenLength);
if Result = tkIdentifier then
FTokenId := LookupXPathKeyword(FTokenStart, FTokenLength)
else
FTokenId := xkNone;
end;
function TXPathScanner.SkipToken(tok: TXPathToken): Boolean; { inline? }
begin
Result := (FCurToken = tok);
if Result then
NextToken;
end;
// TODO: no surrogate pairs/XML 1.1 support yet
function TXPathScanner.ScanQName: Boolean;
var
p: DOMPChar;
begin
FPrefixLength := 0;
p := FCurData;
repeat
if (Byte(p^) in namingBitmap[NamePages[hi(Word(p^))]]) then
Inc(p)
else
begin
// either the first char of name is bad (it may be a colon),
// or a colon is not followed by a valid NameStartChar
Result := False;
Break;
end;
while Byte(p^) in NamingBitmap[NamePages[$100+hi(Word(p^))]] do
Inc(p);
Result := True;
if (p^ <> ':') or (p[1] = ':') or (FPrefixLength > 0) then
Break;
// first colon, and not followed by another one -> remember its position
FPrefixLength := p-FTokenStart;
Inc(p);
until False;
FCurData := p;
FTokenLength := p-FTokenStart;
end;
function TXPathScanner.GetToken: TXPathToken;
procedure GetNumber(HasDot: Boolean);
begin
FTokenLength := 1;
while ((FCurData[1] >= '0') and (FCurData[1] <= '9')) or ((FCurData[1] = '.') and not HasDot) do
begin
Inc(FCurData);
Inc(FTokenLength);
if FCurData[0] = '.' then
HasDot := True;
end;
Result := tkNumber;
end;
var
Delim: DOMChar;
begin
// Skip whitespace
while (FCurData[0] < #255) and (char(ord(FCurData[0])) in [#9, #10, #13, ' ']) do
Inc(FCurData);
FTokenStart := FCurData;
FTokenLength := 0;
Result := tkInvalid;
case FCurData[0] of
#0:
Result := tkEndOfStream;
'!':
if FCurData[1] = '=' then
begin
Inc(FCurData);
Result := tkNotEqual;
end;
'"', '''':
begin
Delim := FCurData^;
Inc(FCurData);
FTokenStart := FCurData;
while FCurData[0] <> Delim do
begin
if FCurData[0] = #0 then
Error(lrsScannerUnclosedString);
Inc(FCurData);
end;
FTokenLength := FCurData-FTokenStart;
Result := tkString;
end;
'$':
begin
Inc(FCurData);
Inc(FTokenStart);
if ScanQName then
Result := tkVariable
else
Error(lrsScannerExpectedVarName);
Exit;
end;
'(':
Result := tkLeftBracket;
')':
Result := tkRightBracket;
'*':
Result := tkAsterisk;
'+':
Result := tkPlus;
',':
Result := tkComma;
'-':
Result := tkMinus;
'.':
if FCurData[1] = '.' then
begin
Inc(FCurData);
Result := tkDotDot;
end else if (FCurData[1] >= '0') and (FCurData[1] <= '9') then
GetNumber(True)
else
Result := tkDot;
'/':
if FCurData[1] = '/' then
begin
Inc(FCurData);
Result := tkSlashSlash;
end else
Result := tkSlash;
'0'..'9':
GetNumber(False);
':':
if FCurData[1] = ':' then
begin
Inc(FCurData);
Result := tkColonColon;
end;
'<':
if FCurData[1] = '=' then
begin
Inc(FCurData);
Result := tkLessEqual;
end else
Result := tkLess;
'=':
Result := tkEqual;
'>':
if FCurData[1] = '=' then
begin
Inc(FCurData);
Result := tkGreaterEqual;
end else
Result := tkGreater;
'@':
Result := tkAt;
'[':
Result := tkLeftSquareBracket;
']':
Result := tkRightSquareBracket;
'|':
Result := tkPipe;
else
if ScanQName then
begin
Result := tkIdentifier;
Exit;
end
else if FPrefixLength > 0 then
begin
if FCurData^ = '*' then
begin
Inc(FCurData);
Dec(FTokenLength); // exclude ':'
Result := tkNSNameTest;
Exit;
end
else
Error(lrsScannerMalformedQName);
end;
end;
if Result = tkInvalid then
Error(lrsScannerInvalidChar);
// We have processed at least one character now; eat it:
if Result > tkEndOfStream then
Inc(FCurData);
end;
procedure TXPathScanner.Error(const Msg: String);
begin
raise Exception.Create(Msg) at get_caller_addr(get_frame);
end;
procedure TXPathScanner.ParsePredicates(var Dest: TXPathNodeArray);
var
Buffer: array[0..15] of TXPathExprNode;
I: Integer;
begin
I := 0;
// accumulate nodes in local buffer, then add all at once
// this reduces amount of ReallocMem's
while SkipToken(tkLeftSquareBracket) do
begin
Buffer[I] := ParseOrExpr;
Inc(I);
if I > High(Buffer) then
AddNodes(Dest, Buffer, I); // will reset I to zero
if not SkipToken(tkRightSquareBracket) then
Error(lrsParserExpectedRightSquareBracket);
end;
AddNodes(Dest, Buffer, I);
end;
function TXPathScanner.ParseStep: TStep; // [4]
var
Axis: TAxis;
begin
if CurToken = tkDot then // [12] Abbreviated step, first case
begin
NextToken;
Result := TStep.Create(axisSelf, ntAnyNode);
end
else if CurToken = tkDotDot then // [12] Abbreviated step, second case
begin
NextToken;
Result := TStep.Create(axisParent, ntAnyNode);
end
else // Parse [5] AxisSpecifier
begin
if CurToken = tkAt then // [13] AbbreviatedAxisSpecifier
begin
Axis := axisAttribute;
NextToken;
end
else if (CurToken = tkIdentifier) and (PeekToken = tkColonColon) then // [5] AxisName '::'
begin
if FTokenId in AxisNameKeywords then
Axis := AxisNameMap[FTokenId]
else
Error(lrsParserBadAxisName);
NextToken; // skip identifier and the '::'
NextToken;
end
else
Axis := axisChild;
Result := ParseNodeTest(Axis);
ParsePredicates(Result.FPredicates);
end;
end;
function TXPathScanner.ParseNodeTest(Axis: TAxis): TStep; // [7]
var
nodeType: TNodeTestType;
nodeName: DOMString;
nsURI: DOMString;
begin
nodeName := '';
nsURI := '';
if CurToken = tkAsterisk then // [37] NameTest, first case
begin
nodeType := ntAnyPrincipal;
NextToken;
end
else if CurToken = tkNSNameTest then // [37] NameTest, second case
begin
if Assigned(FResolver) then
nsURI := FResolver.lookupNamespaceURI(CurTokenString);
if nsURI = '' then
// !! localization disrupted by DOM exception specifics
raise EDOMNamespace.Create('TXPathScanner.ParseStep');
NextToken;
nodeType := ntName;
end
else if CurToken = tkIdentifier then
begin
// Check for case [38] NodeType
if PeekToken = tkLeftBracket then
begin
if FTokenId in NodeTestKeywords then
begin
nodeType := NodeTestMap[FTokenId];
if FTokenId = xkProcessingInstruction then
begin
NextToken;
if NextToken = tkString then
begin
nodeName := CurTokenString;
NextToken;
end;
end
else
begin
NextToken;
NextToken;
end;
if CurToken <> tkRightBracket then
Error(lrsParserExpectedRightBracket);
NextToken;
end
else
Error(lrsParserBadNodeType);
end
else // [37] NameTest, third case
begin
nodeType := ntName;
if FPrefixLength > 0 then
begin
if Assigned(FResolver) then
nsURI := FResolver.lookupNamespaceURI(Copy(CurTokenString, 1, FPrefixLength));
if nsURI = '' then
raise EDOMNamespace.Create('TXPathScanner.ParseStep');
nodeName := Copy(CurTokenString, FPrefixLength+2, MaxInt);
end
else
nodeName := CurTokenString;
NextToken;
end;
end
else
Error(lrsParserInvalidNodeTest);
Result := TStep.Create(Axis, nodeType);
Result.NodeTestString := nodeName;
Result.NSTestString := nsURI;
end;
function TXPathScanner.ParsePrimaryExpr: TXPathExprNode; // [15]
begin
case CurToken of
tkVariable: // [36] Variable reference
Result := TXPathVariableNode.Create(CurTokenString);
tkLeftBracket:
begin
NextToken;
Result := ParseOrExpr;
if CurToken <> tkRightBracket then
Error(lrsParserExpectedRightBracket);
end;
tkString: // [29] Literal
Result := TXPathConstantNode.Create(
TXPathStringVariable.Create(CurTokenString));
tkNumber: // [30] Number
Result := TXPathConstantNode.Create(
TXPathNumberVariable.Create(StrToNumber(CurTokenString)));
tkIdentifier: // [16] Function call
Result := ParseFunctionCall;
else
Error(lrsParserInvalidPrimExpr);
Result := nil; // satisfy compiler
end;
NextToken;
end;
function TXPathScanner.ParseFunctionCall: TXPathExprNode;
var
Name: DOMString;
Args: TXPathNodeArray;
Buffer: array[0..15] of TXPathExprNode;
I: Integer;
begin
Name := CurTokenString;
I := 0;
if NextToken <> tkLeftBracket then
Error(lrsParserExpectedLeftBracket);
NextToken;
// Parse argument list
Args:=nil;
if CurToken <> tkRightBracket then
repeat
Buffer[I] := ParseOrExpr;
Inc(I);
if I > High(Buffer) then
AddNodes(Args, Buffer, I);
until not SkipToken(tkComma);
if CurToken <> tkRightBracket then
Error(lrsParserExpectedRightBracket);
AddNodes(Args, Buffer, I);
Result := TXPathFunctionNode.Create(Name, Args);
end;
function TXPathScanner.ParseUnionExpr: TXPathExprNode; // [18]
begin
Result := ParsePathExpr;
while SkipToken(tkPipe) do
Result := TXPathUnionNode.Create(Result, ParsePathExpr);
end;
function AddStep(Left: TXPathExprNode; Right: TStep): TXPathExprNode;
begin
Right.FLeft := Left;
Result := Right;
end;
function TXPathScanner.ParsePathExpr: TXPathExprNode; // [19]
var
tok: TXPathToken;
begin
Result := nil;
// Try to detect whether a LocationPath [1] or a FilterExpr [20] follows
if ((CurToken = tkIdentifier) and (PeekToken = tkLeftBracket) and
not (FTokenId in NodeTestKeywords)) or
(CurToken in [tkVariable, tkLeftBracket, tkString, tkNumber]) then
begin
// second, third or fourth case of [19]
Result := ParseFilterExpr;
if SkipToken(tkSlash) then { do nothing }
else if SkipToken(tkSlashSlash) then
Result := AddStep(Result, TStep.Create(axisDescendantOrSelf, ntAnyNode))
else
Exit;
end
else if CurToken in [tkSlash, tkSlashSlash] then
begin
tok := CurToken;
NextToken;
Result := TStep.Create(axisRoot, ntAnyNode);
if tok = tkSlashSlash then
Result := AddStep(Result, TStep.Create(axisDescendantOrSelf, ntAnyNode))
else if not (CurToken in [tkDot, tkDotDot, tkAt, tkAsterisk, tkIdentifier, tkNSNameTest]) then
Exit; // allow '/' alone
end;
// Continue with parsing of [3] RelativeLocationPath
repeat
Result := AddStep(Result, ParseStep);
if CurToken = tkSlashSlash then
begin
NextToken;
// Found abbreviated step ("//" for "descendant-or-self::node()")
Result := AddStep(Result, TStep.Create(axisDescendantOrSelf, ntAnyNode));
end
else if not SkipToken(tkSlash) then
break;
until False;
end;
function TXPathScanner.ParseFilterExpr: TXPathExprNode; // [20]
begin
Result := ParsePrimaryExpr;
// Parse predicates
if CurToken = tkLeftSquareBracket then
begin
Result := TXPathFilterNode.Create(Result);
ParsePredicates(TXPathFilterNode(Result).FPredicates);
end;
end;
function TXPathScanner.ParseOrExpr: TXPathExprNode; // [21]
begin
Result := ParseAndExpr;
while FTokenId = xkOr do
begin
NextToken;
Result := TXPathBooleanOpNode.Create(opOr, Result, ParseAndExpr);
end;
end;
function TXPathScanner.ParseAndExpr: TXPathExprNode; // [22]
begin
Result := ParseEqualityExpr;
while FTokenId = xkAnd do
begin
NextToken;
Result := TXPathBooleanOpNode.Create(opAnd, Result, ParseEqualityExpr);
end;
end;
function TXPathScanner.ParseEqualityExpr: TXPathExprNode; // [23]
var
op: TXPathCompareOp;
begin
Result := ParseRelationalExpr;
repeat
case CurToken of
tkEqual: op := opEqual;
tkNotEqual: op := opNotEqual;
else
Break;
end;
NextToken;
Result := TXPathCompareNode.Create(op, Result, ParseRelationalExpr);
until False;
end;
function TXPathScanner.ParseRelationalExpr: TXPathExprNode; // [24]
var
op: TXPathCompareOp;
begin
Result := ParseAdditiveExpr;
repeat
case CurToken of
tkLess: op := opLess;
tkLessEqual: op := opLessEqual;
tkGreater: op := opGreater;
tkGreaterEqual: op := opGreaterEqual;
else
Break;
end;
NextToken;
Result := TXPathCompareNode.Create(op, Result, ParseAdditiveExpr);
until False;
end;
function TXPathScanner.ParseAdditiveExpr: TXPathExprNode; // [25]
var
op: TXPathMathOp;
begin
Result := ParseMultiplicativeExpr;
repeat
case CurToken of
tkPlus: op := opAdd;
tkMinus: op := opSubtract;
else
Break;
end;
NextToken;
Result := TXPathMathOpNode.Create(op, Result, ParseMultiplicativeExpr);
until False;
end;
function TXPathScanner.ParseMultiplicativeExpr: TXPathExprNode; // [26]
var
op: TXPathMathOp;
begin
Result := ParseUnaryExpr;
repeat
case CurToken of
tkAsterisk:
op := opMultiply;
tkIdentifier:
if FTokenId = xkDiv then
op := opDivide
else if FTokenId = xkMod then
op := opMod
else
break;
else
break;
end;
NextToken;
Result := TXPathMathOpNode.Create(op, Result, ParseUnaryExpr);
until False;
end;
function TXPathScanner.ParseUnaryExpr: TXPathExprNode; // [27]
var
NegCount: Integer;
begin
NegCount := 0;
while SkipToken(tkMinus) do
Inc(NegCount);
Result := ParseUnionExpr;
if Odd(NegCount) then
Result := TXPathNegationNode.Create(Result);
end;
{ TXPathContext }
constructor TXPathContext.Create(AContextNode: TDOMNode;
AContextPosition, AContextSize: Integer);
begin
inherited Create;
ContextNode := AContextNode;
ContextPosition := AContextPosition;
ContextSize := AContextSize;
end;
{ TXPathEnvironment }
type
PFunctionInfo = ^TFunctionInfo;
TFunctionInfo = record
Name: String;
Fn: TXPathFunction;
end;
PVariableInfo = ^TVariableInfo;
TVariableInfo = record
Name: String;
Variable: TXPathVariable;
end;
constructor TXPathEnvironment.Create;
begin
inherited Create;
FFunctions := TFPList.Create;
FVariables := TFPList.Create;
// Add the functions of the XPath Core Function Library
// Node set functions
AddFunction('last', @xpLast);
AddFunction('position', @xpPosition);
AddFunction('count', @xpCount);
AddFunction('id', @xpId);
AddFunction('local-name', @xpLocalName);
AddFunction('namespace-uri', @xpNamespaceURI);
AddFunction('name', @xpName);
// String functions
AddFunction('string', @xpString);
AddFunction('concat', @xpConcat);
AddFunction('starts-with', @xpStartsWith);
AddFunction('contains', @xpContains);
AddFunction('substring-before', @xpSubstringBefore);
AddFunction('substring-after', @xpSubstringAfter);
AddFunction('substring', @xpSubstring);
AddFunction('string-length', @xpStringLength);
AddFunction('normalize-space', @xpNormalizeSpace);
AddFunction('translate', @xpTranslate);
// Boolean functions
AddFunction('boolean', @xpBoolean);
AddFunction('not', @xpNot);
AddFunction('true', @xpTrue);
AddFunction('false', @xpFalse);
AddFunction('lang', @xpLang);
// Number functions
AddFunction('number', @xpNumber);
AddFunction('sum', @xpSum);
AddFunction('floor', @xpFloor);
AddFunction('ceiling', @xpCeiling);
AddFunction('round', @xpRound);
end;
destructor TXPathEnvironment.Destroy;
var
i: Integer;
FunctionInfo: PFunctionInfo;
VariableInfo: PVariableInfo;
begin
for i := 0 to FFunctions.Count - 1 do
begin
FunctionInfo := PFunctionInfo(FFunctions[i]);
FreeMem(FunctionInfo);
end;
FFunctions.Free;
for i := 0 to FVariables.Count - 1 do
begin
VariableInfo := PVariableInfo(FVariables[i]);
FreeMem(VariableInfo);
end;
FVariables.Free;
inherited Destroy;
end;
function TXPathEnvironment.GetFunctionIndex(const AName: String): Integer;
var
i: Integer;
begin
for i := 0 to FFunctions.Count - 1 do
if PFunctionInfo(FFunctions[i])^.Name = AName then
begin
Result := i;
exit;
end;
Result := -1;
end;
function TXPathEnvironment.GetVariableIndex(const AName: String): Integer;
var
i: Integer;
begin
for i := 0 to FVariables.Count - 1 do
if PVariableInfo(FFunctions[i])^.Name = AName then
begin
Result := i;
exit;
end;
Result := -1;
end;
procedure TXPathEnvironment.AddFunction(const AName: String; AFunction: TXPathFunction);
var
NewFunctionInfo: PFunctionInfo;
begin
// !!!: Prevent the addition of duplicate functions
New(NewFunctionInfo);
NewFunctionInfo^.Name := AName;
NewFunctionInfo^.Fn := AFunction;
FFunctions.Add(NewFunctionInfo);
end;
procedure TXPathEnvironment.AddVariable(const AName: String; AVariable: TXPathVariable);
var
NewVariableInfo: PVariableInfo;
begin
// !!!: Prevent the addition of duplicate variables
New(NewVariableInfo);
NewVariableInfo^.Name := AName;
NewVariableInfo^.Variable := AVariable;
FVariables.Add(NewVariableInfo);
end;
procedure TXPathEnvironment.RemoveFunction(Index: Integer);
var
FunctionInfo: PFunctionInfo;
begin
FunctionInfo := PFunctionInfo(FFunctions[Index]);
Dispose(FunctionInfo);
FFunctions.Delete(Index);
end;
procedure TXPathEnvironment.RemoveFunction(const AName: String);
var
i: Integer;
begin
for i := 0 to FFunctions.Count - 1 do
if PFunctionInfo(FFunctions[i])^.Name = AName then
begin
RemoveFunction(i);
exit;
end;
end;
procedure TXPathEnvironment.RemoveVariable(Index: Integer);
var
VariableInfo: PVariableInfo;
begin
VariableInfo := PVariableInfo(FVariables[Index]);
Dispose(VariableInfo);
FVariables.Delete(Index);
end;
procedure TXPathEnvironment.RemoveVariable(const AName: String);
var
Index: Integer;
begin
Index := GetVariableIndex(AName);
if Index >= 0 then
RemoveVariable(Index);
end;
function TXPathEnvironment.GetFunctionCount: Integer;
begin
Result := FFunctions.Count;
end;
function TXPathEnvironment.GetVariableCount: Integer;
begin
Result := FVariables.Count;
end;
function TXPathEnvironment.GetFunction(Index: Integer): TXPathFunction;
begin
Result := PFunctionInfo(FFunctions[Index])^.Fn;
end;
function TXPathEnvironment.GetFunction(const AName: String): TXPathFunction;
var
i: Integer;
begin
for i := 0 to FFunctions.Count - 1 do
if PFunctionInfo(FFunctions[i])^.Name = AName then
begin
Result := PFunctionInfo(FFunctions[i])^.Fn;
exit;
end;
Result := nil;
end;
function TXPathEnvironment.GetVariable(Index: Integer): TXPathVariable;
begin
Result := PVariableInfo(FVariables[Index])^.Variable;
end;
function TXPathEnvironment.GetVariable(const AName: String): TXPathVariable;
var
i: Integer;
begin
for i := 0 to FVariables.Count - 1 do
if PFunctionInfo(FVariables[i])^.Name = AName then
begin
Result := PVariableInfo(FVariables[i])^.Variable;
exit;
end;
Result := nil;
end;
function TXPathEnvironment.xpLast(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
begin
if Args.Count <> 0 then
EvaluationError(lrsEvalInvalidArgCount);
Result := TXPathNumberVariable.Create(Context.ContextSize);
end;
function TXPathEnvironment.xpPosition(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
begin
if Args.Count <> 0 then
EvaluationError(lrsEvalInvalidArgCount);
Result := TXPathNumberVariable.Create(Context.ContextPosition);
end;
function TXPathEnvironment.xpCount(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
begin
if Args.Count <> 1 then
EvaluationError(lrsEvalInvalidArgCount);
Result := TXPathNumberVariable.Create(TXPathVariable(Args[0]).AsNodeSet.Count);
end;
function TXPathEnvironment.xpId(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
i: Integer;
ResultSet: TNodeSet;
TheArg: TXPathVariable;
doc: TDOMDocument;
procedure AddId(ns: TNodeSet; const s: DOMString);
var
Head, Tail, L: Integer;
Token: DOMString;
Element: TDOMNode;
begin
Head := 1;
L := Length(s);
while Head <= L do
begin
while (Head <= L) and IsXmlWhiteSpace(@s[Head]) do
Inc(Head);
Tail := Head;
while (Tail <= L) and not IsXmlWhiteSpace(@s[Tail]) do
Inc(Tail);
SetString(Token, @s[Head], Tail - Head);
Element := doc.GetElementById(Token);
if Assigned(Element) then
ns.Add(Element);
Head := Tail;
end;
end;
begin
if Args.Count <> 1 then
EvaluationError(lrsEvalInvalidArgCount);
// TODO: probably have doc as member of Context
if Context.ContextNode.NodeType = DOCUMENT_NODE then
doc := TDOMDocument(Context.ContextNode)
else
doc := Context.ContextNode.OwnerDocument;
ResultSet := TNodeSet.Create;
TheArg := TXPathVariable(Args[0]);
if TheArg is TXPathNodeSetVariable then
begin
with TheArg.AsNodeSet do
for i := 0 to Count-1 do
AddId(ResultSet, NodeToText(TDOMNode(Items[i])));
end
else
AddId(ResultSet, TheArg.AsText);
Result := TXPathNodeSetVariable.Create(ResultSet);
end;
function TXPathEnvironment.xpLocalName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
n: TDOMNode;
NodeSet: TNodeSet;
s: DOMString;
begin
if Args.Count > 1 then
EvaluationError(lrsEvalInvalidArgCount);
n := nil;
if Args.Count = 0 then
n := Context.ContextNode
else
begin
NodeSet := TXPathVariable(Args[0]).AsNodeSet;
if NodeSet.Count > 0 then
n := TDOMNode(NodeSet[0]);
end;
s := '';
if Assigned(n) then
begin
case n.NodeType of
ELEMENT_NODE,ATTRIBUTE_NODE:
with TDOMNode_NS(n) do
s := Copy(NSI.QName^.Key, NSI.PrefixLen+1, MaxInt);
PROCESSING_INSTRUCTION_NODE:
s := TDOMProcessingInstruction(n).Target;
// TODO: NAMESPACE_NODE: must return prefix part
end;
end;
Result := TXPathStringVariable.Create(s);
end;
function TXPathEnvironment.xpNamespaceURI(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
n: TDOMNode;
NodeSet: TNodeSet;
s: DOMString;
begin
if Args.Count > 1 then
EvaluationError(lrsEvalInvalidArgCount);
n := nil;
if Args.Count = 0 then
n := Context.ContextNode
else
begin
NodeSet := TXPathVariable(Args[0]).AsNodeSet;
if NodeSet.Count > 0 then
n := TDOMNode(NodeSet[0]);
end;
if Assigned(n) then
s := n.namespaceUri
else
s := '';
Result := TXPathStringVariable.Create(s);
end;
function TXPathEnvironment.xpName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
n: TDOMNode;
NodeSet: TNodeSet;
s: DOMString;
begin
if Args.Count > 1 then
EvaluationError(lrsEvalInvalidArgCount);
n := nil;
if Args.Count = 0 then
n := Context.ContextNode
else
begin
NodeSet := TXPathVariable(Args[0]).AsNodeSet;
if NodeSet.Count > 0 then
n := TDOMNode(NodeSet[0]);
end;
s := '';
if Assigned(n) then
begin
case n.NodeType of
ELEMENT_NODE,ATTRIBUTE_NODE:
s := TDOMNode_NS(n).NSI.QName^.Key;
PROCESSING_INSTRUCTION_NODE:
s := TDOMProcessingInstruction(n).Target;
// TODO: NAMESPACE_NODE: must return prefix part
end;
end;
Result := TXPathStringVariable.Create(s);
end;
function TXPathEnvironment.xpString(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
s: DOMString;
begin
if Args.Count > 1 then
EvaluationError(lrsEvalInvalidArgCount);
if Args.Count = 0 then
s := NodeToText(Context.ContextNode)
else
s := TXPathVariable(Args[0]).AsText;
Result := TXPathStringVariable.Create(s);
end;
function TXPathEnvironment.xpConcat(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
i: Integer;
s: DOMString;
begin
if Args.Count < 2 then
EvaluationError(lrsEvalInvalidArgCount);
SetLength(s, 0);
for i := 0 to Args.Count - 1 do
s := s + TXPathVariable(Args[i]).AsText;
Result := TXPathStringVariable.Create(s);
end;
function TXPathEnvironment.xpStartsWith(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
s1, s2: DOMString;
res: Boolean;
begin
if Args.Count <> 2 then
EvaluationError(lrsEvalInvalidArgCount);
s1 := TXPathVariable(Args[0]).AsText;
s2 := TXPathVariable(Args[1]).AsText;
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(lrsEvalInvalidArgCount);
s1 := TXPathVariable(Args[0]).AsText;
s2 := TXPathVariable(Args[1]).AsText;
if s2 = '' then
res := True
else
res := Pos(s2, s1) <> 0;
Result := TXPathBooleanVariable.Create(res);
end;
function TXPathEnvironment.xpSubstringBefore(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
s, substr: DOMString;
begin
if Args.Count <> 2 then
EvaluationError(lrsEvalInvalidArgCount);
s := TXPathVariable(Args[0]).AsText;
substr := TXPathVariable(Args[1]).AsText;
Result := TXPathStringVariable.Create(Copy(s, 1, Pos(substr, s)-1));
end;
function TXPathEnvironment.xpSubstringAfter(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
s, substr: DOMString;
i: Integer;
begin
if Args.Count <> 2 then
EvaluationError(lrsEvalInvalidArgCount);
s := TXPathVariable(Args[0]).AsText;
substr := TXPathVariable(Args[1]).AsText;
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;
i, n1, n2: Integer;
e1, e2: Extended;
empty: Boolean;
begin
if (Args.Count < 2) or (Args.Count > 3) then
EvaluationError(lrsEvalInvalidArgCount);
s := TXPathVariable(Args[0]).AsText;
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
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);
if empty then
n2 := -1
else if n2 < MaxInt then
n2 := n2 + (n1 - i);
Result := TXPathStringVariable.Create(Copy(s, i, n2));
end;
function TXPathEnvironment.xpStringLength(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
s: DOMString;
begin
if Args.Count > 1 then
EvaluationError(lrsEvalInvalidArgCount);
if Args.Count = 0 then
s := NodeToText(Context.ContextNode)
else
s := TXPathVariable(Args[0]).AsText;
Result := TXPathNumberVariable.Create(Length(s));
end;
function TXPathEnvironment.xpNormalizeSpace(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
s: DOMString;
p: DOMPChar;
i: Integer;
begin
if Args.Count > 1 then
EvaluationError(lrsEvalInvalidArgCount);
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;
var
S: DOMString;
begin
if Args.Count <> 3 then
EvaluationError(lrsEvalInvalidArgCount);
S := TXPathVariable(Args[0]).AsText;
TranslateUTF8Chars(S, TXPathVariable(Args[1]).AsText, TXPathVariable(Args[2]).AsText);
Result := TXPathStringVariable.Create(S);
end;
function TXPathEnvironment.xpBoolean(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
begin
if Args.Count <> 1 then
EvaluationError(lrsEvalInvalidArgCount);
Result := TXPathBooleanVariable.Create(TXPathVariable(Args[0]).AsBoolean);
end;
function TXPathEnvironment.xpNot(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
begin
if Args.Count <> 1 then
EvaluationError(lrsEvalInvalidArgCount);
Result := TXPathBooleanVariable.Create(not TXPathVariable(Args[0]).AsBoolean);
end;
function TXPathEnvironment.xpTrue(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
begin
if Args.Count <> 0 then
EvaluationError(lrsEvalInvalidArgCount);
Result := TXPathBooleanVariable.Create(True);
end;
function TXPathEnvironment.xpFalse(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
begin
if Args.Count <> 0 then
EvaluationError(lrsEvalInvalidArgCount);
Result := TXPathBooleanVariable.Create(False);
end;
function TXPathEnvironment.xpLang(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
L: Integer;
TheArg, NodeLang: DOMString;
res: Boolean;
begin
if Args.Count <> 1 then
EvaluationError(lrsEvalInvalidArgCount);
TheArg := TXPathVariable(Args[0]).AsText;
NodeLang := GetNodeLanguage(Context.ContextNode);
L := Length(TheArg);
res := (L <= Length(NodeLang)) and
(XUStrLIComp(DOMPChar(NodeLang), DOMPChar(TheArg), L) = 0) and
((L = Length(NodeLang)) or (NodeLang[L+1] = '-'));
Result := TXPathBooleanVariable.Create(res);
end;
function TXPathEnvironment.xpNumber(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
begin
if Args.Count > 1 then
EvaluationError(lrsEvalInvalidArgCount);
if Args.Count = 0 then
Result := TXPathNumberVariable.Create(StrToNumber(NodeToText(Context.ContextNode)))
else
Result := TXPathNumberVariable.Create(TXPathVariable(Args[0]).AsNumber);
end;
function TXPathEnvironment.xpSum(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
var
i: Integer;
ns: TNodeSet;
sum: Extended;
begin
if Args.Count <> 1 then
EvaluationError(lrsEvalInvalidArgCount);
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(lrsEvalInvalidArgCount);
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(lrsEvalInvalidArgCount);
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;
var
num: Extended;
begin
if Args.Count <> 1 then
EvaluationError(lrsEvalInvalidArgCount);
num := TXPathVariable(Args[0]).AsNumber;
if not (IsNan(num) or IsInfinite(num)) then
num := floor(0.5 + num);
Result := TXPathNumberVariable.Create(num);
end;
{ TXPathNSResolver }
constructor TXPathNSResolver.Create(aNode: TDOMNode);
begin
inherited Create;
FNode := aNode;
end;
function TXPathNSResolver.LookupNamespaceURI(const aPrefix: DOMString): DOMString;
begin
if assigned(FNode) then
result := FNode.LookupNamespaceURI(aPrefix)
else
result := '';
end;
{ TXPathExpression }
constructor TXPathExpression.Create(AScanner: TXPathScanner;
CompleteExpression: Boolean; AResolver: TXPathNSResolver);
begin
inherited Create;
AScanner.FResolver := AResolver;
FRootNode := AScanner.ParseOrExpr;
if CompleteExpression and (AScanner.CurToken <> tkEndOfStream) then
EvaluationError(lrsParserGarbageAfterExpression);
end;
function TXPathExpression.Evaluate(AContextNode: TDOMNode): TXPathVariable;
var
Environment: TXPathEnvironment;
begin
Environment := TXPathEnvironment.Create;
try
Result := Evaluate(AContextNode, Environment);
finally
Environment.Free;
end;
end;
destructor TXPathExpression.Destroy;
begin
FRootNode.Free;
inherited Destroy;
end;
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;
end;
function EvaluateXPathExpression(const AExpressionString: DOMString;
AContextNode: TDOMNode; AResolver: TXPathNSResolver): TXPathVariable;
var
Scanner: TXPathScanner;
Expression: TXPathExpression;
begin
Scanner := TXPathScanner.Create(AExpressionString);
try
Expression := TXPathExpression.Create(Scanner, True, AResolver);
try
Result := Expression.Evaluate(AContextNode);
finally
Expression.Free;
end;
finally
Scanner.Free;
end;
end;
end.