mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-04 08:43:46 +02:00
2982 lines
77 KiB
ObjectPascal
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.
|