fpc/fcl/xml/xpath.pp

2511 lines
66 KiB
ObjectPascal

{
$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, sg@freepascal.org
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: DOMString;
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.2 2003-03-17 14:32:15 peter
* Change ASText return to DOMString in implementation
Revision 1.1 2003/03/16 22:10:57 sg
* Added XPath unit
}