|
@@ -0,0 +1,2507 @@
|
|
|
|
+{
|
|
|
|
+ $Id$
|
|
|
|
+ This file is part of the Free Component Library
|
|
|
|
+
|
|
|
|
+ Implementation of the XML Path Language (XPath) for Free Pascal
|
|
|
|
+ Copyright (c) 2000 - 2003 by
|
|
|
|
+ Areca Systems GmbH / Sebastian Guenther, [email protected]
|
|
|
|
+
|
|
|
|
+ See the file COPYING.FPC, included in this distribution,
|
|
|
|
+ for details about the copyright.
|
|
|
|
+
|
|
|
|
+ This program is distributed in the hope that it will be useful,
|
|
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
+
|
|
|
|
+ **********************************************************************}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+unit XPath;
|
|
|
|
+
|
|
|
|
+interface
|
|
|
|
+
|
|
|
|
+uses SysUtils, Classes, DOM;
|
|
|
|
+
|
|
|
|
+resourcestring
|
|
|
|
+ { XPath variables type names }
|
|
|
|
+ SNodeSet = 'node set';
|
|
|
|
+ SBoolean = 'boolean';
|
|
|
|
+ SNumber = 'number';
|
|
|
|
+ SString = 'string';
|
|
|
|
+
|
|
|
|
+ { Variable errors }
|
|
|
|
+ SVarNoConversion = 'Conversion from %s to %s not possible';
|
|
|
|
+
|
|
|
|
+ { Scanner errors }
|
|
|
|
+ SScannerInternalError = 'Internal expression scanner error';
|
|
|
|
+ SScannerQuotStringIsOpen = 'Ending ''"'' for string not found';
|
|
|
|
+ SScannerAposStringIsOpen = 'Ending "''" for string not found';
|
|
|
|
+ SScannerInvalidChar = 'Invalid character';
|
|
|
|
+
|
|
|
|
+ { Parser errors }
|
|
|
|
+ SParserExpectedLeftBracket = 'Expected ")"';
|
|
|
|
+ SParserExpectedRightBracket = 'Expected ")"';
|
|
|
|
+ SParserExpectedColonColor = 'Expected "::" after axis specifier';
|
|
|
|
+ SParserExpectedBrackets = 'Expected "()" after NodeType test';
|
|
|
|
+ SParserExpectedRightSquareBracket = 'Expected "]" after predicate';
|
|
|
|
+ SParserInvalidPrimExpr = 'Invalid primary expression';
|
|
|
|
+ SParserGarbageAfterExpression = 'Unrecognized input after expression';
|
|
|
|
+ SParserInvalidNodeTest = 'Invalid node test (syntax error)';
|
|
|
|
+ SParserExpectedVarName = 'Expected variable name after "$"';
|
|
|
|
+
|
|
|
|
+ { Evaluation errors }
|
|
|
|
+ SEvalUnknownFunction = 'Unknown function: "%s"';
|
|
|
|
+ SEvalUnknownVariable = 'Unknown variable: "%s"';
|
|
|
|
+ SEvalInvalidArgCount = 'Invalid number of function arguments';
|
|
|
|
+ SEvalFunctionNotImplementedYet = 'Function "%s" has not been implemented yet'; // !!!
|
|
|
|
+
|
|
|
|
+type
|
|
|
|
+
|
|
|
|
+ TXPathContext = class;
|
|
|
|
+ TXPathEnvironment = class;
|
|
|
|
+ TXPathVariable = class;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ XPath lexical scanner }
|
|
|
|
+
|
|
|
|
+ TXPathToken = ( // [28] - [38]
|
|
|
|
+ tkInvalid,
|
|
|
|
+ tkEndOfStream,
|
|
|
|
+ tkIdentifier,
|
|
|
|
+ tkString,
|
|
|
|
+ tkNumber,
|
|
|
|
+ tkDollar, // "$"
|
|
|
|
+ tkLeftBracket, // "("
|
|
|
|
+ tkRightBracket, // ")"
|
|
|
|
+ tkAsterisk, // "*"
|
|
|
|
+ tkPlus, // "+"
|
|
|
|
+ tkComma, // ","
|
|
|
|
+ tkMinus, // "-"
|
|
|
|
+ tkDot, // "."
|
|
|
|
+ tkDotDot, // ".."
|
|
|
|
+ tkSlash, // "/"
|
|
|
|
+ tkSlashSlash, // "//"
|
|
|
|
+ tkColon, // ":"
|
|
|
|
+ tkColonColon, // "::"
|
|
|
|
+ tkLess, // "<"
|
|
|
|
+ tkLessEqual, // "<="
|
|
|
|
+ tkEqual, // "="
|
|
|
|
+ tkNotEqual, // "!="
|
|
|
|
+ tkGreater, // ">"
|
|
|
|
+ tkGreaterEqual, // ">="
|
|
|
|
+ tkAt, // "@"
|
|
|
|
+ tkLeftSquareBracket, // "["
|
|
|
|
+ tkRightSquareBracket, // "]"
|
|
|
|
+ tkPipe // "|"
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ XPath expression parse tree }
|
|
|
|
+
|
|
|
|
+ TXPathExprNode = class
|
|
|
|
+ public
|
|
|
|
+ function Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable; virtual; abstract;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ TXPathConstantNode = class(TXPathExprNode)
|
|
|
|
+ private
|
|
|
|
+ FValue: TXPathVariable;
|
|
|
|
+ public
|
|
|
|
+ constructor Create(AValue: TXPathVariable);
|
|
|
|
+ destructor Destroy; override;
|
|
|
|
+ function Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable; override;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ TXPathVariableNode = class(TXPathExprNode)
|
|
|
|
+ private
|
|
|
|
+ FName: DOMString;
|
|
|
|
+ public
|
|
|
|
+ constructor Create(const AName: DOMString);
|
|
|
|
+ function Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable; override;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ TXPathFunctionNode = class(TXPathExprNode)
|
|
|
|
+ private
|
|
|
|
+ FName: DOMString;
|
|
|
|
+ FArgs: TList;
|
|
|
|
+ public
|
|
|
|
+ constructor Create(const AName: DOMString);
|
|
|
|
+ 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;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Node for (binary) mathematical operation
|
|
|
|
+
|
|
|
|
+ TXPathMathOp = (opAdd, opSubtract, opMultiply, opDivide, opMod);
|
|
|
|
+
|
|
|
|
+ TXPathMathOpNode = class(TXPathExprNode)
|
|
|
|
+ private
|
|
|
|
+ FOperand1, FOperand2: TXPathExprNode;
|
|
|
|
+ FOperator: TXPathMathOp;
|
|
|
|
+ public
|
|
|
|
+ constructor Create(AOperator: TXPathMathOp;
|
|
|
|
+ AOperand1, AOperand2: TXPathExprNode);
|
|
|
|
+ destructor Destroy; override;
|
|
|
|
+ function Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable; override;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Node for boolean operations
|
|
|
|
+
|
|
|
|
+ TXPathBooleanOp = (opEqual, opNotEqual, opLess, opLessEqual, opGreater,
|
|
|
|
+ opGreaterEqual, opOr, opAnd);
|
|
|
|
+
|
|
|
|
+ TXPathBooleanOpNode = class(TXPathExprNode)
|
|
|
|
+ private
|
|
|
|
+ FOperand1, FOperand2: TXPathExprNode;
|
|
|
|
+ FOperator: TXPathBooleanOp;
|
|
|
|
+ public
|
|
|
|
+ constructor Create(AOperator: TXPathBooleanOp;
|
|
|
|
+ AOperand1, AOperand2: TXPathExprNode);
|
|
|
|
+ destructor Destroy; override;
|
|
|
|
+ function Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable; override;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Node for unions (see [18])
|
|
|
|
+
|
|
|
|
+ TXPathUnionNode = class(TXPathExprNode)
|
|
|
|
+ private
|
|
|
|
+ FOperand1, FOperand2: TXPathExprNode;
|
|
|
|
+ public
|
|
|
|
+ constructor Create(AOperand1, AOperand2: TXPathExprNode);
|
|
|
|
+ destructor Destroy; override;
|
|
|
|
+ function Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable; override;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // Filter node (for [20])
|
|
|
|
+
|
|
|
|
+ TXPathFilterNode = class(TXPathExprNode)
|
|
|
|
+ private
|
|
|
|
+ FExpr: TXPathExprNode;
|
|
|
|
+ FPredicates: TList;
|
|
|
|
+ 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);
|
|
|
|
+
|
|
|
|
+ TNodeTestType = (ntAnyPrincipal, ntName, ntTextNode,
|
|
|
|
+ ntCommentNode, ntPINode, ntAnyNode);
|
|
|
|
+
|
|
|
|
+ TStep = class
|
|
|
|
+ public
|
|
|
|
+ constructor Create;
|
|
|
|
+ destructor Destroy; override;
|
|
|
|
+ NextStep: TStep;
|
|
|
|
+ Axis: TAxis;
|
|
|
|
+ NodeTestType: TNodeTestType;
|
|
|
|
+ NodeTestString: DOMString;
|
|
|
|
+ Predicates: TList;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ TXPathLocationPathNode = class(TXPathExprNode)
|
|
|
|
+ private
|
|
|
|
+ FFirstStep: TStep;
|
|
|
|
+ FIsAbsolutePath: Boolean;
|
|
|
|
+ public
|
|
|
|
+ constructor Create(AIsAbsolutePath: Boolean);
|
|
|
|
+ function Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable; override;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ TNodeSet = TList;
|
|
|
|
+
|
|
|
|
+{ 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;
|
|
|
|
+ function AsNumber: Extended; virtual;
|
|
|
|
+ function AsText: DOMString; virtual;
|
|
|
|
+ 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;
|
|
|
|
+ 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;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ XPath lexical scanner }
|
|
|
|
+
|
|
|
|
+ TXPathScannerState = class
|
|
|
|
+ private
|
|
|
|
+ FCurData: PChar;
|
|
|
|
+ FCurToken: TXPathToken;
|
|
|
|
+ FCurTokenString: DOMString;
|
|
|
|
+ FDoUnget: Boolean;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ TXPathScanner = class
|
|
|
|
+ private
|
|
|
|
+ FExpressionString, FCurData: PChar; // !!!: Change to PWideChar in future
|
|
|
|
+ FCurToken: TXPathToken;
|
|
|
|
+ FCurTokenString: DOMString;
|
|
|
|
+ FDoUnget: Boolean;
|
|
|
|
+ procedure Error(const Msg: String);
|
|
|
|
+ procedure Error(const Msg: String; const Args: array of const);
|
|
|
|
+ public
|
|
|
|
+ constructor Create(const AExpressionString: DOMString);
|
|
|
|
+ function NextToken: TXPathToken;
|
|
|
|
+ procedure UngetToken;
|
|
|
|
+ function SaveState: TXPathScannerState;
|
|
|
|
+ procedure RestoreState(AState: TXPathScannerState);
|
|
|
|
+ property CurToken: TXPathToken read FCurToken;
|
|
|
|
+ property CurTokenString: DOMString read FCurTokenString;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ XPath context }
|
|
|
|
+
|
|
|
|
+ TXPathContext = class
|
|
|
|
+ public
|
|
|
|
+ constructor Create(AContextNode: TDOMNode;
|
|
|
|
+ AContextPosition, AContextSize: Integer);
|
|
|
|
+ ContextNode: TDOMNode;
|
|
|
|
+ ContextPosition: Integer;
|
|
|
|
+ ContextSize: 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 = TList;
|
|
|
|
+
|
|
|
|
+ TXPathFunction = function(Context: TXPathContext; Args: TXPathVarList):
|
|
|
|
+ TXPathVariable of object;
|
|
|
|
+
|
|
|
|
+ TXPathEnvironment = class
|
|
|
|
+ private
|
|
|
|
+ FFunctions: TList;
|
|
|
|
+ FVariables: TList;
|
|
|
|
+ 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(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(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpStartsWith(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpContains(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpSubstringBefore(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpSubstringAfter(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpSubstring(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpStringLength(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpNormalizeSpace(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpTranslate(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpBoolean(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpNot(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpTrue(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpFalse(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpLang(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpNumber(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpSum(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpFloor(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpCeiling(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+ function xpRound(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);
|
|
|
|
+ function Evaluate(AContextNode: TDOMNode): TXPathVariable;
|
|
|
|
+ function Evaluate(AContextNode: TDOMNode;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function EvaluateXPathExpression(const AExpressionString: DOMString;
|
|
|
|
+ AContextNode: TDOMNode): TXPathVariable;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+// ===================================================================
|
|
|
|
+// ===================================================================
|
|
|
|
+
|
|
|
|
+implementation
|
|
|
|
+
|
|
|
|
+{ 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
|
|
|
|
+ Result := Result + NodeToText(Child);
|
|
|
|
+ Child := Child.NextSibling;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ ATTRIBUTE_NODE, PROCESSING_INSTRUCTION_NODE, COMMENT_NODE, TEXT_NODE,
|
|
|
|
+ CDATA_SECTION_NODE, ENTITY_REFERENCE_NODE:
|
|
|
|
+ Result := Node.NodeValue;
|
|
|
|
+ end;
|
|
|
|
+ // !!!: What to do with 'namespace nodes'?
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ XPath parse tree classes }
|
|
|
|
+
|
|
|
|
+constructor TXPathConstantNode.Create(AValue: TXPathVariable);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FValue := AValue;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+destructor TXPathConstantNode.Destroy;
|
|
|
|
+begin
|
|
|
|
+ FValue.Free;
|
|
|
|
+ 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(SEvalUnknownVariable, [FName]);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+constructor TXPathFunctionNode.Create(const AName: DOMString);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FName := AName;
|
|
|
|
+ FArgs := TList.Create;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+destructor TXPathFunctionNode.Destroy;
|
|
|
|
+var
|
|
|
|
+ i: Integer;
|
|
|
|
+begin
|
|
|
|
+ for i := 0 to FArgs.Count - 1 do
|
|
|
|
+ TXPathExprNode(FArgs[i]).Free;
|
|
|
|
+ FArgs.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(SEvalUnknownFunction, [FName]);
|
|
|
|
+
|
|
|
|
+ Args := TXPathVarList.Create;
|
|
|
|
+ try
|
|
|
|
+ for i := 0 to FArgs.Count - 1 do
|
|
|
|
+ Args.Add(TXPathExprNode(FArgs[i]).Evaluate(AContext, AEnvironment));
|
|
|
|
+ Result := Fn(AContext, Args);
|
|
|
|
+ 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;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+constructor TXPathMathOpNode.Create(AOperator: TXPathMathOp;
|
|
|
|
+ AOperand1, AOperand2: TXPathExprNode);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FOperator := AOperator;
|
|
|
|
+ FOperand1 := AOperand1;
|
|
|
|
+ FOperand2 := AOperand2;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+destructor TXPathMathOpNode.Destroy;
|
|
|
|
+begin
|
|
|
|
+ FOperand1.Free;
|
|
|
|
+ FOperand2.Free;
|
|
|
|
+ inherited Destroy;
|
|
|
|
+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:
|
|
|
|
+ NumberResult := Trunc(Op1) mod Trunc(Op2);
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ Op2Result.Release;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ Op1Result.Release;
|
|
|
|
+ end;
|
|
|
|
+ Result := TXPathNumberVariable.Create(NumberResult);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+constructor TXPathBooleanOpNode.Create(AOperator: TXPathBooleanOp;
|
|
|
|
+ AOperand1, AOperand2: TXPathExprNode);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FOperator := AOperator;
|
|
|
|
+ FOperand1 := AOperand1;
|
|
|
|
+ FOperand2 := AOperand2;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+destructor TXPathBooleanOpNode.Destroy;
|
|
|
|
+begin
|
|
|
|
+ FOperand1.Free;
|
|
|
|
+ FOperand2.Free;
|
|
|
|
+ inherited Destroy;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathBooleanOpNode.Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ Op1, Op2: TXPathVariable;
|
|
|
|
+
|
|
|
|
+ function EvalEqual: Boolean;
|
|
|
|
+ var
|
|
|
|
+ i, j: Integer;
|
|
|
|
+ NodeSet1, NodeSet2: TNodeSet;
|
|
|
|
+ s: DOMString;
|
|
|
|
+ begin
|
|
|
|
+ // !!!: Doesn't handle nodesets yet!
|
|
|
|
+ if Op1.InheritsFrom(TXPathNodeSetVariable) then
|
|
|
|
+ begin
|
|
|
|
+ NodeSet1 := Op1.AsNodeSet;
|
|
|
|
+ if Op2.InheritsFrom(TXPathNodeSetVariable) then
|
|
|
|
+ begin
|
|
|
|
+ NodeSet2 := Op2.AsNodeSet;
|
|
|
|
+ for i := 0 to NodeSet1.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ s := NodeToText(TDOMNode(NodeSet1[i]));
|
|
|
|
+ for j := 0 to NodeSet2.Count - 1 do
|
|
|
|
+ if s = NodeToText(TDOMNode(NodeSet2[j])) then
|
|
|
|
+ begin
|
|
|
|
+ Result := True;
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ end else
|
|
|
|
+ begin
|
|
|
|
+ s := Op2.AsText;
|
|
|
|
+ for i := 0 to NodeSet1.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ if NodeToText(TDOMNode(NodeSet1[i])) = s then
|
|
|
|
+ begin
|
|
|
|
+ Result := True;
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ Result := False;
|
|
|
|
+ end else if Op2.InheritsFrom(TXPathNodeSetVariable) then
|
|
|
|
+ begin
|
|
|
|
+ s := Op1.AsText;
|
|
|
|
+ for i := 0 to NodeSet2.Count - 1 do
|
|
|
|
+ if s = NodeToText(TDOMNode(NodeSet2[i])) then
|
|
|
|
+ begin
|
|
|
|
+ Result := True;
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ Result := False;
|
|
|
|
+ end else if Op1.InheritsFrom(TXPathBooleanVariable) or
|
|
|
|
+ Op2.InheritsFrom(TXPathBooleanVariable) then
|
|
|
|
+ Result := Op1.AsBoolean = Op2.AsBoolean
|
|
|
|
+ else if Op1.InheritsFrom(TXPathNumberVariable) or
|
|
|
|
+ Op2.InheritsFrom(TXPathNumberVariable) then
|
|
|
|
+ Result := Op1.AsNumber = Op2.AsNumber
|
|
|
|
+ else
|
|
|
|
+ Result := Op1.AsText = Op2.AsText; // !!!: Attention with Unicode!
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+var
|
|
|
|
+ BoolResult: Boolean;
|
|
|
|
+begin
|
|
|
|
+ Op1 := FOperand1.Evaluate(AContext, AEnvironment);
|
|
|
|
+ try
|
|
|
|
+ Op2 := FOperand2.Evaluate(AContext, AEnvironment);
|
|
|
|
+ try
|
|
|
|
+ case FOperator of
|
|
|
|
+ opEqual:
|
|
|
|
+ BoolResult := EvalEqual;
|
|
|
|
+ opNotEqual:
|
|
|
|
+ BoolResult := not EvalEqual;
|
|
|
|
+ opLess:
|
|
|
|
+ BoolResult := Op1.AsNumber < Op2.AsNumber;
|
|
|
|
+ opLessEqual:
|
|
|
|
+ BoolResult := Op1.AsNumber <= Op2.AsNumber;
|
|
|
|
+ opGreater:
|
|
|
|
+ BoolResult := Op1.AsNumber > Op2.AsNumber;
|
|
|
|
+ opGreaterEqual:
|
|
|
|
+ BoolResult := Op1.AsNumber >= Op2.AsNumber;
|
|
|
|
+ opOr:
|
|
|
|
+ BoolResult := Op1.AsBoolean or Op2.AsBoolean;
|
|
|
|
+ opAnd:
|
|
|
|
+ BoolResult := Op1.AsBoolean and Op2.AsBoolean;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ Op2.Release;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ Op1.Release;
|
|
|
|
+ end;
|
|
|
|
+ Result := TXPathBooleanVariable.Create(BoolResult);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+constructor TXPathUnionNode.Create(AOperand1, AOperand2: TXPathExprNode);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FOperand1 := AOperand1;
|
|
|
|
+ FOperand2 := AOperand2;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+destructor TXPathUnionNode.Destroy;
|
|
|
|
+begin
|
|
|
|
+ FOperand1.Free;
|
|
|
|
+ FOperand2.Free;
|
|
|
|
+ inherited Destroy;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathUnionNode.Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ Op1Result, Op2Result: TXPathVariable;
|
|
|
|
+ NodeSet, NodeSet2: TNodeSet;
|
|
|
|
+ CurNode: Pointer;
|
|
|
|
+ i, j: Integer;
|
|
|
|
+ DoAdd: Boolean;
|
|
|
|
+begin
|
|
|
|
+ Op1Result := FOperand1.Evaluate(AContext, AEnvironment);
|
|
|
|
+ try
|
|
|
|
+ Op2Result := FOperand2.Evaluate(AContext, AEnvironment);
|
|
|
|
+ try
|
|
|
|
+ NodeSet := Op1Result.AsNodeSet;
|
|
|
|
+ NodeSet2 := Op2Result.AsNodeSet;
|
|
|
|
+ try
|
|
|
|
+ for i := 0 to NodeSet2.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ DoAdd := True;
|
|
|
|
+ CurNode := NodeSet2[i];
|
|
|
|
+ for j := 0 to NodeSet.Count - 1 do
|
|
|
|
+ if NodeSet[j] = CurNode then
|
|
|
|
+ begin
|
|
|
|
+ DoAdd := False;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ if DoAdd then
|
|
|
|
+ NodeSet.Add(CurNode);
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ NodeSet2.Free;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ Op2Result.Release;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ Op1Result.Release;
|
|
|
|
+ end;
|
|
|
|
+ Result := TXPathNodeSetVariable.Create(NodeSet);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+constructor TXPathFilterNode.Create(AExpr: TXPathExprNode);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FExpr := AExpr;
|
|
|
|
+ FPredicates := TList.Create;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+destructor TXPathFilterNode.Destroy;
|
|
|
|
+var
|
|
|
|
+ i: Integer;
|
|
|
|
+begin
|
|
|
|
+ for i := 0 to FPredicates.Count - 1 do
|
|
|
|
+ TXPathExprNode(FPredicates[i]).Free;
|
|
|
|
+ FPredicates.Free;
|
|
|
|
+ inherited Destroy;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathFilterNode.Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ ExprResult, PredicateResult: TXPathVariable;
|
|
|
|
+ NodeSet, NewNodeSet: TNodeSet;
|
|
|
|
+ i, j: Integer;
|
|
|
|
+ CurContextNode: TDOMNode;
|
|
|
|
+ NewContext: TXPathContext;
|
|
|
|
+ DoAdd: Boolean;
|
|
|
|
+begin
|
|
|
|
+ ExprResult := FExpr.Evaluate(AContext, AEnvironment);
|
|
|
|
+ NewContext := nil;
|
|
|
|
+ NewNodeSet := nil;
|
|
|
|
+ try
|
|
|
|
+ NodeSet := ExprResult.AsNodeSet;
|
|
|
|
+ NewContext := TXPathContext.Create(nil, 0, NodeSet.Count);
|
|
|
|
+ NewNodeSet := TNodeSet.Create;
|
|
|
|
+ try
|
|
|
|
+ for i := 0 to NodeSet.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ CurContextNode := TDOMNode(NodeSet[i]);
|
|
|
|
+ NewContext.ContextNode := CurContextNode;
|
|
|
|
+ Inc(NewContext.ContextPosition);
|
|
|
|
+ DoAdd := True;
|
|
|
|
+ for j := 0 to FPredicates.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ PredicateResult := TXPathExprNode(FPredicates[j]).Evaluate(NewContext,
|
|
|
|
+ AEnvironment);
|
|
|
|
+ try
|
|
|
|
+ if PredicateResult.InheritsFrom(TXPathNumberVariable) then
|
|
|
|
+ begin
|
|
|
|
+ if PredicateResult.AsNumber <> i + 1 then
|
|
|
|
+ begin
|
|
|
|
+ DoAdd := False;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ end else if not PredicateResult.AsBoolean then
|
|
|
|
+ begin
|
|
|
|
+ DoAdd := False;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ PredicateResult.Release;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ if DoAdd then
|
|
|
|
+ NewNodeSet.Add(CurContextNode);
|
|
|
|
+ end;
|
|
|
|
+ except
|
|
|
|
+ NewNodeSet.Free;
|
|
|
|
+ raise;
|
|
|
|
+ end;
|
|
|
|
+ Result := TXPathNodeSetVariable.Create(NewNodeSet);
|
|
|
|
+ finally
|
|
|
|
+ NewContext.Free;
|
|
|
|
+ ExprResult.Release;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+constructor TStep.Create;
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ Predicates := TList.Create;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+destructor TStep.Destroy;
|
|
|
|
+var
|
|
|
|
+ i: Integer;
|
|
|
|
+begin
|
|
|
|
+ for i := 0 to Predicates.Count - 1 do
|
|
|
|
+ TXPathExprNode(Predicates[i]).Free;
|
|
|
|
+ Predicates.Free;
|
|
|
|
+ inherited Free;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+constructor TXPathLocationPathNode.Create(AIsAbsolutePath: Boolean);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FIsAbsolutePath := AIsAbsolutePath;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathLocationPathNode.Evaluate(AContext: TXPathContext;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ ResultNodeSet: TNodeSet;
|
|
|
|
+
|
|
|
|
+ procedure EvaluateStep(AStep: TStep; AContext: TXPathContext);
|
|
|
|
+ var
|
|
|
|
+ StepNodes: TList;
|
|
|
|
+
|
|
|
|
+ procedure DoNodeTest(Node: TDOMNode);
|
|
|
|
+ var
|
|
|
|
+ i: Integer;
|
|
|
|
+ DoAdd: Boolean;
|
|
|
|
+ begin
|
|
|
|
+ case AStep.NodeTestType of
|
|
|
|
+ ntAnyPrincipal:
|
|
|
|
+ // !!!: Probably this isn't ready for namespace support yet
|
|
|
|
+ if (AStep.Axis <> axisAttribute) and
|
|
|
|
+ (Node.NodeType <> ELEMENT_NODE) then
|
|
|
|
+ exit;
|
|
|
|
+ ntName:
|
|
|
|
+ if Node.NodeName <> AStep.NodeTestString then
|
|
|
|
+ exit;
|
|
|
|
+ ntTextNode:
|
|
|
|
+ if not Node.InheritsFrom(TDOMCharacterData) then
|
|
|
|
+ exit;
|
|
|
|
+ ntCommentNode:
|
|
|
|
+ if Node.NodeType <> COMMENT_NODE then
|
|
|
|
+ exit;
|
|
|
|
+ ntPINode:
|
|
|
|
+ if Node.NodeType <> PROCESSING_INSTRUCTION_NODE then
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ DoAdd := True;
|
|
|
|
+ for i := 0 to StepNodes.Count - 1 do
|
|
|
|
+ if TDOMNode(StepNodes[i]) = Node then
|
|
|
|
+ begin
|
|
|
|
+ DoAdd := False;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ if DoAdd then
|
|
|
|
+ StepNodes.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;
|
|
|
|
+
|
|
|
|
+ var
|
|
|
|
+ Node, Node2: TDOMNode;
|
|
|
|
+ Attr: TDOMNamedNodeMap;
|
|
|
|
+ i, j, k: Integer;
|
|
|
|
+ DoAdd: Boolean;
|
|
|
|
+
|
|
|
|
+ NewContext: TXPathContext;
|
|
|
|
+ NewStepNodes: TNodeSet;
|
|
|
|
+ Predicate: TXPathExprNode;
|
|
|
|
+ PredicateResult: TXPathVariable;
|
|
|
|
+
|
|
|
|
+ begin
|
|
|
|
+ StepNodes := TList.Create;
|
|
|
|
+ // !!!: Protect this with an try/finally block
|
|
|
|
+ case AStep.Axis of
|
|
|
|
+ axisAncestor:
|
|
|
|
+ begin
|
|
|
|
+ Node := AContext.ContextNode.ParentNode;
|
|
|
|
+ while Assigned(Node) do
|
|
|
|
+ begin
|
|
|
|
+ DoNodeTest(Node);
|
|
|
|
+ Node := Node.ParentNode;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ axisAncestorOrSelf:
|
|
|
|
+ begin
|
|
|
|
+ Node := AContext.ContextNode;
|
|
|
|
+ repeat
|
|
|
|
+ DoNodeTest(Node);
|
|
|
|
+ Node := Node.ParentNode;
|
|
|
|
+ until not Assigned(Node);
|
|
|
|
+ end;
|
|
|
|
+ axisAttribute:
|
|
|
|
+ begin
|
|
|
|
+ Attr := AContext.ContextNode.Attributes;
|
|
|
|
+ if Assigned(Attr) then
|
|
|
|
+ for i := 0 to Attr.Length - 1 do
|
|
|
|
+ DoNodeTest(Attr[i]);
|
|
|
|
+ end;
|
|
|
|
+ axisChild:
|
|
|
|
+ begin
|
|
|
|
+ Node := AContext.ContextNode.FirstChild;
|
|
|
|
+ while Assigned(Node) do
|
|
|
|
+ begin
|
|
|
|
+ DoNodeTest(Node);
|
|
|
|
+ Node := Node.NextSibling;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ axisDescendant:
|
|
|
|
+ AddDescendants(AContext.ContextNode);
|
|
|
|
+ axisDescendantOrSelf:
|
|
|
|
+ begin
|
|
|
|
+ DoNodeTest(AContext.ContextNode);
|
|
|
|
+ AddDescendants(AContext.ContextNode);
|
|
|
|
+ end;
|
|
|
|
+ axisFollowing:
|
|
|
|
+ begin
|
|
|
|
+ Node := AContext.ContextNode;
|
|
|
|
+ repeat
|
|
|
|
+ Node2 := Node.NextSibling;
|
|
|
|
+ while Assigned(Node2) do
|
|
|
|
+ begin
|
|
|
|
+ DoNodeTest(Node2);
|
|
|
|
+ AddDescendants(Node2);
|
|
|
|
+ Node := Node.NextSibling;
|
|
|
|
+ end;
|
|
|
|
+ Node := Node.ParentNode;
|
|
|
|
+ until not Assigned(Node);
|
|
|
|
+ end;
|
|
|
|
+ axisFollowingSibling:
|
|
|
|
+ begin
|
|
|
|
+ Node := AContext.ContextNode.NextSibling;
|
|
|
|
+ while Assigned(Node) do
|
|
|
|
+ begin
|
|
|
|
+ DoNodeTest(Node);
|
|
|
|
+ Node := Node.NextSibling;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ {axisNamespace: !!!: Not supported yet}
|
|
|
|
+ axisParent:
|
|
|
|
+ if Assigned(AContext.ContextNode.ParentNode) then
|
|
|
|
+ DoNodeTest(AContext.ContextNode);
|
|
|
|
+ axisPreceding:
|
|
|
|
+ begin
|
|
|
|
+ Node := AContext.ContextNode;
|
|
|
|
+ repeat
|
|
|
|
+ Node2 := Node.PreviousSibling;
|
|
|
|
+ while Assigned(Node2) do
|
|
|
|
+ begin
|
|
|
|
+ DoNodeTest(Node2);
|
|
|
|
+ AddDescendants(Node2);
|
|
|
|
+ Node := Node.PreviousSibling;
|
|
|
|
+ end;
|
|
|
|
+ Node := Node.ParentNode;
|
|
|
|
+ until not Assigned(Node);
|
|
|
|
+ end;
|
|
|
|
+ axisPrecedingSibling:
|
|
|
|
+ begin
|
|
|
|
+ Node := AContext.ContextNode.PreviousSibling;
|
|
|
|
+ while Assigned(Node) do
|
|
|
|
+ begin
|
|
|
|
+ DoNodeTest(Node);
|
|
|
|
+ Node := Node.PreviousSibling;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ axisSelf:
|
|
|
|
+ DoNodeTest(AContext.ContextNode);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ { Filter the nodes of this step using the predicates: The current
|
|
|
|
+ node set (StepNodes) is filtered, all passed nodes will be added
|
|
|
|
+ to NewStepNodes. After one filter has been applied, NewStepNodes
|
|
|
|
+ gets copied to StepNodes, and the next filter will be processed.
|
|
|
|
+ The final result will then be passed to the next step, or added
|
|
|
|
+ to the result of the LocationPath if this is the last step. }
|
|
|
|
+
|
|
|
|
+ for i := 0 to AStep.Predicates.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ NewContext := TXPathContext.Create(nil, 0, StepNodes.Count);
|
|
|
|
+ NewStepNodes := nil;
|
|
|
|
+ try
|
|
|
|
+ NewStepNodes := TNodeSet.Create;
|
|
|
|
+ Predicate := TXPathExprNode(AStep.Predicates[i]);
|
|
|
|
+ for j := 0 to StepNodes.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ Node := TDOMNode(StepNodes[j]);
|
|
|
|
+ NewContext.ContextNode := Node;
|
|
|
|
+ Inc(NewContext.ContextPosition);
|
|
|
|
+ PredicateResult := Predicate.Evaluate(NewContext, AEnvironment);
|
|
|
|
+ try
|
|
|
|
+ if (PredicateResult.InheritsFrom(TXPathNumberVariable) and
|
|
|
|
+ (PredicateResult.AsNumber = j + 1)) or
|
|
|
|
+ PredicateResult.AsBoolean then
|
|
|
|
+ NewStepNodes.Add(Node);
|
|
|
|
+ finally
|
|
|
|
+ PredicateResult.Release;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ NewContext.Free;
|
|
|
|
+ StepNodes.Free;
|
|
|
|
+ StepNodes := NewStepNodes;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ if Assigned(AStep.NextStep) then
|
|
|
|
+ begin
|
|
|
|
+ NewContext := TXPathContext.Create(nil, 0, StepNodes.Count);
|
|
|
|
+ try
|
|
|
|
+ for i := 0 to StepNodes.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ NewContext.ContextNode := TDOMNode(StepNodes[i]);
|
|
|
|
+ Inc(NewContext.ContextPosition);
|
|
|
|
+ EvaluateStep(AStep.NextStep, NewContext);
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ NewContext.Free;
|
|
|
|
+ end;
|
|
|
|
+ end else
|
|
|
|
+ begin
|
|
|
|
+ // Only add nodes to result if it isn't duplicate
|
|
|
|
+ for i := 0 to StepNodes.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ Node := TDOMNode(StepNodes[i]);
|
|
|
|
+ DoAdd := True;
|
|
|
|
+ for j := 0 to ResultNodeSet.Count - 1 do
|
|
|
|
+ if TDOMNode(ResultNodeSet[j]) = Node then
|
|
|
|
+ begin
|
|
|
|
+ DoAdd := False;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ if DoAdd then
|
|
|
|
+ ResultNodeSet.Add(Node);
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ StepNodes.Free;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+var
|
|
|
|
+ NewContext: TXPathContext;
|
|
|
|
+begin
|
|
|
|
+ ResultNodeSet := TNodeSet.Create;
|
|
|
|
+ try
|
|
|
|
+ if FIsAbsolutePath then
|
|
|
|
+ begin
|
|
|
|
+ NewContext := TXPathContext.Create(AContext.ContextNode.OwnerDocument,
|
|
|
|
+ 1, 1);
|
|
|
|
+ try
|
|
|
|
+ EvaluateStep(FFirstStep, NewContext);
|
|
|
|
+ finally
|
|
|
|
+ NewContext.Free;
|
|
|
|
+ end;
|
|
|
|
+ end else
|
|
|
|
+ EvaluateStep(FFirstStep, AContext);
|
|
|
|
+ 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(SVarNoConversion, [TypeName, TXPathNodeSetVariable.TypeName]);
|
|
|
|
+ Result := nil;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathVariable.AsBoolean: Boolean;
|
|
|
|
+begin
|
|
|
|
+ Error(SVarNoConversion, [TypeName, TXPathBooleanVariable.TypeName]);
|
|
|
|
+ Result := False;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathVariable.AsNumber: Extended;
|
|
|
|
+begin
|
|
|
|
+ Error(SVarNoConversion, [TypeName, TXPathNumberVariable.TypeName]);
|
|
|
|
+ Result := 0;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathVariable.AsText: String;
|
|
|
|
+begin
|
|
|
|
+ Error(SVarNoConversion, [TypeName, TXPathStringVariable.TypeName]);
|
|
|
|
+ SetLength(Result, 0);
|
|
|
|
+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;
|
|
|
|
+
|
|
|
|
+function TXPathNodeSetVariable.TypeName: String;
|
|
|
|
+begin
|
|
|
|
+ Result := SNodeSet;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathNodeSetVariable.AsNodeSet: TNodeSet;
|
|
|
|
+begin
|
|
|
|
+ Result := FValue;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathNodeSetVariable.AsText: DOMString;
|
|
|
|
+begin
|
|
|
|
+ if FValue.Count = 0 then
|
|
|
|
+ SetLength(Result, 0)
|
|
|
|
+ else
|
|
|
|
+ Result := NodeToText(TDOMNode(FValue[0]));
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+constructor TXPathBooleanVariable.Create(AValue: Boolean);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FValue := AValue;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathBooleanVariable.TypeName: String;
|
|
|
|
+begin
|
|
|
|
+ Result := SBoolean;
|
|
|
|
+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;
|
|
|
|
+
|
|
|
|
+function TXPathNumberVariable.TypeName: String;
|
|
|
|
+begin
|
|
|
|
+ Result := SNumber;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathNumberVariable.AsBoolean: Boolean;
|
|
|
|
+begin
|
|
|
|
+ // !!!: What about NaNs and so on?
|
|
|
|
+ if FValue = 0 then
|
|
|
|
+ Result := False
|
|
|
|
+ else
|
|
|
|
+ Result := True;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathNumberVariable.AsNumber: Extended;
|
|
|
|
+begin
|
|
|
|
+ Result := FValue;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathNumberVariable.AsText: DOMString;
|
|
|
|
+begin
|
|
|
|
+ Result := FloatToStr(FValue);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+constructor TXPathStringVariable.Create(const AValue: DOMString);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FValue := AValue;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathStringVariable.TypeName: String;
|
|
|
|
+begin
|
|
|
|
+ Result := SString;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathStringVariable.AsBoolean: Boolean;
|
|
|
|
+begin
|
|
|
|
+ Result := Length(FValue) > 0;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathStringVariable.AsNumber: Extended;
|
|
|
|
+begin
|
|
|
|
+ Result := StrToFloat(FValue);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathStringVariable.AsText: DOMString;
|
|
|
|
+begin
|
|
|
|
+ Result := FValue;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ XPath lexical scanner }
|
|
|
|
+
|
|
|
|
+constructor TXPathScanner.Create(const AExpressionString: DOMString);
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FExpressionString := PChar(AExpressionString);
|
|
|
|
+ FCurData := FExpressionString;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathScanner.NextToken: TXPathToken;
|
|
|
|
+
|
|
|
|
+ procedure GetNumber;
|
|
|
|
+ var
|
|
|
|
+ HasDot: Boolean;
|
|
|
|
+ begin
|
|
|
|
+ HasDot := Pos('.', FCurTokenString) > 0;
|
|
|
|
+ while (FCurData[1] in ['0'..'9']) or ((FCurData[1] = '.') and not HasDot) do
|
|
|
|
+ begin
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+ FCurTokenString := FCurTokenString + FCurData[0];
|
|
|
|
+ if FCurData[0] = '.' then
|
|
|
|
+ HasDot := True;
|
|
|
|
+ end;
|
|
|
|
+ Result := tkNumber;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+const
|
|
|
|
+ IdentifierChars = ['A'..'Z', 'a'..'z', '0'..'9', '.', '-', '_'];
|
|
|
|
+begin
|
|
|
|
+ if FDoUnget then
|
|
|
|
+ begin
|
|
|
|
+ FDoUnget := False;
|
|
|
|
+ Result := FCurToken;
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ if FCurToken = tkEndOfStream then
|
|
|
|
+ begin
|
|
|
|
+ Result := tkEndOfStream;
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ { No, we cannot use a lookup table here, as future
|
|
|
|
+ versions will use WideStrings -sg }
|
|
|
|
+
|
|
|
|
+ // Skip whitespace
|
|
|
|
+ while FCurData[0] in [#9, #10, #12, #13, ' '] do
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+
|
|
|
|
+ FCurTokenString := FCurData[0];
|
|
|
|
+
|
|
|
|
+ case FCurData[0] of
|
|
|
|
+ #0:
|
|
|
|
+ Result := tkEndOfStream;
|
|
|
|
+ '!':
|
|
|
|
+ if FCurData[1] = '=' then
|
|
|
|
+ begin
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+ Result := tkNotEqual;
|
|
|
|
+ end;
|
|
|
|
+ '"':
|
|
|
|
+ begin
|
|
|
|
+ SetLength(FCurTokenString, 0);
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+ while FCurData[0] <> '"' do
|
|
|
|
+ begin
|
|
|
|
+ if FCurData[0] = #0 then
|
|
|
|
+ Error(SScannerQuotStringIsOpen);
|
|
|
|
+ FCurTokenString := FCurTokenString + FCurData[0];
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+ end;
|
|
|
|
+ Result := tkString;
|
|
|
|
+ end;
|
|
|
|
+ '$':
|
|
|
|
+ Result := tkDollar;
|
|
|
|
+ '''':
|
|
|
|
+ begin
|
|
|
|
+ SetLength(FCurTokenString, 0);
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+ while FCurData[0] <> '''' do
|
|
|
|
+ begin
|
|
|
|
+ if FCurData[0] = #0 then
|
|
|
|
+ Error(SScannerAposStringIsOpen);
|
|
|
|
+ FCurTokenString := FCurTokenString + FCurData[0];
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+ end;
|
|
|
|
+ Result := tkString;
|
|
|
|
+ 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] in ['0'..'9'] then
|
|
|
|
+ GetNumber
|
|
|
|
+ else
|
|
|
|
+ Result := tkDot;
|
|
|
|
+ '/':
|
|
|
|
+ if FCurData[1] = '/' then
|
|
|
|
+ begin
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+ Result := tkSlashSlash;
|
|
|
|
+ end else
|
|
|
|
+ Result := tkSlash;
|
|
|
|
+ '0'..'9':
|
|
|
|
+ GetNumber;
|
|
|
|
+ ':':
|
|
|
|
+ if FCurData[1] = ':' then
|
|
|
|
+ begin
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+ Result := tkColonColon;
|
|
|
|
+ end else
|
|
|
|
+ Result := tkColon;
|
|
|
|
+ '<':
|
|
|
|
+ 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;
|
|
|
|
+ 'A'..'Z', 'a'..'z':
|
|
|
|
+ begin
|
|
|
|
+ Result := tkIdentifier;
|
|
|
|
+ while FCurData[1] in IdentifierChars do
|
|
|
|
+ begin
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+ FCurTokenString := FCurTokenString + FCurData[0];
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ '[':
|
|
|
|
+ Result := tkLeftSquareBracket;
|
|
|
|
+ ']':
|
|
|
|
+ Result := tkRightSquareBracket;
|
|
|
|
+ '|':
|
|
|
|
+ Result := tkPipe;
|
|
|
|
+ else
|
|
|
|
+ Error(SScannerInvalidChar);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ // We have processed at least one character now; eat it:
|
|
|
|
+ if Result <> tkEndOfStream then
|
|
|
|
+ Inc(FCurData);
|
|
|
|
+
|
|
|
|
+ FCurToken := Result;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TXPathScanner.UngetToken;
|
|
|
|
+begin
|
|
|
|
+ if FDoUnget then
|
|
|
|
+ Error(SScannerInternalError, ['Tried to unget token a second time']);
|
|
|
|
+ FDoUnget := True;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathScanner.SaveState: TXPathScannerState;
|
|
|
|
+begin
|
|
|
|
+ Result := TXPathScannerState.Create;
|
|
|
|
+ Result.FCurData := FCurData;
|
|
|
|
+ Result.FCurToken := FCurToken;
|
|
|
|
+ Result.FCurTokenString := FCurTokenString;
|
|
|
|
+ Result.FDoUnget := FDoUnget;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TXPathScanner.RestoreState(AState: TXPathScannerState);
|
|
|
|
+begin
|
|
|
|
+ FCurData := AState.FCurData;
|
|
|
|
+ FCurToken := AState.FCurToken;
|
|
|
|
+ FCurTokenString := AState.FCurTokenString;
|
|
|
|
+ FDoUnget := AState.FDoUnget;
|
|
|
|
+ AState.Free;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TXPathScanner.Error(const Msg: String);
|
|
|
|
+begin
|
|
|
|
+ raise Exception.Create(Msg) at get_caller_addr(get_frame);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TXPathScanner.Error(const Msg: String; const Args: array of const);
|
|
|
|
+begin
|
|
|
|
+ raise Exception.CreateFmt(Msg, Args) at get_caller_addr(get_frame);
|
|
|
|
+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 := TList.Create;
|
|
|
|
+ FVariables := TList.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(SEvalInvalidArgCount);
|
|
|
|
+ Result := TXPathNumberVariable.Create(Context.ContextSize);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpPosition(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 0 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ Result := TXPathNumberVariable.Create(Context.ContextPosition);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpCount(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ Result := TXPathNumberVariable.Create(TXPathVariable(Args[0]).AsNodeSet.Count);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpId(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['id']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpLocalName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count > 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['local-name']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpNamespaceURI(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count > 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['namespace-uri']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ NodeSet: TNodeSet;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ NodeSet := TXPathVariable(Args[0]).AsNodeSet;
|
|
|
|
+ if NodeSet.Count = 0 then
|
|
|
|
+ Result := TXPathStringVariable.Create('')
|
|
|
|
+ else
|
|
|
|
+ // !!!: Probably not really correct regarding namespaces...
|
|
|
|
+ Result := TXPathStringVariable.Create(TDOMNode(NodeSet[0]).NodeName);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpString(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ s: String;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count > 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ 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(SEvalInvalidArgCount);
|
|
|
|
+ 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;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 2 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['namespace-uri']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpContains(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 2 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['contains']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpSubstringBefore(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 2 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['substring-before']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpSubstringAfter(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['substring-after']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpSubstring(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if (Args.Count < 2) or (Args.Count > 3) then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['substring']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpStringLength(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ s: DOMString;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count < 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ 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;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count < 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['normalize-space']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpTranslate(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 3 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['translate']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpBoolean(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ Result := TXPathBooleanVariable.Create(TXPathVariable(Args[0]).AsBoolean);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpNot(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ Result := TXPathBooleanVariable.Create(not TXPathVariable(Args[0]).AsBoolean);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpTrue(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 0 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ Result := TXPathBooleanVariable.Create(True);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpFalse(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 0 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ Result := TXPathBooleanVariable.Create(False);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpLang(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['lang']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpNumber(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count > 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['number']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpSum(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['sum']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpFloor(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['floor']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpCeiling(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['ceiling']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathEnvironment.xpRound(Context: TXPathContext; Args: TXPathVarList): TXPathVariable;
|
|
|
|
+begin
|
|
|
|
+ if Args.Count <> 1 then
|
|
|
|
+ EvaluationError(SEvalInvalidArgCount);
|
|
|
|
+ EvaluationError(SEvalFunctionNotImplementedYet, ['round']); // !!!
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ TXPathExpression }
|
|
|
|
+
|
|
|
|
+constructor TXPathExpression.Create(AScanner: TXPathScanner;
|
|
|
|
+ CompleteExpression: Boolean);
|
|
|
|
+
|
|
|
|
+ function ParseLocationPath: TXPathLocationPathNode; forward; // [1]
|
|
|
|
+ function ParsePrimaryExpr: TXPathExprNode; forward; // [15]
|
|
|
|
+ function ParseUnionExpr: TXPathExprNode; forward; // [18]
|
|
|
|
+ function ParsePathExpr: TXPathExprNode; forward; // [19]
|
|
|
|
+ function ParseFilterExpr: TXPathExprNode; forward; // [20]
|
|
|
|
+ function ParseOrExpr: TXPathExprNode; forward; // [21]
|
|
|
|
+ function ParseAndExpr: TXPathExprNode; forward; // [22]
|
|
|
|
+ function ParseEqualityExpr: TXPathExprNode; forward; // [23]
|
|
|
|
+ function ParseRelationalExpr: TXPathExprNode; forward; // [24]
|
|
|
|
+ function ParseAdditiveExpr: TXPathExprNode; forward; // [25]
|
|
|
|
+ function ParseMultiplicativeExpr: TXPathExprNode; forward; // [26]
|
|
|
|
+ function ParseUnaryExpr: TXPathExprNode; forward; // [27]
|
|
|
|
+
|
|
|
|
+ procedure Error(const Msg: String);
|
|
|
|
+ begin
|
|
|
|
+ raise Exception.Create(Msg) at get_caller_addr(get_frame);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ procedure Error(const Msg: String; const Args: array of const);
|
|
|
|
+ begin
|
|
|
|
+ raise Exception.CreateFmt(Msg, Args) at get_caller_addr(get_frame);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseLocationPath: TXPathLocationPathNode; // [1]
|
|
|
|
+ var
|
|
|
|
+ IsAbsolute, NeedColonColon: Boolean;
|
|
|
|
+ FirstStep, CurStep, NextStep: TStep;
|
|
|
|
+ begin
|
|
|
|
+ IsAbsolute := False;
|
|
|
|
+ CurStep := nil;
|
|
|
|
+
|
|
|
|
+ case AScanner.NextToken of
|
|
|
|
+ tkSlash: // [2] AbsoluteLocationPath, first case
|
|
|
|
+ begin
|
|
|
|
+ IsAbsolute := True;
|
|
|
|
+ if not (AScanner.NextToken in
|
|
|
|
+ [tkDot, tkDotDot, tkAsterisk, tkAt, tkIdentifier]) then
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ end;
|
|
|
|
+ tkSlashSlash: // [10] AbbreviatedAbsoluteLocationPath
|
|
|
|
+ begin
|
|
|
|
+ IsAbsolute := True;
|
|
|
|
+ CurStep := TStep.Create;
|
|
|
|
+ CurStep.Axis := axisDescendantOrSelf;
|
|
|
|
+ CurStep.NodeTestType := ntAnyNode;
|
|
|
|
+ end;
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ IsAbsolute := False;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ // Parse [3] RelativeLocationPath
|
|
|
|
+ FirstStep := CurStep;
|
|
|
|
+ while True do
|
|
|
|
+ begin
|
|
|
|
+ NextStep := TStep.Create;
|
|
|
|
+ if Assigned(CurStep) then
|
|
|
|
+ CurStep.NextStep := NextStep
|
|
|
|
+ else
|
|
|
|
+ FirstStep := NextStep;
|
|
|
|
+ CurStep := NextStep;
|
|
|
|
+
|
|
|
|
+ // Parse [4] Step
|
|
|
|
+ case AScanner.NextToken of
|
|
|
|
+ tkDot: // [12] Abbreviated step, first case
|
|
|
|
+ begin
|
|
|
|
+ CurStep.Axis := axisSelf;
|
|
|
|
+ CurStep.NodeTestType := ntAnyNode;
|
|
|
|
+ end;
|
|
|
|
+ tkDotDot: // [12] Abbreviated step, second case
|
|
|
|
+ begin
|
|
|
|
+ CurStep.Axis := axisParent;
|
|
|
|
+ CurStep.NodeTestType := ntAnyNode;
|
|
|
|
+ end;
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+
|
|
|
|
+ // Parse [5] AxisSpecifier
|
|
|
|
+ case AScanner.NextToken of
|
|
|
|
+ tkAt: // [13] AbbreviatedAxisSpecifier
|
|
|
|
+ CurStep.Axis := axisAttribute;
|
|
|
|
+ tkIdentifier: // [5] AxisName '::'
|
|
|
|
+ begin
|
|
|
|
+ // Check for [6] AxisName
|
|
|
|
+ NeedColonColon := True;
|
|
|
|
+ if AScanner.CurTokenString = 'ancestor' then
|
|
|
|
+ CurStep.Axis := axisAncestor
|
|
|
|
+ else if AScanner.CurTokenString = 'ancestor-or-self' then
|
|
|
|
+ CurStep.Axis := axisAncestorOrSelf
|
|
|
|
+ else if AScanner.CurTokenString = 'attribute' then
|
|
|
|
+ CurStep.Axis := axisAttribute
|
|
|
|
+ else if AScanner.CurTokenString = 'child' then
|
|
|
|
+ CurStep.Axis := axisChild
|
|
|
|
+ else if AScanner.CurTokenString = 'descendant' then
|
|
|
|
+ CurStep.Axis := axisDescendant
|
|
|
|
+ else if AScanner.CurTokenString = 'descendant-or-self' then
|
|
|
|
+ CurStep.Axis := axisDescendantOrSelf
|
|
|
|
+ else if AScanner.CurTokenString = 'following' then
|
|
|
|
+ CurStep.Axis := axisFollowing
|
|
|
|
+ else if AScanner.CurTokenString = 'following-sibling' then
|
|
|
|
+ CurStep.Axis := axisFollowingSibling
|
|
|
|
+ else if AScanner.CurTokenString = 'namespace' then
|
|
|
|
+ CurStep.Axis := axisNamespace
|
|
|
|
+ else if AScanner.CurTokenString = 'parent' then
|
|
|
|
+ CurStep.Axis := axisParent
|
|
|
|
+ else if AScanner.CurTokenString = 'preceding' then
|
|
|
|
+ CurStep.Axis := axisPreceding
|
|
|
|
+ else if AScanner.CurTokenString = 'preceding-sibling' then
|
|
|
|
+ CurStep.Axis := axisPrecedingSibling
|
|
|
|
+ else if AScanner.CurTokenString = 'self' then
|
|
|
|
+ CurStep.Axis := axisSelf
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ NeedColonColon := False;
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ CurStep.Axis := axisChild;
|
|
|
|
+ end;
|
|
|
|
+ if NeedColonColon and (AScanner.NextToken <> tkColonColon) then
|
|
|
|
+ Error(SParserExpectedColonColor);
|
|
|
|
+ end;
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ CurStep.Axis := axisChild;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ // Parse [7] NodeTest
|
|
|
|
+ case AScanner.NextToken of
|
|
|
|
+ tkAsterisk: // [37] NameTest, first case
|
|
|
|
+ CurStep.NodeTestType := ntAnyPrincipal;
|
|
|
|
+ tkIdentifier:
|
|
|
|
+ begin
|
|
|
|
+ // Check for case [38] NodeType
|
|
|
|
+ if AScanner.CurTokenString = 'comment' then
|
|
|
|
+ begin
|
|
|
|
+ if (AScanner.NextToken <> tkLeftBracket) or
|
|
|
|
+ (AScanner.NextToken <> tkRightBracket) then
|
|
|
|
+ Error(SParserExpectedBrackets);
|
|
|
|
+ CurStep.NodeTestType := ntCommentNode;
|
|
|
|
+ end else if AScanner.CurTokenString = 'text' then
|
|
|
|
+ begin
|
|
|
|
+ if (AScanner.NextToken <> tkLeftBracket) or
|
|
|
|
+ (AScanner.NextToken <> tkRightBracket) then
|
|
|
|
+ Error(SParserExpectedBrackets);
|
|
|
|
+ CurStep.NodeTestType := ntTextNode;
|
|
|
|
+ end else if AScanner.CurTokenString = 'processing-instruction' then
|
|
|
|
+ begin
|
|
|
|
+ if (AScanner.NextToken <> tkLeftBracket) or
|
|
|
|
+ (AScanner.NextToken <> tkRightBracket) then
|
|
|
|
+ Error(SParserExpectedBrackets);
|
|
|
|
+ CurStep.NodeTestType := ntPINode;
|
|
|
|
+ end else if AScanner.CurTokenString = 'node' then
|
|
|
|
+ begin
|
|
|
|
+ if (AScanner.NextToken <> tkLeftBracket) or
|
|
|
|
+ (AScanner.NextToken <> tkRightBracket) then
|
|
|
|
+ Error(SParserExpectedBrackets);
|
|
|
|
+ CurStep.NodeTestType := ntAnyNode;
|
|
|
|
+ end else // [37] NameTest, second or third case
|
|
|
|
+ begin
|
|
|
|
+ // !!!: Doesn't support namespaces yet
|
|
|
|
+ // (this will have to wait until the DOM unit supports them)
|
|
|
|
+ CurStep.NodeTestType := ntName;
|
|
|
|
+ CurStep.NodeTestString := AScanner.CurTokenString;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ else
|
|
|
|
+ Error(SParserInvalidNodeTest);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ // Parse predicates
|
|
|
|
+ while AScanner.NextToken = tkLeftSquareBracket do
|
|
|
|
+ begin
|
|
|
|
+ CurStep.Predicates.Add(ParseOrExpr);
|
|
|
|
+ if AScanner.NextToken <> tkRightSquareBracket then
|
|
|
|
+ Error(SParserExpectedRightSquareBracket);
|
|
|
|
+ end;
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ // Continue with parsing of [3] RelativeLocationPath
|
|
|
|
+ if AScanner.NextToken = tkSlashSlash then
|
|
|
|
+ begin
|
|
|
|
+ // Found abbreviated step ("//" for "descendant-or-self::node()")
|
|
|
|
+ NextStep := TStep.Create;
|
|
|
|
+ CurStep.NextStep := NextStep;
|
|
|
|
+ CurStep := NextStep;
|
|
|
|
+ CurStep.Axis := axisDescendantOrSelf;
|
|
|
|
+ CurStep.NodeTestType := ntAnyNode;
|
|
|
|
+ end else if AScanner.CurToken <> tkSlash then
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ Result := TXPathLocationPathNode.Create(IsAbsolute);
|
|
|
|
+ TXPathLocationPathNode(Result).FFirstStep := FirstStep;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParsePrimaryExpr: TXPathExprNode; // [15]
|
|
|
|
+ var
|
|
|
|
+ IsFirstArg: Boolean;
|
|
|
|
+ begin
|
|
|
|
+ case AScanner.NextToken of
|
|
|
|
+ tkDollar: // [36] Variable reference
|
|
|
|
+ begin
|
|
|
|
+ if AScanner.NextToken <> tkIdentifier then
|
|
|
|
+ Error(SParserExpectedVarName);
|
|
|
|
+ Result := TXPathVariableNode.Create(AScanner.CurTokenString);
|
|
|
|
+ end;
|
|
|
|
+ tkLeftBracket:
|
|
|
|
+ begin
|
|
|
|
+ Result := ParseOrExpr;
|
|
|
|
+ if AScanner.NextToken <> tkRightBracket then
|
|
|
|
+ Error(SParserExpectedRightBracket);
|
|
|
|
+ end;
|
|
|
|
+ tkString: // [29] Literal
|
|
|
|
+ Result := TXPathConstantNode.Create(
|
|
|
|
+ TXPathStringVariable.Create(AScanner.CurTokenString));
|
|
|
|
+ tkNumber: // [30] Number
|
|
|
|
+ Result := TXPathConstantNode.Create(
|
|
|
|
+ TXPathNumberVariable.Create(StrToFloat(AScanner.CurTokenString)));
|
|
|
|
+ tkIdentifier: // [16] Function call
|
|
|
|
+ begin
|
|
|
|
+ Result := TXPathFunctionNode.Create(AScanner.CurTokenString);
|
|
|
|
+ if AScanner.NextToken <> tkLeftBracket then
|
|
|
|
+ Error(SParserExpectedLeftBracket);
|
|
|
|
+ // Parse argument list
|
|
|
|
+ IsFirstArg := True;
|
|
|
|
+ while AScanner.NextToken <> tkRightBracket do
|
|
|
|
+ begin
|
|
|
|
+ if IsFirstArg then
|
|
|
|
+ begin
|
|
|
|
+ IsFirstArg := False;
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ end else
|
|
|
|
+ if AScanner.CurToken <> tkComma then
|
|
|
|
+ Error(SParserExpectedRightBracket);
|
|
|
|
+ TXPathFunctionNode(Result).FArgs.Add(ParseOrExpr);
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ else
|
|
|
|
+ Error(SParserInvalidPrimExpr);
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseUnionExpr: TXPathExprNode; // [18]
|
|
|
|
+ begin
|
|
|
|
+ Result := ParsePathExpr;
|
|
|
|
+ while True do
|
|
|
|
+ if AScanner.NextToken = tkPipe then
|
|
|
|
+ Result := TXPathUnionNode.Create(Result, ParsePathExpr)
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParsePathExpr: TXPathExprNode; // [19]
|
|
|
|
+ var
|
|
|
|
+ ScannerState: TXPathScannerState;
|
|
|
|
+ IsFunctionCall: Boolean;
|
|
|
|
+ begin
|
|
|
|
+ // Try to detect wether a LocationPath [1] or a FilterExpr [20] follows
|
|
|
|
+
|
|
|
|
+ IsFunctionCall := False;
|
|
|
|
+ if (AScanner.NextToken = tkIdentifier) and
|
|
|
|
+ (AScanner.CurTokenString <> 'comment') and
|
|
|
|
+ (AScanner.CurTokenString <> 'text') and
|
|
|
|
+ (AScanner.CurTokenString <> 'processing-instruction') and
|
|
|
|
+ (AScanner.CurTokenString <> 'node') then
|
|
|
|
+ begin
|
|
|
|
+ ScannerState := AScanner.SaveState;
|
|
|
|
+ if AScanner.NextToken = tkLeftBracket then
|
|
|
|
+ IsFunctionCall := True;
|
|
|
|
+ AScanner.RestoreState(ScannerState);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ if IsFunctionCall or (AScanner.CurToken in
|
|
|
|
+ [tkDollar, tkLeftBracket, tkString, tkNumber]) then
|
|
|
|
+ begin
|
|
|
|
+ // second, third or fourth case of [19]
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ Result := ParseFilterExpr;
|
|
|
|
+ // !!!: Doesn't handle "/" or "//" plus RelativeLocationPath yet!
|
|
|
|
+ end else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ Result := ParseLocationPath;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseFilterExpr: TXPathExprNode; // [20]
|
|
|
|
+ var
|
|
|
|
+ IsFirst: Boolean;
|
|
|
|
+ begin
|
|
|
|
+ Result := ParsePrimaryExpr;
|
|
|
|
+ // Parse predicates
|
|
|
|
+ IsFirst := True;
|
|
|
|
+ while AScanner.NextToken = tkLeftSquareBracket do
|
|
|
|
+ begin
|
|
|
|
+ if IsFirst then
|
|
|
|
+ begin
|
|
|
|
+ Result := TXPathFilterNode.Create(Result);
|
|
|
|
+ IsFirst := False;
|
|
|
|
+ end;
|
|
|
|
+ TXPathFilterNode(Result).FPredicates.Add(ParseOrExpr);
|
|
|
|
+ if AScanner.NextToken <> tkRightSquareBracket then
|
|
|
|
+ Error(SParserExpectedRightSquareBracket);
|
|
|
|
+ end;
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseOrExpr: TXPathExprNode; // [21]
|
|
|
|
+ begin
|
|
|
|
+ Result := ParseAndExpr;
|
|
|
|
+ while True do
|
|
|
|
+ if (AScanner.NextToken = tkIdentifier) and
|
|
|
|
+ (AScanner.CurTokenString = 'or') then
|
|
|
|
+ Result := TXPathBooleanOpNode.Create(opOr, Result, ParseAndExpr)
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseAndExpr: TXPathExprNode; // [22]
|
|
|
|
+ begin
|
|
|
|
+ Result := ParseEqualityExpr;
|
|
|
|
+ while True do
|
|
|
|
+ if (AScanner.NextToken = tkIdentifier) and
|
|
|
|
+ (AScanner.CurTokenString = 'and') then
|
|
|
|
+ Result := TXPathBooleanOpNode.Create(opAnd, Result, ParseEqualityExpr)
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseEqualityExpr: TXPathExprNode; // [23]
|
|
|
|
+ begin
|
|
|
|
+ Result := ParseRelationalExpr;
|
|
|
|
+ while True do
|
|
|
|
+ case AScanner.NextToken of
|
|
|
|
+ tkEqual:
|
|
|
|
+ Result := TXPathBooleanOpNode.Create(opEqual, Result,
|
|
|
|
+ ParseRelationalExpr);
|
|
|
|
+ tkNotEqual:
|
|
|
|
+ Result := TXPathBooleanOpNode.Create(opNotEqual, Result,
|
|
|
|
+ ParseRelationalExpr);
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseRelationalExpr: TXPathExprNode; // [24]
|
|
|
|
+ begin
|
|
|
|
+ Result := ParseAdditiveExpr;
|
|
|
|
+ while True do
|
|
|
|
+ case AScanner.NextToken of
|
|
|
|
+ tkLess:
|
|
|
|
+ Result := TXPathBooleanOpNode.Create(opLess, Result,
|
|
|
|
+ ParseAdditiveExpr);
|
|
|
|
+ tkLessEqual:
|
|
|
|
+ Result := TXPathBooleanOpNode.Create(opLessEqual, Result,
|
|
|
|
+ ParseAdditiveExpr);
|
|
|
|
+ tkGreater:
|
|
|
|
+ Result := TXPathBooleanOpNode.Create(opGreater, Result,
|
|
|
|
+ ParseAdditiveExpr);
|
|
|
|
+ tkGreaterEqual:
|
|
|
|
+ Result := TXPathBooleanOpNode.Create(opGreaterEqual, Result,
|
|
|
|
+ ParseAdditiveExpr);
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseAdditiveExpr: TXPathExprNode; // [25]
|
|
|
|
+ begin
|
|
|
|
+ Result := ParseMultiplicativeExpr;
|
|
|
|
+ while True do
|
|
|
|
+ case AScanner.NextToken of
|
|
|
|
+ tkPlus:
|
|
|
|
+ Result := TXPathMathOpNode.Create(opAdd, Result,
|
|
|
|
+ ParseMultiplicativeExpr);
|
|
|
|
+ tkMinus:
|
|
|
|
+ Result := TXPathMathOpNode.Create(opSubtract, Result,
|
|
|
|
+ ParseMultiplicativeExpr);
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseMultiplicativeExpr: TXPathExprNode; // [26]
|
|
|
|
+ begin
|
|
|
|
+ Result := ParseUnaryExpr;
|
|
|
|
+ while True do
|
|
|
|
+ case AScanner.NextToken of
|
|
|
|
+ tkAsterisk:
|
|
|
|
+ Result := TXPathMathOpNode.Create(opMultiply, Result,
|
|
|
|
+ ParseUnaryExpr);
|
|
|
|
+ tkIdentifier:
|
|
|
|
+ if AScanner.CurTokenString = 'div' then
|
|
|
|
+ Result := TXPathMathOpNode.Create(opDivide, Result,
|
|
|
|
+ ParseUnaryExpr)
|
|
|
|
+ else if AScanner.CurTokenString = 'mod' then
|
|
|
|
+ Result := TXPathMathOpNode.Create(opMod, Result,
|
|
|
|
+ ParseUnaryExpr)
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function ParseUnaryExpr: TXPathExprNode; // [27]
|
|
|
|
+ var
|
|
|
|
+ NegCount: Integer;
|
|
|
|
+ begin
|
|
|
|
+ NegCount := 0;
|
|
|
|
+ while AScanner.NextToken = tkMinus do
|
|
|
|
+ Inc(NegCount);
|
|
|
|
+ AScanner.UngetToken;
|
|
|
|
+
|
|
|
|
+ Result := ParseUnionExpr;
|
|
|
|
+
|
|
|
|
+ if Odd(NegCount) then
|
|
|
|
+ Result := TXPathNegationNode.Create(Result);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+begin
|
|
|
|
+ inherited Create;
|
|
|
|
+ FRootNode := ParseOrExpr;
|
|
|
|
+ if CompleteExpression and (AScanner.NextToken <> tkEndOfStream) then
|
|
|
|
+ Error(SParserGarbageAfterExpression);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathExpression.Evaluate(AContextNode: TDOMNode): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ Environment: TXPathEnvironment;
|
|
|
|
+begin
|
|
|
|
+ Environment := TXPathEnvironment.Create;
|
|
|
|
+ try
|
|
|
|
+ Result := Evaluate(AContextNode, Environment);
|
|
|
|
+ finally
|
|
|
|
+ Environment.Free;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TXPathExpression.Evaluate(AContextNode: TDOMNode;
|
|
|
|
+ AEnvironment: TXPathEnvironment): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ Context: TXPathContext;
|
|
|
|
+begin
|
|
|
|
+ if Assigned(FRootNode) then
|
|
|
|
+ begin
|
|
|
|
+ Context := TXPathContext.Create(AContextNode, 1, 1);
|
|
|
|
+ try
|
|
|
|
+ Result := FRootNode.Evaluate(Context, AEnvironment);
|
|
|
|
+ finally
|
|
|
|
+ Context.Free;
|
|
|
|
+ end;
|
|
|
|
+ end else
|
|
|
|
+ Result := nil;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function EvaluateXPathExpression(const AExpressionString: DOMString;
|
|
|
|
+ AContextNode: TDOMNode): TXPathVariable;
|
|
|
|
+var
|
|
|
|
+ Scanner: TXPathScanner;
|
|
|
|
+ Expression: TXPathExpression;
|
|
|
|
+begin
|
|
|
|
+ Scanner := TXPathScanner.Create(AExpressionString);
|
|
|
|
+ try
|
|
|
|
+ Expression := TXPathExpression.Create(Scanner, True);
|
|
|
|
+ try
|
|
|
|
+ Result := Expression.Evaluate(AContextNode);
|
|
|
|
+ finally
|
|
|
|
+ Expression.Free;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ Scanner.Free;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+end.
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{
|
|
|
|
+ $Log$
|
|
|
|
+ Revision 1.1 2003-03-16 22:10:57 sg
|
|
|
|
+ * Added XPath unit
|
|
|
|
+
|
|
|
|
+}
|