1{ 2 This file is part of the Free Component Library 3 4 Implementation of the XML Path Language (XPath) for Free Pascal 5 Copyright (c) 2000 - 2003 by 6 Areca Systems GmbH / Sebastian Guenther, sg@freepascal.org 7 8 See the file COPYING.FPC, included in this distribution, 9 for details about the copyright. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 15 **********************************************************************} 16 17{$MODE objfpc} 18{$H+} 19 20unit XPath; 21 22interface 23 24uses SysUtils, Classes, DOM; 25 26resourcestring 27 { XPath variables type names } 28 SNodeSet = 'node set'; 29 SBoolean = 'boolean'; 30 SNumber = 'number'; 31 SString = 'string'; 32 33 { Variable errors } 34 SVarNoConversion = 'Conversion from %s to %s not possible'; 35 36 { Scanner errors } 37 SScannerUnclosedString = 'String literal was not closed'; 38 SScannerInvalidChar = 'Invalid character'; 39 SScannerMalformedQName = 'Expected "*" or local part after colon'; 40 SScannerExpectedVarName = 'Expected variable name after "$"'; 41 42 { Parser errors } 43 SParserExpectedLeftBracket = 'Expected "("'; 44 SParserExpectedRightBracket = 'Expected ")"'; 45 SParserBadAxisName = 'Invalid axis name'; 46 SParserBadNodeType = 'Invalid node type'; 47 SParserExpectedRightSquareBracket = 'Expected "]" after predicate'; 48 SParserInvalidPrimExpr = 'Invalid primary expression'; 49 SParserGarbageAfterExpression = 'Unrecognized input after expression'; 50 SParserInvalidNodeTest = 'Invalid node test (syntax error)'; 51 52 53 { Evaluation errors } 54 SEvalUnknownFunction = 'Unknown function: "%s"'; 55 SEvalUnknownVariable = 'Unknown variable: "%s"'; 56 SEvalInvalidArgCount = 'Invalid number of function arguments'; 57 58type 59 60 TXPathContext = class; 61 TXPathEnvironment = class; 62 TXPathVariable = class; 63 64 65{ XPath lexical scanner } 66 67 TXPathToken = ( // [28] - [38] 68 tkInvalid, 69 tkEndOfStream, 70 tkIdentifier, 71 tkNSNameTest, // NCName:* 72 tkString, 73 tkNumber, 74 tkVariable, // $QName 75 tkLeftBracket, // "(" 76 tkRightBracket, // ")" 77 tkAsterisk, // "*" 78 tkPlus, // "+" 79 tkComma, // "," 80 tkMinus, // "-" 81 tkDot, // "." 82 tkDotDot, // ".." 83 tkSlash, // "/" 84 tkSlashSlash, // "//" 85 tkColonColon, // "::" 86 tkLess, // "<" 87 tkLessEqual, // "<=" 88 tkEqual, // "=" 89 tkNotEqual, // "!=" 90 tkGreater, // ">" 91 tkGreaterEqual, // ">=" 92 tkAt, // "@" 93 tkLeftSquareBracket, // "[" 94 tkRightSquareBracket, // "]" 95 tkPipe // "|" 96 ); 97 98 TXPathKeyword = ( 99 // axis names 100 xkNone, xkAncestor, xkAncestorOrSelf, xkAttribute, xkChild, 101 xkDescendant, xkDescendantOrSelf, xkFollowing, xkFollowingSibling, 102 xkNamespace, xkParent, xkPreceding, xkPrecedingSibling, xkSelf, 103 // node tests 104 xkComment, xkText, xkProcessingInstruction, xkNode, 105 // operators 106 xkAnd, xkOr, xkDiv, xkMod, 107 // standard functions 108 xkLast, xkPosition, xkCount, xkId, xkLocalName, xkNamespaceUri, 109 xkName, xkString, xkConcat, xkStartsWith, xkContains, 110 xkSubstringBefore, xkSubstringAfter, xkSubstring, 111 xkStringLength, xkNormalizeSpace, xkTranslate, xkBoolean, 112 xkNot, xkTrue, xkFalse, xkLang, xkNumber, xkSum, xkFloor, 113 xkCeiling, xkRound 114 ); 115 116{ XPath expression parse tree } 117 118 TXPathExprNode = class 119 protected 120 function EvalPredicate(AContext: TXPathContext; 121 AEnvironment: TXPathEnvironment): Boolean; 122 public 123 function Evaluate(AContext: TXPathContext; 124 AEnvironment: TXPathEnvironment): TXPathVariable; virtual; abstract; 125 end; 126 127 TXPathNodeArray = array of TXPathExprNode; 128 129 TXPathConstantNode = class(TXPathExprNode) 130 private 131 FValue: TXPathVariable; 132 public 133 constructor Create(AValue: TXPathVariable); 134 destructor Destroy; override; 135 function Evaluate(AContext: TXPathContext; 136 AEnvironment: TXPathEnvironment): TXPathVariable; override; 137 end; 138 139 140 TXPathVariableNode = class(TXPathExprNode) 141 private 142 FName: DOMString; 143 public 144 constructor Create(const AName: DOMString); 145 function Evaluate(AContext: TXPathContext; 146 AEnvironment: TXPathEnvironment): TXPathVariable; override; 147 end; 148 149 150 TXPathFunctionNode = class(TXPathExprNode) 151 private 152 FName: DOMString; 153 FArgs: TXPathNodeArray; 154 public 155 constructor Create(const AName: DOMString; const Args: TXPathNodeArray); 156 destructor Destroy; override; 157 function Evaluate(AContext: TXPathContext; 158 AEnvironment: TXPathEnvironment): TXPathVariable; override; 159 end; 160 161 162 TXPathNegationNode = class(TXPathExprNode) 163 private 164 FOperand: TXPathExprNode; 165 public 166 constructor Create(AOperand: TXPathExprNode); 167 destructor Destroy; override; 168 function Evaluate(AContext: TXPathContext; 169 AEnvironment: TXPathEnvironment): TXPathVariable; override; 170 end; 171 172 // common ancestor for binary operations 173 174 TXPathBinaryNode = class(TXPathExprNode) 175 protected 176 FOperand1, FOperand2: TXPathExprNode; 177 public 178 destructor Destroy; override; 179 end; 180 181 // Node for (binary) mathematical operation 182 183 TXPathMathOp = (opAdd, opSubtract, opMultiply, opDivide, opMod); 184 185 TXPathMathOpNode = class(TXPathBinaryNode) 186 private 187 FOperator: TXPathMathOp; 188 public 189 constructor Create(AOperator: TXPathMathOp; 190 AOperand1, AOperand2: TXPathExprNode); 191 function Evaluate(AContext: TXPathContext; 192 AEnvironment: TXPathEnvironment): TXPathVariable; override; 193 end; 194 195 // Node for comparison operations 196 197 TXPathCompareOp = (opEqual, opNotEqual, opLess, opLessEqual, opGreater, 198 opGreaterEqual); 199 200 TXPathCompareNode = class(TXPathBinaryNode) 201 private 202 FOperator: TXPathCompareOp; 203 public 204 constructor Create(AOperator: TXPathCompareOp; 205 AOperand1, AOperand2: TXPathExprNode); 206 function Evaluate(AContext: TXPathContext; 207 AEnvironment: TXPathEnvironment): TXPathVariable; override; 208 end; 209 210 211 // Node for boolean operations (and, or) 212 213 TXPathBooleanOp = (opOr, opAnd); 214 215 TXPathBooleanOpNode = class(TXPathBinaryNode) 216 private 217 FOperator: TXPathBooleanOp; 218 public 219 constructor Create(AOperator: TXPathBooleanOp; 220 AOperand1, AOperand2: TXPathExprNode); 221 function Evaluate(AContext: TXPathContext; 222 AEnvironment: TXPathEnvironment): TXPathVariable; override; 223 end; 224 225 226 // Node for unions (see [18]) 227 228 TXPathUnionNode = class(TXPathBinaryNode) 229 public 230 constructor Create(AOperand1, AOperand2: TXPathExprNode); 231 function Evaluate(AContext: TXPathContext; 232 AEnvironment: TXPathEnvironment): TXPathVariable; override; 233 end; 234 235 236 TNodeSet = TFPList; 237 238 // Filter node (for [20]) 239 240 TXPathFilterNode = class(TXPathExprNode) 241 private 242 FLeft: TXPathExprNode; 243 FPredicates: TXPathNodeArray; 244 procedure ApplyPredicates(Nodes: TNodeSet; AEnvironment: TXPathEnvironment); 245 public 246 constructor Create(AExpr: TXPathExprNode); 247 destructor Destroy; override; 248 function Evaluate(AContext: TXPathContext; 249 AEnvironment: TXPathEnvironment): TXPathVariable; override; 250 end; 251 252 253 // Node for location paths 254 255 TAxis = (axisInvalid, axisAncestor, axisAncestorOrSelf, axisAttribute, 256 axisChild, axisDescendant, axisDescendantOrSelf, axisFollowing, 257 axisFollowingSibling, axisNamespace, axisParent, axisPreceding, 258 axisPrecedingSibling, axisSelf, axisRoot); 259 260 TNodeTestType = (ntAnyPrincipal, ntName, ntTextNode, 261 ntCommentNode, ntPINode, ntAnyNode); 262 263 TStep = class(TXPathFilterNode) 264 private 265 procedure SelectNodes(ANode: TDOMNode; out ResultNodes: TNodeSet); 266 public 267 Axis: TAxis; 268 NodeTestType: TNodeTestType; 269 NodeTestString: DOMString; 270 NSTestString: DOMString; 271 constructor Create(aAxis: TAxis; aTest: TNodeTestType); 272 function Evaluate(AContext: TXPathContext; 273 AEnvironment: TXPathEnvironment): TXPathVariable; override; 274 end; 275 276{ Exceptions } 277 278 EXPathEvaluationError = class(Exception); 279 280 procedure EvaluationError(const Msg: String); 281 procedure EvaluationError(const Msg: String; const Args: array of const); 282 283 284type 285 286{ XPath variables and results classes } 287 288 TXPathVariable = class 289 protected 290 FRefCount: Integer; 291 procedure Error(const Msg: String; const Args: array of const); 292 public 293 class function TypeName: String; virtual; abstract; 294 procedure Release; 295 function AsNodeSet: TNodeSet; virtual; 296 function AsBoolean: Boolean; virtual; abstract; 297 function AsNumber: Extended; virtual; abstract; 298 function AsText: DOMString; virtual; abstract; 299 end; 300 301 TXPathNodeSetVariable = class(TXPathVariable) 302 private 303 FValue: TNodeSet; 304 public 305 constructor Create(AValue: TNodeSet); 306 destructor Destroy; override; 307 class function TypeName: String; override; 308 function AsNodeSet: TNodeSet; override; 309 function AsText: DOMString; override; 310 function AsBoolean: Boolean; override; 311 function AsNumber: Extended; override; 312 property Value: TNodeSet read FValue; 313 end; 314 315 TXPathBooleanVariable = class(TXPathVariable) 316 private 317 FValue: Boolean; 318 public 319 constructor Create(AValue: Boolean); 320 class function TypeName: String; override; 321 function AsBoolean: Boolean; override; 322 function AsNumber: Extended; override; 323 function AsText: DOMString; override; 324 property Value: Boolean read FValue; 325 end; 326 327 TXPathNumberVariable = class(TXPathVariable) 328 private 329 FValue: Extended; 330 public 331 constructor Create(AValue: Extended); 332 class function TypeName: String; override; 333 function AsBoolean: Boolean; override; 334 function AsNumber: Extended; override; 335 function AsText: DOMString; override; 336 property Value: Extended read FValue; 337 end; 338 339 TXPathStringVariable = class(TXPathVariable) 340 private 341 FValue: DOMString; 342 public 343 constructor Create(const AValue: DOMString); 344 class function TypeName: String; override; 345 function AsBoolean: Boolean; override; 346 function AsNumber: Extended; override; 347 function AsText: DOMString; override; 348 property Value: DOMString read FValue; 349 end; 350 351 TXPathNSResolver = class 352 protected 353 FNode: TDOMNode; 354 public 355 constructor Create(aNode: TDOMNode); 356 function LookupNamespaceURI(const aPrefix: DOMString): DOMString; virtual; 357 end; 358 359{ XPath lexical scanner } 360 361 TXPathScanner = class 362 private 363 FExpressionString, FCurData: DOMPChar; 364 FCurToken: TXPathToken; 365 FCurTokenString: DOMString; 366 FTokenStart: DOMPChar; 367 FTokenLength: Integer; 368 FPrefixLength: Integer; 369 FTokenId: TXPathKeyword; 370 FResolver: TXPathNSResolver; 371 procedure Error(const Msg: String); 372 procedure ParsePredicates(var Dest: TXPathNodeArray); 373 function ParseStep: TStep; // [4] 374 function ParseNodeTest(axis: TAxis): TStep; // [7] 375 function ParsePrimaryExpr: TXPathExprNode; // [15] 376 function ParseFunctionCall: TXPathExprNode; // [16] 377 function ParseUnionExpr: TXPathExprNode; // [18] 378 function ParsePathExpr: TXPathExprNode; // [19] 379 function ParseFilterExpr: TXPathExprNode; // [20] 380 function ParseOrExpr: TXPathExprNode; // [21] 381 function ParseAndExpr: TXPathExprNode; // [22] 382 function ParseEqualityExpr: TXPathExprNode; // [23] 383 function ParseRelationalExpr: TXPathExprNode; // [24] 384 function ParseAdditiveExpr: TXPathExprNode; // [25] 385 function ParseMultiplicativeExpr: TXPathExprNode; // [26] 386 function ParseUnaryExpr: TXPathExprNode; // [27] 387 function GetToken: TXPathToken; 388 function ScanQName: Boolean; 389 public 390 constructor Create(const AExpressionString: DOMString); 391 function NextToken: TXPathToken; 392 function PeekToken: TXPathToken; 393 function SkipToken(tok: TXPathToken): Boolean; 394 property CurToken: TXPathToken read FCurToken; 395 property CurTokenString: DOMString read FCurTokenString; 396 end; 397 398 399{ XPath context } 400 401 TXPathContext = class 402 public 403 ContextNode: TDOMNode; 404 ContextPosition: Integer; 405 ContextSize: Integer; 406 407 constructor Create(AContextNode: TDOMNode; 408 AContextPosition, AContextSize: Integer); 409 end; 410 411 412{ XPath environments (not defined in XPath standard: an environment contains 413 the variables and functions, which are part of the context in the official 414 standard). } 415 416 TXPathVarList = TFPList; 417 418 TXPathFunction = function(Context: TXPathContext; Args: TXPathVarList): 419 TXPathVariable of object; 420 421 TXPathEnvironment = class 422 private 423 FFunctions: TFPList; 424 FVariables: TFPList; 425 function GetFunctionCount: Integer; 426 function GetVariableCount: Integer; 427 function GetFunction(Index: Integer): TXPathFunction; 428 function GetFunction(const AName: String): TXPathFunction; 429 function GetVariable(Index: Integer): TXPathVariable; 430 function GetVariable(const AName: String): TXPathVariable; 431 protected 432 // XPath Core Function Library: 433 function xpLast(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 434 function xpPosition(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 435 function xpCount(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 436 function xpId(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 437 function xpLocalName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 438 function xpNamespaceURI(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 439 function xpName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 440 function xpString(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 441 function xpConcat(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 442 function xpStartsWith(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 443 function xpContains(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 444 function xpSubstringBefore(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 445 function xpSubstringAfter(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 446 function xpSubstring(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 447 function xpStringLength(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 448 function xpNormalizeSpace(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 449 function xpTranslate(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 450 function xpBoolean(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 451 function xpNot(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 452 function xpTrue(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 453 function xpFalse(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 454 function xpLang(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 455 function xpNumber(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 456 function xpSum(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 457 function xpFloor(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 458 function xpCeiling(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 459 function xpRound(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 460 public 461 constructor Create; 462 destructor Destroy; override; 463 function GetFunctionIndex(const AName: String): Integer; 464 function GetVariableIndex(const AName: String): Integer; 465 procedure AddFunction(const AName: String; AFunction: TXPathFunction); 466 procedure AddVariable(const AName: String; AVariable: TXPathVariable); 467 procedure RemoveFunction(Index: Integer); 468 procedure RemoveFunction(const AName: String); 469 procedure RemoveVariable(Index: Integer); 470 procedure RemoveVariable(const AName: String); 471 property FunctionCount: Integer read GetFunctionCount; 472 property VariableCount: Integer read GetVariableCount; 473 property Functions[Index: Integer]: TXPathFunction read GetFunction; 474 property FunctionsByName[const AName: String]: TXPathFunction 475 read GetFunction; 476 property Variables[Index: Integer]: TXPathVariable read GetVariable; 477 property VariablesByName[const AName: String]: TXPathVariable read GetVariable; 478 end; 479 480 481{ XPath expressions } 482 483 TXPathExpression = class 484 private 485 FRootNode: TXPathExprNode; 486 public 487 { CompleteExpresion specifies wether the parser should check for gargabe 488 after the recognised part. True => Throw exception if there is garbage } 489 constructor Create(AScanner: TXPathScanner; CompleteExpression: Boolean; 490 AResolver: TXPathNSResolver = nil); 491 destructor Destroy; override; 492 function Evaluate(AContextNode: TDOMNode): TXPathVariable; 493 function Evaluate(AContextNode: TDOMNode; 494 AEnvironment: TXPathEnvironment): TXPathVariable; 495 end; 496 497 498function EvaluateXPathExpression(const AExpressionString: DOMString; 499 AContextNode: TDOMNode; AResolver: TXPathNSResolver = nil): TXPathVariable; 500 501 502// =================================================================== 503// =================================================================== 504 505implementation 506 507uses Math, xmlutils; 508 509{$i xpathkw.inc} 510 511const 512 AxisNameKeywords = [xkAncestor..xkSelf]; 513 AxisNameMap: array[xkAncestor..xkSelf] of TAxis = ( 514 axisAncestor, axisAncestorOrSelf, axisAttribute, axisChild, 515 axisDescendant, axisDescendantOrSelf, axisFollowing, 516 axisFollowingSibling, axisNamespace, axisParent, axisPreceding, 517 axisPrecedingSibling, axisSelf 518 ); 519 NodeTestKeywords = [xkComment..xkNode]; 520 NodeTestMap: array[xkComment..xkNode] of TNodeTestType = ( 521 ntCommentNode, ntTextNode, ntPINode, ntAnyNode 522 ); 523 524 FunctionKeywords = [xkLast..xkRound]; 525 526{ Helper functions } 527 528function NodeToText(Node: TDOMNode): DOMString; 529var 530 Child: TDOMNode; 531begin 532 case Node.NodeType of 533 DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE{, ELEMENT_NODE}: 534 begin 535 SetLength(Result, 0); 536 Child := Node.FirstChild; 537 while Assigned(Child) do 538 begin 539 if Result <> '' then 540 Result := Result + LineEnding; 541 Result := Result + NodeToText(Child); 542 Child := Child.NextSibling; 543 end; 544 end; 545 ELEMENT_NODE: 546 Result := Node.TextContent; 547 ATTRIBUTE_NODE, PROCESSING_INSTRUCTION_NODE, COMMENT_NODE, TEXT_NODE, 548 CDATA_SECTION_NODE, ENTITY_REFERENCE_NODE: 549 Result := Node.NodeValue; 550 end; 551 // !!!: What to do with 'namespace nodes'? 552end; 553 554function StrToNumber(const s: DOMString): Extended; 555var 556 Code: Integer; 557begin 558 Val(s, Result, Code); 559{$push} 560{$r-,q-} 561 if Code <> 0 then 562 Result := NaN; 563{$pop} 564end; 565 566procedure TranslateWideString(var S: DOMString; const SrcPat, DstPat: DOMString); 567var 568 I, J, L: Integer; 569 P, Start: DOMPChar; 570begin 571 UniqueString(S); 572 L := Length(DstPat); 573 P := DOMPChar(S); 574 if Length(SrcPat) > L then // may remove some chars 575 begin 576 Start := P; 577 for I := 1 to Length(S) do 578 begin 579 J := Pos(S[I], SrcPat); 580 if J > 0 then 581 begin 582 if J <= L then 583 begin 584 P^ := DstPat[J]; 585 Inc(P); 586 end; 587 end 588 else 589 begin 590 P^ := S[I]; 591 Inc(P); 592 end; 593 end; 594 SetLength(S, P-Start); 595 end 596 else // no char removal possible 597 for I := 1 to Length(S) do 598 begin 599 J := Pos(S[I], SrcPat); 600 if J > 0 then 601 P^ := DstPat[J] 602 else 603 P^ := S[I]; 604 Inc(P); 605 end; 606end; 607 608function GetNodeLanguage(aNode: TDOMNode): DOMString; 609var 610 Attr: TDomAttr; 611begin 612 Result := ''; 613 if aNode = nil then 614 Exit; 615 case aNode.NodeType of 616 ELEMENT_NODE: begin 617 Attr := TDomElement(aNode).GetAttributeNode('xml:lang'); 618 if Assigned(Attr) then 619 Result := Attr.Value 620 else 621 Result := GetNodeLanguage(aNode.ParentNode); 622 end; 623 TEXT_NODE, CDATA_SECTION_NODE, ENTITY_REFERENCE_NODE, 624 PROCESSING_INSTRUCTION_NODE, COMMENT_NODE: 625 Result := GetNodeLanguage(aNode.ParentNode); 626 ATTRIBUTE_NODE: 627 Result := GetNodeLanguage(TDOMAttr(aNode).OwnerElement); 628 end; 629end; 630 631procedure AddNodes(var Dst: TXPathNodeArray; const Src: array of TXPathExprNode; 632 var Count: Integer); 633var 634 L: Integer; 635begin 636 if Count > 0 then 637 begin 638 L := Length(Dst); 639 SetLength(Dst, L + Count); 640 Move(Src[0], Dst[L], Count*sizeof(TObject)); 641 Count := 0; 642 end; 643end; 644 645{ XPath parse tree classes } 646 647function TXPathExprNode.EvalPredicate(AContext: TXPathContext; 648 AEnvironment: TXPathEnvironment): Boolean; 649var 650 resvar: TXPathVariable; 651begin 652 resvar := Evaluate(AContext, AEnvironment); 653 try 654 if resvar.InheritsFrom(TXPathNumberVariable) then 655 Result := resvar.AsNumber = AContext.ContextPosition // TODO: trunc/round? 656 else 657 Result := resvar.AsBoolean; 658 finally 659 resvar.Release; 660 end; 661end; 662 663constructor TXPathConstantNode.Create(AValue: TXPathVariable); 664begin 665 inherited Create; 666 FValue := AValue; 667end; 668 669destructor TXPathConstantNode.Destroy; 670begin 671 FValue.Release; 672 inherited Destroy; 673end; 674 675function TXPathConstantNode.Evaluate(AContext: TXPathContext; 676 AEnvironment: TXPathEnvironment): TXPathVariable; 677begin 678 Result := FValue; 679 Inc(Result.FRefCount); 680end; 681 682 683constructor TXPathVariableNode.Create(const AName: DOMString); 684begin 685 inherited Create; 686 FName := AName; 687end; 688 689function TXPathVariableNode.Evaluate(AContext: TXPathContext; 690 AEnvironment: TXPathEnvironment): TXPathVariable; 691begin 692 Result := AEnvironment.VariablesByName[FName]; 693 if not Assigned(Result) then 694 EvaluationError(SEvalUnknownVariable, [FName]); 695end; 696 697 698constructor TXPathFunctionNode.Create(const AName: DOMString; const Args: TXPathNodeArray); 699begin 700 inherited Create; 701 FName := AName; 702 FArgs := Args; 703end; 704 705destructor TXPathFunctionNode.Destroy; 706var 707 i: Integer; 708begin 709 for i := Low(FArgs) to High(FArgs) do 710 FArgs[i].Free; 711 inherited Destroy; 712end; 713 714function TXPathFunctionNode.Evaluate(AContext: TXPathContext; 715 AEnvironment: TXPathEnvironment): TXPathVariable; 716var 717 Fn: TXPathFunction; 718 Args: TXPathVarList; 719 i: Integer; 720begin 721 Fn := AEnvironment.FunctionsByName[FName]; 722 if not Assigned(Fn) then 723 EvaluationError(SEvalUnknownFunction, [FName]); 724 725 Args := TXPathVarList.Create; 726 try 727 for i := Low(FArgs) to High(FArgs) do 728 Args.Add(FArgs[i].Evaluate(AContext, AEnvironment)); 729 Result := Fn(AContext, Args); 730 for i := Low(FArgs) to High(FArgs) do 731 TXPathVariable(Args[i]).Release; 732 finally 733 Args.Free; 734 end; 735end; 736 737 738constructor TXPathNegationNode.Create(AOperand: TXPathExprNode); 739begin 740 inherited Create; 741 FOperand := AOperand; 742end; 743 744destructor TXPathNegationNode.Destroy; 745begin 746 FOperand.Free; 747 inherited Destroy; 748end; 749 750function TXPathNegationNode.Evaluate(AContext: TXPathContext; 751 AEnvironment: TXPathEnvironment): TXPathVariable; 752var 753 OpResult: TXPathVariable; 754begin 755 OpResult := FOperand.Evaluate(AContext, AEnvironment); 756 try 757 Result := TXPathNumberVariable.Create(-OpResult.AsNumber); 758 finally 759 OpResult.Release; 760 end; 761end; 762 763destructor TXPathBinaryNode.Destroy; 764begin 765 FOperand1.Free; 766 FOperand2.Free; 767 inherited Destroy; 768end; 769 770constructor TXPathMathOpNode.Create(AOperator: TXPathMathOp; 771 AOperand1, AOperand2: TXPathExprNode); 772begin 773 inherited Create; 774 FOperator := AOperator; 775 FOperand1 := AOperand1; 776 FOperand2 := AOperand2; 777end; 778 779function TXPathMathOpNode.Evaluate(AContext: TXPathContext; 780 AEnvironment: TXPathEnvironment): TXPathVariable; 781var 782 Op1Result, Op2Result: TXPathVariable; 783 Op1, Op2, NumberResult: Extended; 784begin 785 Op1Result := FOperand1.Evaluate(AContext, AEnvironment); 786 try 787 Op2Result := FOperand2.Evaluate(AContext, AEnvironment); 788 try 789 Op1 := Op1Result.AsNumber; 790 Op2 := Op2Result.AsNumber; 791 case FOperator of 792 opAdd: 793 NumberResult := Op1 + Op2; 794 opSubtract: 795 NumberResult := Op1 - Op2; 796 opMultiply: 797 NumberResult := Op1 * Op2; 798 opDivide: 799 NumberResult := Op1 / Op2; 800 opMod: if IsNan(Op1) or IsNan(Op2) then 801{$push} 802{$r-,q-} 803 NumberResult := NaN 804{$pop} 805 else 806 NumberResult := Trunc(Op1) mod Trunc(Op2); 807 end; 808 finally 809 Op2Result.Release; 810 end; 811 finally 812 Op1Result.Release; 813 end; 814 Result := TXPathNumberVariable.Create(NumberResult); 815end; 816 817const 818 reverse: array[TXPathCompareOp] of TXPathCompareOp = ( 819 opEqual, opNotEqual, 820 opGreaterEqual, //opLess 821 opGreater, //opLessEqual 822 opLessEqual, //opGreater 823 opLess //opGreaterEqual 824 ); 825 826function CmpNumbers(const n1, n2: Extended; op: TXPathCompareOp): Boolean; 827begin 828 result := (op = opNotEqual); 829 if IsNan(n1) or IsNan(n2) then 830 Exit; // NaNs are not equal 831 case op of 832 // TODO: should CompareValue() be used here? 833 opLess: result := n1 < n2; 834 opLessEqual: result := n1 <= n2; 835 opGreater: result := n1 > n2; 836 opGreaterEqual: result := n1 >= n2; 837 else 838 if IsInfinite(n1) or IsInfinite(n2) then 839 result := n1 = n2 840 else 841 result := SameValue(n1, n2); 842 result := result xor (op = opNotEqual); 843 end; 844end; 845 846function CmpStrings(const s1, s2: DOMString; op: TXPathCompareOp): Boolean; 847begin 848 case op of 849 opEqual: result := s1 = s2; 850 opNotEqual: result := s1 <> s2; 851 else 852 result := CmpNumbers(StrToNumber(s1), StrToNumber(s2), op); 853 end; 854end; 855 856function CmpNodesetWithString(ns: TNodeSet; const s: DOMString; op: TXPathCompareOp): Boolean; 857var 858 i: Integer; 859begin 860 Result := True; 861 for i := 0 to ns.Count - 1 do 862 begin 863 if CmpStrings(NodeToText(TDOMNode(ns[i])), s, op) then 864 exit; 865 end; 866 Result := False; 867end; 868 869function CmpNodesetWithNumber(ns: TNodeSet; const n: Extended; op: TXPathCompareOp): Boolean; 870var 871 i: Integer; 872begin 873 Result := True; 874 for i := 0 to ns.Count - 1 do 875 begin 876 if CmpNumbers(StrToNumber(NodeToText(TDOMNode(ns[i]))), n, op) then 877 exit; 878 end; 879 Result := False; 880end; 881 882function CmpNodesetWithBoolean(ns: TNodeSet; b: Boolean; op: TXPathCompareOp): Boolean; 883begin 884// TODO: handles only equality 885 result := ((ns.Count <> 0) = b) xor (op = opNotEqual); 886end; 887 888function CmpNodesets(ns1, ns2: TNodeSet; op: TXPathCompareOp): Boolean; 889var 890 i, j: Integer; 891 s: DOMString; 892begin 893 Result := True; 894 for i := 0 to ns1.Count - 1 do 895 begin 896 s := NodeToText(TDOMNode(ns1[i])); 897 for j := 0 to ns2.Count - 1 do 898 if CmpStrings(s, NodeToText(TDOMNode(ns2[j])), op) then 899 exit; 900 end; 901 Result := False; 902end; 903 904constructor TXPathCompareNode.Create(AOperator: TXPathCompareOp; 905 AOperand1, AOperand2: TXPathExprNode); 906begin 907 inherited Create; 908 FOperator := AOperator; 909 FOperand1 := AOperand1; 910 FOperand2 := AOperand2; 911end; 912 913function TXPathCompareNode.Evaluate(AContext: TXPathContext; 914 AEnvironment: TXPathEnvironment): TXPathVariable; 915var 916 Op1, Op2: TXPathVariable; 917 BoolResult: Boolean; 918 nsnum: Integer; 919begin 920 Op1 := FOperand1.Evaluate(AContext, AEnvironment); 921 try 922 Op2 := FOperand2.Evaluate(AContext, AEnvironment); 923 try 924 nsnum := ord(Op1 is TXPathNodeSetVariable) or 925 (ord(Op2 is TXPathNodeSetVariable) shl 1); 926 case nsnum of 927 0: begin // neither op is a nodeset 928 if (FOperator in [opEqual, opNotEqual]) then 929 begin 930 if (Op1 is TXPathBooleanVariable) or (Op2 is TXPathBooleanVariable) then 931 BoolResult := (Op1.AsBoolean = Op2.AsBoolean) xor (FOperator = opNotEqual) 932 else if (Op1 is TXPathNumberVariable) or (Op2 is TXPathNumberVariable) then 933 BoolResult := CmpNumbers(Op1.AsNumber, Op2.AsNumber, FOperator) 934 else 935 BoolResult := (Op1.AsText = Op2.AsText) xor (FOperator = opNotEqual); 936 end 937 else 938 BoolResult := CmpNumbers(Op1.AsNumber, Op2.AsNumber, FOperator); 939 end; 940 941 1: // Op1 is nodeset 942 if Op2 is TXPathNumberVariable then 943 BoolResult := CmpNodesetWithNumber(Op1.AsNodeSet, Op2.AsNumber, FOperator) 944 else if Op2 is TXPathStringVariable then 945 BoolResult := CmpNodesetWithString(Op1.AsNodeSet, Op2.AsText, FOperator) 946 else 947 BoolResult := CmpNodesetWithBoolean(Op1.AsNodeSet, Op2.AsBoolean, FOperator); 948 949 2: // Op2 is nodeset 950 if Op1 is TXPathNumberVariable then 951 BoolResult := CmpNodesetWithNumber(Op2.AsNodeSet, Op1.AsNumber, reverse[FOperator]) 952 else if Op1 is TXPathStringVariable then 953 BoolResult := CmpNodesetWithString(Op2.AsNodeSet, Op1.AsText, reverse[FOperator]) 954 else 955 BoolResult := CmpNodesetWithBoolean(Op2.AsNodeSet, Op1.AsBoolean, reverse[FOperator]); 956 957 else // both ops are nodesets 958 BoolResult := CmpNodesets(Op1.AsNodeSet, Op2.AsNodeSet, FOperator); 959 end; 960 finally 961 Op2.Release; 962 end; 963 finally 964 Op1.Release; 965 end; 966 Result := TXPathBooleanVariable.Create(BoolResult); 967end; 968 969constructor TXPathBooleanOpNode.Create(AOperator: TXPathBooleanOp; 970 AOperand1, AOperand2: TXPathExprNode); 971begin 972 inherited Create; 973 FOperator := AOperator; 974 FOperand1 := AOperand1; 975 FOperand2 := AOperand2; 976end; 977 978function TXPathBooleanOpNode.Evaluate(AContext: TXPathContext; 979 AEnvironment: TXPathEnvironment): TXPathVariable; 980var 981 res: Boolean; 982 Op1, Op2: TXPathVariable; 983begin 984 { don't evaluate second arg if result is determined by first one } 985 Op1 := FOperand1.Evaluate(AContext, AEnvironment); 986 try 987 res := Op1.AsBoolean; 988 finally 989 Op1.Release; 990 end; 991 if not (((FOperator = opAnd) and (not res)) or ((FOperator = opOr) and res)) then 992 begin 993 Op2 := FOperand2.Evaluate(AContext, AEnvironment); 994 try 995 case FOperator of 996 opAnd: res := res and Op2.AsBoolean; 997 opOr: res := res or Op2.AsBoolean; 998 end; 999 finally 1000 Op2.Release; 1001 end; 1002 end; 1003 Result := TXPathBooleanVariable.Create(res); 1004end; 1005 1006constructor TXPathUnionNode.Create(AOperand1, AOperand2: TXPathExprNode); 1007begin 1008 inherited Create; 1009 FOperand1 := AOperand1; 1010 FOperand2 := AOperand2; 1011end; 1012 1013function TXPathUnionNode.Evaluate(AContext: TXPathContext; 1014 AEnvironment: TXPathEnvironment): TXPathVariable; 1015var 1016 Op1Result, Op2Result: TXPathVariable; 1017 NodeSet, NodeSet2: TNodeSet; 1018 CurNode: Pointer; 1019 i: Integer; 1020begin 1021{ TODO: result must be sorted by document order, i.e. 'a|b' yields the 1022 same nodeset as 'b|a' } 1023 Op1Result := FOperand1.Evaluate(AContext, AEnvironment); 1024 try 1025 Op2Result := FOperand2.Evaluate(AContext, AEnvironment); 1026 try 1027 NodeSet := Op1Result.AsNodeSet; 1028 NodeSet2 := Op2Result.AsNodeSet; 1029 for i := 0 to NodeSet2.Count - 1 do 1030 begin 1031 CurNode := NodeSet2[i]; 1032 if NodeSet.IndexOf(CurNode) < 0 then 1033 NodeSet.Add(CurNode); 1034 end; 1035 finally 1036 Op2Result.Release; 1037 end; 1038 finally 1039 Result := Op1Result; 1040 end; 1041end; 1042 1043 1044constructor TXPathFilterNode.Create(AExpr: TXPathExprNode); 1045begin 1046 inherited Create; 1047 FLeft := AExpr; 1048end; 1049 1050destructor TXPathFilterNode.Destroy; 1051var 1052 i: Integer; 1053begin 1054 FLeft.Free; 1055 for i := 0 to High(FPredicates) do 1056 FPredicates[i].Free; 1057 inherited Destroy; 1058end; 1059 1060function TXPathFilterNode.Evaluate(AContext: TXPathContext; 1061 AEnvironment: TXPathEnvironment): TXPathVariable; 1062var 1063 NodeSet: TNodeSet; 1064begin 1065 Result := FLeft.Evaluate(AContext, AEnvironment); 1066 NodeSet := Result.AsNodeSet; 1067 ApplyPredicates(NodeSet, AEnvironment); 1068end; 1069 1070 1071constructor TStep.Create(aAxis: TAxis; aTest: TNodeTestType); 1072begin 1073 Axis := aAxis; 1074 NodeTestType := aTest; 1075end; 1076 1077procedure TStep.SelectNodes(ANode: TDOMNode; out ResultNodes: TNodeSet); 1078var 1079 Node, Node2: TDOMNode; 1080 Attr: TDOMNamedNodeMap; 1081 i: Integer; 1082 1083 procedure DoNodeTest(Node: TDOMNode); 1084 begin 1085 case NodeTestType of 1086 ntAnyPrincipal: 1087 // !!!: Probably this isn't ready for namespace support yet 1088 if (Axis <> axisAttribute) and 1089 (Node.NodeType <> ELEMENT_NODE) then 1090 exit; 1091 ntName: 1092 if NSTestString <> '' then 1093 begin 1094 if Node.namespaceURI <> NSTestString then 1095 exit; 1096 if (NodeTestString <> '') and (Node.localName <> NodeTestString) then 1097 exit; 1098 end 1099 else if Node.NodeName <> NodeTestString then 1100 exit; 1101 ntTextNode: 1102 if not Node.InheritsFrom(TDOMText) then 1103 exit; 1104 ntCommentNode: 1105 if Node.NodeType <> COMMENT_NODE then 1106 exit; 1107 ntPINode: 1108 if (Node.NodeType <> PROCESSING_INSTRUCTION_NODE) or 1109 ((NodeTestString <> '') and (Node.nodeName <> NodeTestString)) then 1110 exit; 1111 end; 1112 if ResultNodes.IndexOf(Node) < 0 then 1113 ResultNodes.Add(Node); 1114 end; 1115 1116 procedure AddDescendants(CurNode: TDOMNode); 1117 var 1118 Child: TDOMNode; 1119 begin 1120 Child := CurNode.FirstChild; 1121 while Assigned(Child) do 1122 begin 1123 DoNodeTest(Child); 1124 AddDescendants(Child); 1125 Child := Child.NextSibling; 1126 end; 1127 end; 1128 1129 procedure AddDescendantsReverse(CurNode: TDOMNode); 1130 var 1131 Child: TDOMNode; 1132 begin 1133 Child := CurNode.LastChild; 1134 while Assigned(Child) do 1135 begin 1136 AddDescendantsReverse(Child); 1137 DoNodeTest(Child); 1138 Child := Child.PreviousSibling; 1139 end; 1140 end; 1141 1142begin 1143 ResultNodes := TNodeSet.Create; 1144 case Axis of 1145 axisAncestor: 1146 begin 1147 // TODO: same check needed for XPATH_NAMESPACE_NODE 1148 if ANode.nodeType = ATTRIBUTE_NODE then 1149 Node := TDOMAttr(ANode).ownerElement 1150 else 1151 Node := ANode.ParentNode; 1152 while Assigned(Node) do 1153 begin 1154 DoNodeTest(Node); 1155 Node := Node.ParentNode; 1156 end; 1157 end; 1158 axisAncestorOrSelf: 1159 begin 1160 DoNodeTest(ANode); 1161 // TODO: same check needed for XPATH_NAMESPACE_NODE 1162 if ANode.nodeType = ATTRIBUTE_NODE then 1163 Node := TDOMAttr(ANode).ownerElement 1164 else 1165 Node := ANode.ParentNode; 1166 while Assigned(Node) do 1167 begin 1168 DoNodeTest(Node); 1169 Node := Node.ParentNode; 1170 end; 1171 end; 1172 axisAttribute: 1173 begin 1174 Attr := ANode.Attributes; 1175 if Assigned(Attr) then 1176 for i := 0 to Attr.Length - 1 do 1177 DoNodeTest(Attr[i]); 1178 end; 1179 axisChild: 1180 begin 1181 Node := ANode.FirstChild; 1182 while Assigned(Node) do 1183 begin 1184 DoNodeTest(Node); 1185 Node := Node.NextSibling; 1186 end; 1187 end; 1188 axisDescendant: 1189 AddDescendants(ANode); 1190 axisDescendantOrSelf: 1191 begin 1192 DoNodeTest(ANode); 1193 AddDescendants(ANode); 1194 end; 1195 axisFollowing: 1196 begin 1197 Node := ANode; 1198 repeat 1199 Node2 := Node.NextSibling; 1200 while Assigned(Node2) do 1201 begin 1202 DoNodeTest(Node2); 1203 AddDescendants(Node2); 1204 Node2 := Node2.NextSibling; 1205 end; 1206 Node := Node.ParentNode; 1207 until not Assigned(Node); 1208 end; 1209 axisFollowingSibling: 1210 begin 1211 Node := ANode.NextSibling; 1212 while Assigned(Node) do 1213 begin 1214 DoNodeTest(Node); 1215 Node := Node.NextSibling; 1216 end; 1217 end; 1218 {axisNamespace: !!!: Not supported yet} 1219 axisParent: 1220 if ANode.NodeType=ATTRIBUTE_NODE then 1221 begin 1222 if Assigned(TDOMAttr(ANode).OwnerElement) then 1223 DoNodeTest(TDOMAttr(ANode).OwnerElement); 1224 end 1225 else if Assigned(ANode.ParentNode) then 1226 DoNodeTest(ANode.ParentNode); 1227 axisPreceding: 1228 begin 1229 Node := ANode; 1230 repeat 1231 Node2 := Node.PreviousSibling; 1232 while Assigned(Node2) do 1233 begin 1234 AddDescendantsReverse(Node2); 1235 DoNodeTest(Node2); 1236 Node2 := Node2.PreviousSibling; 1237 end; 1238 Node := Node.ParentNode; 1239 until not Assigned(Node); 1240 end; 1241 axisPrecedingSibling: 1242 begin 1243 Node := ANode.PreviousSibling; 1244 while Assigned(Node) do 1245 begin 1246 DoNodeTest(Node); 1247 Node := Node.PreviousSibling; 1248 end; 1249 end; 1250 axisSelf: 1251 DoNodeTest(ANode); 1252 axisRoot: 1253 if ANode.nodeType = DOCUMENT_NODE then 1254 ResultNodes.Add(ANode) 1255 else 1256 ResultNodes.Add(ANode.ownerDocument); 1257 end; 1258end; 1259 1260{ Filter the nodes of this step using the predicates: The current 1261 node set is filtered, nodes not passing the filter are replaced 1262 by nil. After one filter has been applied, Nodes is packed, and 1263 the next filter will be processed. } 1264 1265procedure TXPathFilterNode.ApplyPredicates(Nodes: TNodeSet; AEnvironment: TXPathEnvironment); 1266var 1267 i, j: Integer; 1268 NewContext: TXPathContext; 1269begin 1270 for i := 0 to High(FPredicates) do 1271 begin 1272 NewContext := TXPathContext.Create(nil, 0, Nodes.Count); 1273 try 1274 for j := 0 to Nodes.Count - 1 do 1275 begin 1276 NewContext.ContextPosition := j+1; 1277 NewContext.ContextNode := TDOMNode(Nodes[j]); 1278 if not FPredicates[i].EvalPredicate(NewContext, AEnvironment) then 1279 Nodes[j] := nil; 1280 end; 1281 Nodes.Pack; 1282 finally 1283 NewContext.Free; 1284 end; 1285 end; 1286end; 1287 1288function TStep.Evaluate(AContext: TXPathContext; 1289 AEnvironment: TXPathEnvironment): TXPathVariable; 1290var 1291 ResultNodeSet: TNodeSet; 1292 LeftResult: TXPathVariable; 1293 i: Integer; 1294 1295 procedure EvaluateStep(AContextNode: TDOMNode); 1296 var 1297 StepNodes: TFPList; 1298 Node: TDOMNode; 1299 i: Integer; 1300 begin 1301 SelectNodes(AContextNode, StepNodes); 1302 try 1303 ApplyPredicates(StepNodes, AEnvironment); 1304 if Axis in [axisAncestor, axisAncestorOrSelf, 1305 axisPreceding, axisPrecedingSibling] then 1306 for i := StepNodes.Count - 1 downto 0 do 1307 begin 1308 Node := TDOMNode(StepNodes[i]); 1309 if ResultNodeSet.IndexOf(Node) < 0 then 1310 ResultNodeSet.Add(Node); 1311 end 1312 else for i := 0 to StepNodes.Count - 1 do 1313 begin 1314 Node := TDOMNode(StepNodes[i]); 1315 if ResultNodeSet.IndexOf(Node) < 0 then 1316 ResultNodeSet.Add(Node); 1317 end; 1318 finally 1319 StepNodes.Free; 1320 end; 1321 end; 1322 1323begin 1324 ResultNodeSet := TNodeSet.Create; 1325 try 1326 if Assigned(FLeft) then 1327 begin 1328 LeftResult := FLeft.Evaluate(AContext, AEnvironment); 1329 try 1330 with LeftResult.AsNodeSet do 1331 for i := 0 to Count-1 do 1332 EvaluateStep(TDOMNode(Items[i])); 1333 finally 1334 LeftResult.Release; 1335 end; 1336 end 1337 else 1338 EvaluateStep(AContext.ContextNode); 1339 except 1340 ResultNodeSet.Free; 1341 raise; 1342 end; 1343 Result := TXPathNodeSetVariable.Create(ResultNodeSet); 1344end; 1345 1346{ Exceptions } 1347 1348procedure EvaluationError(const Msg: String); 1349begin 1350 raise EXPathEvaluationError.Create(Msg) at get_caller_addr(get_frame), get_caller_frame(get_frame); 1351end; 1352 1353procedure EvaluationError(const Msg: String; const Args: array of const); 1354begin 1355 raise EXPathEvaluationError.CreateFmt(Msg, Args) 1356 at get_caller_addr(get_frame), get_caller_frame(get_frame); 1357end; 1358 1359 1360{ TXPathVariable and derived classes} 1361 1362procedure TXPathVariable.Release; 1363begin 1364 if FRefCount <= 0 then 1365 Free 1366 else 1367 Dec(FRefCount); 1368end; 1369 1370function TXPathVariable.AsNodeSet: TNodeSet; 1371begin 1372 Error(SVarNoConversion, [TypeName, TXPathNodeSetVariable.TypeName]); 1373 Result := nil; 1374end; 1375 1376procedure TXPathVariable.Error(const Msg: String; const Args: array of const); 1377begin 1378 raise Exception.CreateFmt(Msg, Args) at get_caller_addr(get_frame), get_caller_frame(get_frame); 1379end; 1380 1381 1382constructor TXPathNodeSetVariable.Create(AValue: TNodeSet); 1383begin 1384 inherited Create; 1385 FValue := AValue; 1386end; 1387 1388destructor TXPathNodeSetVariable.Destroy; 1389begin 1390 FValue.Free; 1391 inherited Destroy; 1392end; 1393 1394class function TXPathNodeSetVariable.TypeName: String; 1395begin 1396 Result := SNodeSet; 1397end; 1398 1399function TXPathNodeSetVariable.AsNodeSet: TNodeSet; 1400begin 1401 Result := FValue; 1402end; 1403 1404function TXPathNodeSetVariable.AsText: DOMString; 1405begin 1406 if FValue.Count = 0 then 1407 Result := '' 1408 else 1409 Result := NodeToText(TDOMNode(FValue.First)); 1410end; 1411 1412function TXPathNodeSetVariable.AsBoolean: Boolean; 1413begin 1414 Result := FValue.Count <> 0; 1415end; 1416 1417function TXPathNodeSetVariable.AsNumber: Extended; 1418begin 1419 Result := StrToNumber(AsText); 1420end; 1421 1422constructor TXPathBooleanVariable.Create(AValue: Boolean); 1423begin 1424 inherited Create; 1425 FValue := AValue; 1426end; 1427 1428class function TXPathBooleanVariable.TypeName: String; 1429begin 1430 Result := SBoolean; 1431end; 1432 1433function TXPathBooleanVariable.AsBoolean: Boolean; 1434begin 1435 Result := FValue; 1436end; 1437 1438function TXPathBooleanVariable.AsNumber: Extended; 1439begin 1440 if FValue then 1441 Result := 1 1442 else 1443 Result := 0; 1444end; 1445 1446function TXPathBooleanVariable.AsText: DOMString; 1447begin 1448 if FValue then 1449 Result := 'true' // Do not localize! 1450 else 1451 Result := 'false'; // Do not localize! 1452end; 1453 1454 1455constructor TXPathNumberVariable.Create(AValue: Extended); 1456begin 1457 inherited Create; 1458 FValue := AValue; 1459end; 1460 1461class function TXPathNumberVariable.TypeName: String; 1462begin 1463 Result := SNumber; 1464end; 1465 1466function TXPathNumberVariable.AsBoolean: Boolean; 1467begin 1468 Result := not (IsNan(FValue) or IsZero(FValue)); 1469end; 1470 1471function TXPathNumberVariable.AsNumber: Extended; 1472begin 1473 Result := FValue; 1474end; 1475 1476function TXPathNumberVariable.AsText: DOMString; 1477var 1478 frec: TFloatRec; 1479 i, nd, reqlen: Integer; 1480 P: DOMPChar; 1481begin 1482 FloatToDecimal(frec, FValue, fvExtended, 17, 9999); 1483 1484 if frec.Exponent = -32768 then 1485 begin 1486 Result := 'NaN'; // do not localize 1487 Exit; 1488 end 1489 else if frec.Exponent = 32767 then 1490 begin 1491 if frec.Negative then 1492 Result := '-Infinity' // do not localize 1493 else 1494 Result := 'Infinity'; // do not localize 1495 Exit; 1496 end 1497 else if frec.Digits[0] = #0 then 1498 begin 1499 Result := '0'; 1500 Exit; 1501 end 1502 else 1503 begin 1504 nd := StrLen(@frec.Digits[0]); 1505 reqlen := nd + ord(frec.Negative); // maybe minus sign 1506 if frec.Exponent > nd then 1507 Inc(reqlen, frec.Exponent - nd) // add this much zeroes at the right 1508 else if frec.Exponent < nd then 1509 begin 1510 Inc(reqlen); // decimal point 1511 if frec.Exponent <= 0 then 1512 Inc(reqlen, 1 - frec.Exponent); // zeroes at the left + one more for the int part 1513 end; 1514 SetLength(Result, reqlen); 1515 P := DOMPChar(Result); 1516 if frec.Negative then 1517 begin 1518 P^ := '-'; 1519 Inc(P); 1520 end; 1521 if frec.Exponent <= 0 then // value less than 1, put zeroes at left 1522 begin 1523 for i := 0 to 1-frec.Exponent do 1524 P[i] := '0'; 1525 P[1] := '.'; 1526 for i := 0 to nd-1 do 1527 P[i+2-frec.Exponent] := WideChar(ord(frec.Digits[i])); 1528 end 1529 else if frec.Exponent > nd then // large integer, put zeroes at right 1530 begin 1531 for i := 0 to nd-1 do 1532 P[i] := WideChar(ord(frec.Digits[i])); 1533 for i := nd to reqlen-1-ord(frec.Negative) do 1534 P[i] := '0'; 1535 end 1536 else // 0 < exponent <= digits, insert decimal point into middle 1537 begin 1538 for i := 0 to frec.Exponent-1 do 1539 P[i] := WideChar(ord(frec.Digits[i])); 1540 if frec.Exponent < nd then 1541 begin 1542 P[frec.Exponent] := '.'; 1543 for i := frec.Exponent to nd-1 do 1544 P[i+1] := WideChar(ord(frec.Digits[i])); 1545 end; 1546 end; 1547 end; 1548end; 1549 1550 1551constructor TXPathStringVariable.Create(const AValue: DOMString); 1552begin 1553 inherited Create; 1554 FValue := AValue; 1555end; 1556 1557class function TXPathStringVariable.TypeName: String; 1558begin 1559 Result := SString; 1560end; 1561 1562function TXPathStringVariable.AsBoolean: Boolean; 1563begin 1564 Result := Length(FValue) > 0; 1565end; 1566 1567function TXPathStringVariable.AsNumber: Extended; 1568begin 1569 Result := StrToNumber(FValue); 1570end; 1571 1572function TXPathStringVariable.AsText: DOMString; 1573begin 1574 Result := FValue; 1575end; 1576 1577 1578{ XPath lexical scanner } 1579 1580constructor TXPathScanner.Create(const AExpressionString: DOMString); 1581begin 1582 inherited Create; 1583 FExpressionString := DOMPChar(AExpressionString); 1584 FCurData := FExpressionString; 1585 NextToken; 1586end; 1587 1588function TXPathScanner.PeekToken: TXPathToken; 1589var 1590 save: DOMPChar; 1591begin 1592 save := FCurData; 1593 Result := GetToken; 1594 FCurData := save; 1595end; 1596 1597function TXPathScanner.NextToken: TXPathToken; 1598begin 1599 Result := GetToken; 1600 FCurToken := Result; 1601 if Result in [tkIdentifier, tkNSNameTest, tkNumber, tkString, tkVariable] then 1602 SetString(FCurTokenString, FTokenStart, FTokenLength); 1603 if Result = tkIdentifier then 1604 FTokenId := LookupXPathKeyword(FTokenStart, FTokenLength) 1605 else 1606 FTokenId := xkNone; 1607end; 1608 1609function TXPathScanner.SkipToken(tok: TXPathToken): Boolean; { inline? } 1610begin 1611 Result := (FCurToken = tok); 1612 if Result then 1613 NextToken; 1614end; 1615 1616// TODO: no surrogate pairs/XML 1.1 support yet 1617function TXPathScanner.ScanQName: Boolean; 1618var 1619 p: DOMPChar; 1620begin 1621 FPrefixLength := 0; 1622 p := FCurData; 1623 repeat 1624 if (Byte(p^) in NamingBitmap[NamePages[hi(Word(p^))]]) then 1625 Inc(p) 1626 else 1627 begin 1628 // either the first char of name is bad (it may be a colon), 1629 // or a colon is not followed by a valid NameStartChar 1630 Result := False; 1631 Break; 1632 end; 1633 1634 while Byte(p^) in NamingBitmap[NamePages[$100+hi(Word(p^))]] do 1635 Inc(p); 1636 1637 Result := True; 1638 if (p^ <> ':') or (p[1] = ':') or (FPrefixLength > 0) then 1639 Break; 1640 // first colon, and not followed by another one -> remember its position 1641 FPrefixLength := p-FTokenStart; 1642 Inc(p); 1643 until False; 1644 FCurData := p; 1645 FTokenLength := p-FTokenStart; 1646end; 1647 1648function TXPathScanner.GetToken: TXPathToken; 1649 1650 procedure GetNumber(HasDot: Boolean); 1651 begin 1652 FTokenLength := 1; 1653 while ((FCurData[1] >= '0') and (FCurData[1] <= '9')) or ((FCurData[1] = '.') and not HasDot) do 1654 begin 1655 Inc(FCurData); 1656 Inc(FTokenLength); 1657 if FCurData[0] = '.' then 1658 HasDot := True; 1659 end; 1660 Result := tkNumber; 1661 end; 1662 1663var 1664 Delim: WideChar; 1665begin 1666 // Skip whitespace 1667 while (FCurData[0] < #255) and (char(ord(FCurData[0])) in [#9, #10, #13, ' ']) do 1668 Inc(FCurData); 1669 1670 FTokenStart := FCurData; 1671 FTokenLength := 0; 1672 Result := tkInvalid; 1673 1674 case FCurData[0] of 1675 #0: 1676 Result := tkEndOfStream; 1677 '!': 1678 if FCurData[1] = '=' then 1679 begin 1680 Inc(FCurData); 1681 Result := tkNotEqual; 1682 end; 1683 '"', '''': 1684 begin 1685 Delim := FCurData^; 1686 Inc(FCurData); 1687 FTokenStart := FCurData; 1688 while FCurData[0] <> Delim do 1689 begin 1690 if FCurData[0] = #0 then 1691 Error(SScannerUnclosedString); 1692 Inc(FCurData); 1693 end; 1694 FTokenLength := FCurData-FTokenStart; 1695 Result := tkString; 1696 end; 1697 '$': 1698 begin 1699 Inc(FCurData); 1700 Inc(FTokenStart); 1701 if ScanQName then 1702 Result := tkVariable 1703 else 1704 Error(SScannerExpectedVarName); 1705 Exit; 1706 end; 1707 '(': 1708 Result := tkLeftBracket; 1709 ')': 1710 Result := tkRightBracket; 1711 '*': 1712 Result := tkAsterisk; 1713 '+': 1714 Result := tkPlus; 1715 ',': 1716 Result := tkComma; 1717 '-': 1718 Result := tkMinus; 1719 '.': 1720 if FCurData[1] = '.' then 1721 begin 1722 Inc(FCurData); 1723 Result := tkDotDot; 1724 end else if (FCurData[1] >= '0') and (FCurData[1] <= '9') then 1725 GetNumber(True) 1726 else 1727 Result := tkDot; 1728 '/': 1729 if FCurData[1] = '/' then 1730 begin 1731 Inc(FCurData); 1732 Result := tkSlashSlash; 1733 end else 1734 Result := tkSlash; 1735 '0'..'9': 1736 GetNumber(False); 1737 ':': 1738 if FCurData[1] = ':' then 1739 begin 1740 Inc(FCurData); 1741 Result := tkColonColon; 1742 end; 1743 '<': 1744 if FCurData[1] = '=' then 1745 begin 1746 Inc(FCurData); 1747 Result := tkLessEqual; 1748 end else 1749 Result := tkLess; 1750 '=': 1751 Result := tkEqual; 1752 '>': 1753 if FCurData[1] = '=' then 1754 begin 1755 Inc(FCurData); 1756 Result := tkGreaterEqual; 1757 end else 1758 Result := tkGreater; 1759 '@': 1760 Result := tkAt; 1761 '[': 1762 Result := tkLeftSquareBracket; 1763 ']': 1764 Result := tkRightSquareBracket; 1765 '|': 1766 Result := tkPipe; 1767 else 1768 if ScanQName then 1769 begin 1770 Result := tkIdentifier; 1771 Exit; 1772 end 1773 else if FPrefixLength > 0 then 1774 begin 1775 if FCurData^ = '*' then 1776 begin 1777 Inc(FCurData); 1778 Dec(FTokenLength); // exclude ':' 1779 Result := tkNSNameTest; 1780 Exit; 1781 end 1782 else 1783 Error(SScannerMalformedQName); 1784 end; 1785 end; 1786 1787 if Result = tkInvalid then 1788 Error(SScannerInvalidChar); 1789 // We have processed at least one character now; eat it: 1790 if Result > tkEndOfStream then 1791 Inc(FCurData); 1792end; 1793 1794procedure TXPathScanner.Error(const Msg: String); 1795begin 1796 raise Exception.Create(Msg) at get_caller_addr(get_frame), get_caller_frame(get_frame); 1797end; 1798 1799procedure TXPathScanner.ParsePredicates(var Dest: TXPathNodeArray); 1800var 1801 Buffer: array[0..15] of TXPathExprNode; 1802 I: Integer; 1803begin 1804 I := 0; 1805 // accumulate nodes in local buffer, then add all at once 1806 // this reduces amount of ReallocMem's 1807 while SkipToken(tkLeftSquareBracket) do 1808 begin 1809 Buffer[I] := ParseOrExpr; 1810 Inc(I); 1811 if I > High(Buffer) then 1812 AddNodes(Dest, Buffer, I); // will reset I to zero 1813 if not SkipToken(tkRightSquareBracket) then 1814 Error(SParserExpectedRightSquareBracket); 1815 end; 1816 AddNodes(Dest, Buffer, I); 1817end; 1818 1819function TXPathScanner.ParseStep: TStep; // [4] 1820var 1821 Axis: TAxis; 1822begin 1823 if CurToken = tkDot then // [12] Abbreviated step, first case 1824 begin 1825 NextToken; 1826 Result := TStep.Create(axisSelf, ntAnyNode); 1827 end 1828 else if CurToken = tkDotDot then // [12] Abbreviated step, second case 1829 begin 1830 NextToken; 1831 Result := TStep.Create(axisParent, ntAnyNode); 1832 end 1833 else // Parse [5] AxisSpecifier 1834 begin 1835 if CurToken = tkAt then // [13] AbbreviatedAxisSpecifier 1836 begin 1837 Axis := axisAttribute; 1838 NextToken; 1839 end 1840 else if (CurToken = tkIdentifier) and (PeekToken = tkColonColon) then // [5] AxisName '::' 1841 begin 1842 if FTokenId in AxisNameKeywords then 1843 Axis := AxisNameMap[FTokenId] 1844 else 1845 Error(SParserBadAxisName); 1846 NextToken; // skip identifier and the '::' 1847 NextToken; 1848 end 1849 else 1850 Axis := axisChild; 1851 1852 Result := ParseNodeTest(Axis); 1853 ParsePredicates(Result.FPredicates); 1854 end; 1855end; 1856 1857function TXPathScanner.ParseNodeTest(Axis: TAxis): TStep; // [7] 1858var 1859 nodeType: TNodeTestType; 1860 nodeName: DOMString; 1861 nsURI: DOMString; 1862begin 1863 nodeName := ''; 1864 nsURI := ''; 1865 if CurToken = tkAsterisk then // [37] NameTest, first case 1866 begin 1867 nodeType := ntAnyPrincipal; 1868 NextToken; 1869 end 1870 else if CurToken = tkNSNameTest then // [37] NameTest, second case 1871 begin 1872 if Assigned(FResolver) then 1873 nsURI := FResolver.lookupNamespaceURI(CurTokenString); 1874 if nsURI = '' then 1875 // !! localization disrupted by DOM exception specifics 1876 raise EDOMNamespace.Create('TXPathScanner.ParseStep'); 1877 NextToken; 1878 nodeType := ntName; 1879 end 1880 else if CurToken = tkIdentifier then 1881 begin 1882 // Check for case [38] NodeType 1883 if PeekToken = tkLeftBracket then 1884 begin 1885 if FTokenId in NodeTestKeywords then 1886 begin 1887 nodeType := NodeTestMap[FTokenId]; 1888 if FTokenId = xkProcessingInstruction then 1889 begin 1890 NextToken; 1891 if NextToken = tkString then 1892 begin 1893 nodeName := CurTokenString; 1894 NextToken; 1895 end; 1896 end 1897 else 1898 begin 1899 NextToken; 1900 NextToken; 1901 end; 1902 if CurToken <> tkRightBracket then 1903 Error(SParserExpectedRightBracket); 1904 NextToken; 1905 end 1906 else 1907 Error(SParserBadNodeType); 1908 end 1909 else // [37] NameTest, third case 1910 begin 1911 nodeType := ntName; 1912 if FPrefixLength > 0 then 1913 begin 1914 if Assigned(FResolver) then 1915 nsURI := FResolver.lookupNamespaceURI(Copy(CurTokenString, 1, FPrefixLength)); 1916 if nsURI = '' then 1917 raise EDOMNamespace.Create('TXPathScanner.ParseStep'); 1918 nodeName := Copy(CurTokenString, FPrefixLength+2, MaxInt); 1919 end 1920 else 1921 nodeName := CurTokenString; 1922 NextToken; 1923 end; 1924 end 1925 else 1926 Error(SParserInvalidNodeTest); 1927 1928 Result := TStep.Create(Axis, nodeType); 1929 Result.NodeTestString := nodeName; 1930 Result.NSTestString := nsURI; 1931end; 1932 1933function TXPathScanner.ParsePrimaryExpr: TXPathExprNode; // [15] 1934begin 1935 case CurToken of 1936 tkVariable: // [36] Variable reference 1937 Result := TXPathVariableNode.Create(CurTokenString); 1938 tkLeftBracket: 1939 begin 1940 NextToken; 1941 Result := ParseOrExpr; 1942 if CurToken <> tkRightBracket then 1943 Error(SParserExpectedRightBracket); 1944 end; 1945 tkString: // [29] Literal 1946 Result := TXPathConstantNode.Create( 1947 TXPathStringVariable.Create(CurTokenString)); 1948 tkNumber: // [30] Number 1949 Result := TXPathConstantNode.Create( 1950 TXPathNumberVariable.Create(StrToNumber(CurTokenString))); 1951 tkIdentifier: // [16] Function call 1952 Result := ParseFunctionCall; 1953 else 1954 Error(SParserInvalidPrimExpr); 1955 Result := nil; // satisfy compiler 1956 end; 1957 NextToken; 1958end; 1959 1960function TXPathScanner.ParseFunctionCall: TXPathExprNode; 1961var 1962 Name: DOMString; 1963 Args: TXPathNodeArray; 1964 Buffer: array[0..15] of TXPathExprNode; 1965 I: Integer; 1966begin 1967 Name := CurTokenString; 1968 I := 0; 1969 if NextToken <> tkLeftBracket then 1970 Error(SParserExpectedLeftBracket); 1971 NextToken; 1972 // Parse argument list 1973 if CurToken <> tkRightBracket then 1974 repeat 1975 Buffer[I] := ParseOrExpr; 1976 Inc(I); 1977 if I > High(Buffer) then 1978 AddNodes(Args, Buffer, I); 1979 until not SkipToken(tkComma); 1980 if CurToken <> tkRightBracket then 1981 Error(SParserExpectedRightBracket); 1982 1983 AddNodes(Args, Buffer, I); 1984 Result := TXPathFunctionNode.Create(Name, Args); 1985end; 1986 1987function TXPathScanner.ParseUnionExpr: TXPathExprNode; // [18] 1988begin 1989 Result := ParsePathExpr; 1990 while SkipToken(tkPipe) do 1991 Result := TXPathUnionNode.Create(Result, ParsePathExpr); 1992end; 1993 1994function AddStep(Left: TXPathExprNode; Right: TStep): TXPathExprNode; 1995begin 1996 Right.FLeft := Left; 1997 Result := Right; 1998end; 1999 2000function TXPathScanner.ParsePathExpr: TXPathExprNode; // [19] 2001var 2002 tok: TXPathToken; 2003begin 2004 Result := nil; 2005 // Try to detect whether a LocationPath [1] or a FilterExpr [20] follows 2006 if ((CurToken = tkIdentifier) and (PeekToken = tkLeftBracket) and 2007 not (FTokenId in NodeTestKeywords)) or 2008 (CurToken in [tkVariable, tkLeftBracket, tkString, tkNumber]) then 2009 begin 2010 // second, third or fourth case of [19] 2011 Result := ParseFilterExpr; 2012 if SkipToken(tkSlash) then { do nothing } 2013 else if SkipToken(tkSlashSlash) then 2014 Result := AddStep(Result, TStep.Create(axisDescendantOrSelf, ntAnyNode)) 2015 else 2016 Exit; 2017 end 2018 else if CurToken in [tkSlash, tkSlashSlash] then 2019 begin 2020 tok := CurToken; 2021 NextToken; 2022 Result := TStep.Create(axisRoot, ntAnyNode); 2023 if tok = tkSlashSlash then 2024 Result := AddStep(Result, TStep.Create(axisDescendantOrSelf, ntAnyNode)) 2025 else if not (CurToken in [tkDot, tkDotDot, tkAt, tkAsterisk, tkIdentifier, tkNSNameTest]) then 2026 Exit; // allow '/' alone 2027 end; 2028 2029 // Continue with parsing of [3] RelativeLocationPath 2030 repeat 2031 Result := AddStep(Result, ParseStep); 2032 if CurToken = tkSlashSlash then 2033 begin 2034 NextToken; 2035 // Found abbreviated step ("//" for "descendant-or-self::node()") 2036 Result := AddStep(Result, TStep.Create(axisDescendantOrSelf, ntAnyNode)); 2037 end 2038 else if not SkipToken(tkSlash) then 2039 break; 2040 until False; 2041end; 2042 2043function TXPathScanner.ParseFilterExpr: TXPathExprNode; // [20] 2044begin 2045 Result := ParsePrimaryExpr; 2046 // Parse predicates 2047 if CurToken = tkLeftSquareBracket then 2048 begin 2049 Result := TXPathFilterNode.Create(Result); 2050 ParsePredicates(TXPathFilterNode(Result).FPredicates); 2051 end; 2052end; 2053 2054function TXPathScanner.ParseOrExpr: TXPathExprNode; // [21] 2055begin 2056 Result := ParseAndExpr; 2057 while FTokenId = xkOr do 2058 begin 2059 NextToken; 2060 Result := TXPathBooleanOpNode.Create(opOr, Result, ParseAndExpr); 2061 end; 2062end; 2063 2064function TXPathScanner.ParseAndExpr: TXPathExprNode; // [22] 2065begin 2066 Result := ParseEqualityExpr; 2067 while FTokenId = xkAnd do 2068 begin 2069 NextToken; 2070 Result := TXPathBooleanOpNode.Create(opAnd, Result, ParseEqualityExpr); 2071 end; 2072end; 2073 2074function TXPathScanner.ParseEqualityExpr: TXPathExprNode; // [23] 2075var 2076 op: TXPathCompareOp; 2077begin 2078 Result := ParseRelationalExpr; 2079 repeat 2080 case CurToken of 2081 tkEqual: op := opEqual; 2082 tkNotEqual: op := opNotEqual; 2083 else 2084 Break; 2085 end; 2086 NextToken; 2087 Result := TXPathCompareNode.Create(op, Result, ParseRelationalExpr); 2088 until False; 2089end; 2090 2091function TXPathScanner.ParseRelationalExpr: TXPathExprNode; // [24] 2092var 2093 op: TXPathCompareOp; 2094begin 2095 Result := ParseAdditiveExpr; 2096 repeat 2097 case CurToken of 2098 tkLess: op := opLess; 2099 tkLessEqual: op := opLessEqual; 2100 tkGreater: op := opGreater; 2101 tkGreaterEqual: op := opGreaterEqual; 2102 else 2103 Break; 2104 end; 2105 NextToken; 2106 Result := TXPathCompareNode.Create(op, Result, ParseAdditiveExpr); 2107 until False; 2108end; 2109 2110function TXPathScanner.ParseAdditiveExpr: TXPathExprNode; // [25] 2111var 2112 op: TXPathMathOp; 2113begin 2114 Result := ParseMultiplicativeExpr; 2115 repeat 2116 case CurToken of 2117 tkPlus: op := opAdd; 2118 tkMinus: op := opSubtract; 2119 else 2120 Break; 2121 end; 2122 NextToken; 2123 Result := TXPathMathOpNode.Create(op, Result, ParseMultiplicativeExpr); 2124 until False; 2125end; 2126 2127function TXPathScanner.ParseMultiplicativeExpr: TXPathExprNode; // [26] 2128var 2129 op: TXPathMathOp; 2130begin 2131 Result := ParseUnaryExpr; 2132 repeat 2133 case CurToken of 2134 tkAsterisk: 2135 op := opMultiply; 2136 tkIdentifier: 2137 if FTokenId = xkDiv then 2138 op := opDivide 2139 else if FTokenId = xkMod then 2140 op := opMod 2141 else 2142 break; 2143 else 2144 break; 2145 end; 2146 NextToken; 2147 Result := TXPathMathOpNode.Create(op, Result, ParseUnaryExpr); 2148 until False; 2149end; 2150 2151function TXPathScanner.ParseUnaryExpr: TXPathExprNode; // [27] 2152var 2153 NegCount: Integer; 2154begin 2155 NegCount := 0; 2156 while SkipToken(tkMinus) do 2157 Inc(NegCount); 2158 Result := ParseUnionExpr; 2159 2160 if Odd(NegCount) then 2161 Result := TXPathNegationNode.Create(Result); 2162end; 2163 2164{ TXPathContext } 2165 2166constructor TXPathContext.Create(AContextNode: TDOMNode; 2167 AContextPosition, AContextSize: Integer); 2168begin 2169 inherited Create; 2170 ContextNode := AContextNode; 2171 ContextPosition := AContextPosition; 2172 ContextSize := AContextSize; 2173end; 2174 2175 2176{ TXPathEnvironment } 2177 2178type 2179 PFunctionInfo = ^TFunctionInfo; 2180 TFunctionInfo = record 2181 Name: String; 2182 Fn: TXPathFunction; 2183 end; 2184 2185 PVariableInfo = ^TVariableInfo; 2186 TVariableInfo = record 2187 Name: String; 2188 Variable: TXPathVariable; 2189 end; 2190 2191 2192constructor TXPathEnvironment.Create; 2193begin 2194 inherited Create; 2195 FFunctions := TFPList.Create; 2196 FVariables := TFPList.Create; 2197 2198 // Add the functions of the XPath Core Function Library 2199 2200 // Node set functions 2201 AddFunction('last', @xpLast); 2202 AddFunction('position', @xpPosition); 2203 AddFunction('count', @xpCount); 2204 AddFunction('id', @xpId); 2205 AddFunction('local-name', @xpLocalName); 2206 AddFunction('namespace-uri', @xpNamespaceURI); 2207 AddFunction('name', @xpName); 2208 // String functions 2209 AddFunction('string', @xpString); 2210 AddFunction('concat', @xpConcat); 2211 AddFunction('starts-with', @xpStartsWith); 2212 AddFunction('contains', @xpContains); 2213 AddFunction('substring-before', @xpSubstringBefore); 2214 AddFunction('substring-after', @xpSubstringAfter); 2215 AddFunction('substring', @xpSubstring); 2216 AddFunction('string-length', @xpStringLength); 2217 AddFunction('normalize-space', @xpNormalizeSpace); 2218 AddFunction('translate', @xpTranslate); 2219 // Boolean functions 2220 AddFunction('boolean', @xpBoolean); 2221 AddFunction('not', @xpNot); 2222 AddFunction('true', @xpTrue); 2223 AddFunction('false', @xpFalse); 2224 AddFunction('lang', @xpLang); 2225 // Number functions 2226 AddFunction('number', @xpNumber); 2227 AddFunction('sum', @xpSum); 2228 AddFunction('floor', @xpFloor); 2229 AddFunction('ceiling', @xpCeiling); 2230 AddFunction('round', @xpRound); 2231end; 2232 2233destructor TXPathEnvironment.Destroy; 2234var 2235 i: Integer; 2236 FunctionInfo: PFunctionInfo; 2237 VariableInfo: PVariableInfo; 2238begin 2239 for i := 0 to FFunctions.Count - 1 do 2240 begin 2241 FunctionInfo := PFunctionInfo(FFunctions[i]); 2242 FreeMem(FunctionInfo); 2243 end; 2244 FFunctions.Free; 2245 for i := 0 to FVariables.Count - 1 do 2246 begin 2247 VariableInfo := PVariableInfo(FVariables[i]); 2248 FreeMem(VariableInfo); 2249 end; 2250 FVariables.Free; 2251 inherited Destroy; 2252end; 2253 2254function TXPathEnvironment.GetFunctionIndex(const AName: String): Integer; 2255var 2256 i: Integer; 2257begin 2258 for i := 0 to FFunctions.Count - 1 do 2259 if PFunctionInfo(FFunctions[i])^.Name = AName then 2260 begin 2261 Result := i; 2262 exit; 2263 end; 2264 Result := -1; 2265end; 2266 2267function TXPathEnvironment.GetVariableIndex(const AName: String): Integer; 2268var 2269 i: Integer; 2270begin 2271 for i := 0 to FVariables.Count - 1 do 2272 if PVariableInfo(FFunctions[i])^.Name = AName then 2273 begin 2274 Result := i; 2275 exit; 2276 end; 2277 Result := -1; 2278end; 2279 2280procedure TXPathEnvironment.AddFunction(const AName: String; AFunction: TXPathFunction); 2281var 2282 NewFunctionInfo: PFunctionInfo; 2283begin 2284 // !!!: Prevent the addition of duplicate functions 2285 New(NewFunctionInfo); 2286 NewFunctionInfo^.Name := AName; 2287 NewFunctionInfo^.Fn := AFunction; 2288 FFunctions.Add(NewFunctionInfo); 2289end; 2290 2291procedure TXPathEnvironment.AddVariable(const AName: String; AVariable: TXPathVariable); 2292var 2293 NewVariableInfo: PVariableInfo; 2294begin 2295 // !!!: Prevent the addition of duplicate variables 2296 New(NewVariableInfo); 2297 NewVariableInfo^.Name := AName; 2298 NewVariableInfo^.Variable := AVariable; 2299 FVariables.Add(NewVariableInfo); 2300end; 2301 2302procedure TXPathEnvironment.RemoveFunction(Index: Integer); 2303var 2304 FunctionInfo: PFunctionInfo; 2305begin 2306 FunctionInfo := PFunctionInfo(FFunctions[Index]); 2307 Dispose(FunctionInfo); 2308 FFunctions.Delete(Index); 2309end; 2310 2311procedure TXPathEnvironment.RemoveFunction(const AName: String); 2312var 2313 i: Integer; 2314begin 2315 for i := 0 to FFunctions.Count - 1 do 2316 if PFunctionInfo(FFunctions[i])^.Name = AName then 2317 begin 2318 RemoveFunction(i); 2319 exit; 2320 end; 2321end; 2322 2323procedure TXPathEnvironment.RemoveVariable(Index: Integer); 2324var 2325 VariableInfo: PVariableInfo; 2326begin 2327 VariableInfo := PVariableInfo(FVariables[Index]); 2328 Dispose(VariableInfo); 2329 FVariables.Delete(Index); 2330end; 2331 2332procedure TXPathEnvironment.RemoveVariable(const AName: String); 2333var 2334 Index: Integer; 2335begin 2336 Index := GetVariableIndex(AName); 2337 if Index >= 0 then 2338 RemoveVariable(Index); 2339end; 2340 2341function TXPathEnvironment.GetFunctionCount: Integer; 2342begin 2343 Result := FFunctions.Count; 2344end; 2345 2346function TXPathEnvironment.GetVariableCount: Integer; 2347begin 2348 Result := FVariables.Count; 2349end; 2350 2351function TXPathEnvironment.GetFunction(Index: Integer): TXPathFunction; 2352begin 2353 Result := PFunctionInfo(FFunctions[Index])^.Fn; 2354end; 2355 2356function TXPathEnvironment.GetFunction(const AName: String): TXPathFunction; 2357var 2358 i: Integer; 2359begin 2360 for i := 0 to FFunctions.Count - 1 do 2361 if PFunctionInfo(FFunctions[i])^.Name = AName then 2362 begin 2363 Result := PFunctionInfo(FFunctions[i])^.Fn; 2364 exit; 2365 end; 2366 Result := nil; 2367end; 2368 2369function TXPathEnvironment.GetVariable(Index: Integer): TXPathVariable; 2370begin 2371 Result := PVariableInfo(FVariables[Index])^.Variable; 2372end; 2373 2374function TXPathEnvironment.GetVariable(const AName: String): TXPathVariable; 2375var 2376 i: Integer; 2377begin 2378 for i := 0 to FVariables.Count - 1 do 2379 if PFunctionInfo(FVariables[i])^.Name = AName then 2380 begin 2381 Result := PVariableInfo(FVariables[i])^.Variable; 2382 exit; 2383 end; 2384 Result := nil; 2385end; 2386 2387function TXPathEnvironment.xpLast(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2388begin 2389 if Args.Count <> 0 then 2390 EvaluationError(SEvalInvalidArgCount); 2391 Result := TXPathNumberVariable.Create(Context.ContextSize); 2392end; 2393 2394function TXPathEnvironment.xpPosition(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2395begin 2396 if Args.Count <> 0 then 2397 EvaluationError(SEvalInvalidArgCount); 2398 Result := TXPathNumberVariable.Create(Context.ContextPosition); 2399end; 2400 2401function TXPathEnvironment.xpCount(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2402begin 2403 if Args.Count <> 1 then 2404 EvaluationError(SEvalInvalidArgCount); 2405 Result := TXPathNumberVariable.Create(TXPathVariable(Args[0]).AsNodeSet.Count); 2406end; 2407 2408function TXPathEnvironment.xpId(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2409var 2410 i: Integer; 2411 ResultSet: TNodeSet; 2412 TheArg: TXPathVariable; 2413 doc: TDOMDocument; 2414 2415 procedure AddId(ns: TNodeSet; const s: DOMString); 2416 var 2417 Head, Tail, L: Integer; 2418 Token: DOMString; 2419 Element: TDOMNode; 2420 begin 2421 Head := 1; 2422 L := Length(s); 2423 2424 while Head <= L do 2425 begin 2426 while (Head <= L) and IsXmlWhiteSpace(s[Head]) do 2427 Inc(Head); 2428 2429 Tail := Head; 2430 while (Tail <= L) and not IsXmlWhiteSpace(s[Tail]) do 2431 Inc(Tail); 2432 SetString(Token, @s[Head], Tail - Head); 2433 Element := doc.GetElementById(Token); 2434 if Assigned(Element) then 2435 ns.Add(Element); 2436 2437 Head := Tail; 2438 end; 2439 end; 2440 2441begin 2442 if Args.Count <> 1 then 2443 EvaluationError(SEvalInvalidArgCount); 2444 // TODO: probably have doc as member of Context 2445 if Context.ContextNode.NodeType = DOCUMENT_NODE then 2446 doc := TDOMDocument(Context.ContextNode) 2447 else 2448 doc := Context.ContextNode.OwnerDocument; 2449 2450 ResultSet := TNodeSet.Create; 2451 TheArg := TXPathVariable(Args[0]); 2452 if TheArg is TXPathNodeSetVariable then 2453 begin 2454 with TheArg.AsNodeSet do 2455 for i := 0 to Count-1 do 2456 AddId(ResultSet, NodeToText(TDOMNode(Items[i]))); 2457 end 2458 else 2459 AddId(ResultSet, TheArg.AsText); 2460 Result := TXPathNodeSetVariable.Create(ResultSet); 2461end; 2462 2463function TXPathEnvironment.xpLocalName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2464var 2465 n: TDOMNode; 2466 NodeSet: TNodeSet; 2467 s: DOMString; 2468begin 2469 if Args.Count > 1 then 2470 EvaluationError(SEvalInvalidArgCount); 2471 n := nil; 2472 if Args.Count = 0 then 2473 n := Context.ContextNode 2474 else 2475 begin 2476 NodeSet := TXPathVariable(Args[0]).AsNodeSet; 2477 if NodeSet.Count > 0 then 2478 n := TDOMNode(NodeSet[0]); 2479 end; 2480 s := ''; 2481 if Assigned(n) then 2482 begin 2483 case n.NodeType of 2484 ELEMENT_NODE,ATTRIBUTE_NODE: 2485 with TDOMNode_NS(n) do 2486 s := Copy(NSI.QName^.Key, NSI.PrefixLen+1, MaxInt); 2487 PROCESSING_INSTRUCTION_NODE: 2488 s := TDOMProcessingInstruction(n).Target; 2489 // TODO: NAMESPACE_NODE: must return prefix part 2490 end; 2491 end; 2492 Result := TXPathStringVariable.Create(s); 2493end; 2494 2495function TXPathEnvironment.xpNamespaceURI(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2496var 2497 n: TDOMNode; 2498 NodeSet: TNodeSet; 2499 s: DOMString; 2500begin 2501 if Args.Count > 1 then 2502 EvaluationError(SEvalInvalidArgCount); 2503 n := nil; 2504 if Args.Count = 0 then 2505 n := Context.ContextNode 2506 else 2507 begin 2508 NodeSet := TXPathVariable(Args[0]).AsNodeSet; 2509 if NodeSet.Count > 0 then 2510 n := TDOMNode(NodeSet[0]); 2511 end; 2512 if Assigned(n) then 2513 s := n.namespaceUri 2514 else 2515 s := ''; 2516 Result := TXPathStringVariable.Create(s); 2517end; 2518 2519function TXPathEnvironment.xpName(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2520var 2521 n: TDOMNode; 2522 NodeSet: TNodeSet; 2523 s: DOMString; 2524begin 2525 if Args.Count > 1 then 2526 EvaluationError(SEvalInvalidArgCount); 2527 n := nil; 2528 if Args.Count = 0 then 2529 n := Context.ContextNode 2530 else 2531 begin 2532 NodeSet := TXPathVariable(Args[0]).AsNodeSet; 2533 if NodeSet.Count > 0 then 2534 n := TDOMNode(NodeSet[0]); 2535 end; 2536 s := ''; 2537 if Assigned(n) then 2538 begin 2539 case n.NodeType of 2540 ELEMENT_NODE,ATTRIBUTE_NODE: 2541 s := TDOMNode_NS(n).NSI.QName^.Key; 2542 PROCESSING_INSTRUCTION_NODE: 2543 s := TDOMProcessingInstruction(n).Target; 2544 // TODO: NAMESPACE_NODE: must return prefix part 2545 end; 2546 end; 2547 Result := TXPathStringVariable.Create(s); 2548end; 2549 2550function TXPathEnvironment.xpString(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2551var 2552 s: DOMString; 2553begin 2554 if Args.Count > 1 then 2555 EvaluationError(SEvalInvalidArgCount); 2556 if Args.Count = 0 then 2557 s := NodeToText(Context.ContextNode) 2558 else 2559 s := TXPathVariable(Args[0]).AsText; 2560 Result := TXPathStringVariable.Create(s); 2561end; 2562 2563function TXPathEnvironment.xpConcat(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2564var 2565 i: Integer; 2566 s: DOMString; 2567begin 2568 if Args.Count < 2 then 2569 EvaluationError(SEvalInvalidArgCount); 2570 SetLength(s, 0); 2571 for i := 0 to Args.Count - 1 do 2572 s := s + TXPathVariable(Args[i]).AsText; 2573 Result := TXPathStringVariable.Create(s); 2574end; 2575 2576function TXPathEnvironment.xpStartsWith(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2577var 2578 s1, s2: DOMString; 2579 res: Boolean; 2580begin 2581 if Args.Count <> 2 then 2582 EvaluationError(SEvalInvalidArgCount); 2583 s1 := TXPathVariable(Args[0]).AsText; 2584 s2 := TXPathVariable(Args[1]).AsText; 2585 if s2 = '' then 2586 res := True 2587 else 2588 res := Pos(s2, s1) = 1; 2589 Result := TXPathBooleanVariable.Create(res); 2590end; 2591 2592function TXPathEnvironment.xpContains(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2593var 2594 s1, s2: DOMString; 2595 res: Boolean; 2596begin 2597 if Args.Count <> 2 then 2598 EvaluationError(SEvalInvalidArgCount); 2599 s1 := TXPathVariable(Args[0]).AsText; 2600 s2 := TXPathVariable(Args[1]).AsText; 2601 if s2 = '' then 2602 res := True 2603 else 2604 res := Pos(s2, s1) <> 0; 2605 Result := TXPathBooleanVariable.Create(res); 2606end; 2607 2608function TXPathEnvironment.xpSubstringBefore(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2609var 2610 s, substr: DOMString; 2611begin 2612 if Args.Count <> 2 then 2613 EvaluationError(SEvalInvalidArgCount); 2614 s := TXPathVariable(Args[0]).AsText; 2615 substr := TXPathVariable(Args[1]).AsText; 2616 Result := TXPathStringVariable.Create(Copy(s, 1, Pos(substr, s)-1)); 2617end; 2618 2619function TXPathEnvironment.xpSubstringAfter(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2620var 2621 s, substr: DOMString; 2622 i: Integer; 2623begin 2624 if Args.Count <> 2 then 2625 EvaluationError(SEvalInvalidArgCount); 2626 s := TXPathVariable(Args[0]).AsText; 2627 substr := TXPathVariable(Args[1]).AsText; 2628 i := Pos(substr, s); 2629 if i <> 0 then 2630 Result := TXPathStringVariable.Create(Copy(s, i + Length(substr), MaxInt)) 2631 else 2632 Result := TXPathStringVariable.Create(''); 2633end; 2634 2635function TXPathEnvironment.xpSubstring(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2636var 2637 s: DOMString; 2638 i, n1, n2: Integer; 2639 e1, e2: Extended; 2640 empty: Boolean; 2641begin 2642 if (Args.Count < 2) or (Args.Count > 3) then 2643 EvaluationError(SEvalInvalidArgCount); 2644 s := TXPathVariable(Args[0]).AsText; 2645 e1 := TXPathVariable(Args[1]).AsNumber; 2646 n1 := 1; // satisfy compiler 2647 n2 := MaxInt; 2648 empty := IsNaN(e1) or IsInfinite(e1); 2649 if not empty then 2650 n1 := floor(0.5 + e1); 2651 if Args.Count = 3 then 2652 begin 2653 e2 := TXPathVariable(Args[2]).AsNumber; 2654 if IsNaN(e2) or (IsInfinite(e2) and (e2 < 0)) then 2655 empty := True 2656 else if not IsInfinite(e2) then 2657 n2 := floor(0.5 + e2); 2658 end; 2659 i := Max(n1, 1); 2660 if empty then 2661 n2 := -1 2662 else if n2 < MaxInt then 2663 n2 := n2 + (n1 - i); 2664 Result := TXPathStringVariable.Create(Copy(s, i, n2)); 2665end; 2666 2667function TXPathEnvironment.xpStringLength(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2668var 2669 s: DOMString; 2670begin 2671 if Args.Count > 1 then 2672 EvaluationError(SEvalInvalidArgCount); 2673 if Args.Count = 0 then 2674 s := NodeToText(Context.ContextNode) 2675 else 2676 s := TXPathVariable(Args[0]).AsText; 2677 Result := TXPathNumberVariable.Create(Length(s)); 2678end; 2679 2680function TXPathEnvironment.xpNormalizeSpace(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2681var 2682 s: DOMString; 2683 p: DOMPChar; 2684 i: Integer; 2685begin 2686 if Args.Count > 1 then 2687 EvaluationError(SEvalInvalidArgCount); 2688 if Args.Count = 0 then 2689 s := NodeToText(Context.ContextNode) 2690 else 2691 s := TXPathVariable(Args[0]).AsText; 2692 UniqueString(s); 2693 p := DOMPChar(s); 2694 for i := 1 to Length(s) do 2695 begin 2696 if (p^ = #10) or (p^ = #13) or (p^ = #9) then 2697 p^ := #32; 2698 Inc(p); 2699 end; 2700 NormalizeSpaces(s); 2701 Result := TXPathStringVariable.Create(s); 2702end; 2703 2704function TXPathEnvironment.xpTranslate(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2705var 2706 S: DOMString; 2707begin 2708 if Args.Count <> 3 then 2709 EvaluationError(SEvalInvalidArgCount); 2710 S := TXPathVariable(Args[0]).AsText; 2711 TranslateWideString(S, TXPathVariable(Args[1]).AsText, TXPathVariable(Args[2]).AsText); 2712 Result := TXPathStringVariable.Create(S); 2713end; 2714 2715function TXPathEnvironment.xpBoolean(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2716begin 2717 if Args.Count <> 1 then 2718 EvaluationError(SEvalInvalidArgCount); 2719 Result := TXPathBooleanVariable.Create(TXPathVariable(Args[0]).AsBoolean); 2720end; 2721 2722function TXPathEnvironment.xpNot(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2723begin 2724 if Args.Count <> 1 then 2725 EvaluationError(SEvalInvalidArgCount); 2726 Result := TXPathBooleanVariable.Create(not TXPathVariable(Args[0]).AsBoolean); 2727end; 2728 2729function TXPathEnvironment.xpTrue(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2730begin 2731 if Args.Count <> 0 then 2732 EvaluationError(SEvalInvalidArgCount); 2733 Result := TXPathBooleanVariable.Create(True); 2734end; 2735 2736function TXPathEnvironment.xpFalse(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2737begin 2738 if Args.Count <> 0 then 2739 EvaluationError(SEvalInvalidArgCount); 2740 Result := TXPathBooleanVariable.Create(False); 2741end; 2742 2743function TXPathEnvironment.xpLang(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2744var 2745 L: Integer; 2746 TheArg, NodeLang: DOMString; 2747 res: Boolean; 2748begin 2749 if Args.Count <> 1 then 2750 EvaluationError(SEvalInvalidArgCount); 2751 TheArg := TXPathVariable(Args[0]).AsText; 2752 NodeLang := GetNodeLanguage(Context.ContextNode); 2753 2754 L := Length(TheArg); 2755 res := (L <= Length(NodeLang)) and 2756 (WStrLIComp(DOMPChar(NodeLang), DOMPChar(TheArg), L) = 0) and 2757 ((L = Length(NodeLang)) or (NodeLang[L+1] = '-')); 2758 2759 Result := TXPathBooleanVariable.Create(res); 2760end; 2761 2762function TXPathEnvironment.xpNumber(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2763begin 2764 if Args.Count > 1 then 2765 EvaluationError(SEvalInvalidArgCount); 2766 if Args.Count = 0 then 2767 Result := TXPathNumberVariable.Create(StrToNumber(NodeToText(Context.ContextNode))) 2768 else 2769 Result := TXPathNumberVariable.Create(TXPathVariable(Args[0]).AsNumber); 2770end; 2771 2772function TXPathEnvironment.xpSum(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2773var 2774 i: Integer; 2775 ns: TNodeSet; 2776 sum: Extended; 2777begin 2778 if Args.Count <> 1 then 2779 EvaluationError(SEvalInvalidArgCount); 2780 ns := TXPathVariable(Args[0]).AsNodeSet; 2781 sum := 0.0; 2782 for i := 0 to ns.Count-1 do 2783 sum := sum + StrToNumber(NodeToText(TDOMNode(ns[i]))); 2784 Result := TXPathNumberVariable.Create(sum); 2785end; 2786 2787function TXPathEnvironment.xpFloor(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2788var 2789 n: Extended; 2790begin 2791 if Args.Count <> 1 then 2792 EvaluationError(SEvalInvalidArgCount); 2793 n := TXPathVariable(Args[0]).AsNumber; 2794 if not IsNan(n) then 2795 n := floor(n); 2796 Result := TXPathNumberVariable.Create(n); 2797end; 2798 2799function TXPathEnvironment.xpCeiling(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2800var 2801 n: Extended; 2802begin 2803 if Args.Count <> 1 then 2804 EvaluationError(SEvalInvalidArgCount); 2805 n := TXPathVariable(Args[0]).AsNumber; 2806 if not IsNan(n) then 2807 n := ceil(n); 2808 Result := TXPathNumberVariable.Create(n); 2809end; 2810 2811function TXPathEnvironment.xpRound(Context: TXPathContext; Args: TXPathVarList): TXPathVariable; 2812var 2813 num: Extended; 2814begin 2815 if Args.Count <> 1 then 2816 EvaluationError(SEvalInvalidArgCount); 2817 num := TXPathVariable(Args[0]).AsNumber; 2818 if not (IsNan(num) or IsInfinite(num)) then 2819 num := floor(0.5 + num); 2820 Result := TXPathNumberVariable.Create(num); 2821end; 2822 2823{ TXPathNSResolver } 2824 2825constructor TXPathNSResolver.Create(aNode: TDOMNode); 2826begin 2827 inherited Create; 2828 FNode := aNode; 2829end; 2830 2831function TXPathNSResolver.LookupNamespaceURI(const aPrefix: DOMString): DOMString; 2832begin 2833 if assigned(FNode) then 2834 result := FNode.LookupNamespaceURI(aPrefix) 2835 else 2836 result := ''; 2837end; 2838 2839{ TXPathExpression } 2840 2841constructor TXPathExpression.Create(AScanner: TXPathScanner; 2842 CompleteExpression: Boolean; AResolver: TXPathNSResolver); 2843begin 2844 inherited Create; 2845 AScanner.FResolver := AResolver; 2846 FRootNode := AScanner.ParseOrExpr; 2847 if CompleteExpression and (AScanner.CurToken <> tkEndOfStream) then 2848 EvaluationError(SParserGarbageAfterExpression); 2849end; 2850 2851function TXPathExpression.Evaluate(AContextNode: TDOMNode): TXPathVariable; 2852var 2853 Environment: TXPathEnvironment; 2854begin 2855 Environment := TXPathEnvironment.Create; 2856 try 2857 Result := Evaluate(AContextNode, Environment); 2858 finally 2859 Environment.Free; 2860 end; 2861end; 2862 2863destructor TXPathExpression.Destroy; 2864begin 2865 FRootNode.Free; 2866 inherited Destroy; 2867end; 2868 2869function TXPathExpression.Evaluate(AContextNode: TDOMNode; 2870 AEnvironment: TXPathEnvironment): TXPathVariable; 2871var 2872 Context: TXPathContext; 2873 mask: TFPUExceptionMask; 2874begin 2875 if Assigned(FRootNode) then 2876 begin 2877 mask := GetExceptionMask; 2878 SetExceptionMask(mask + [exInvalidOp, exZeroDivide]); 2879 Context := TXPathContext.Create(AContextNode, 1, 1); 2880 try 2881 Result := FRootNode.Evaluate(Context, AEnvironment); 2882 finally 2883 Context.Free; 2884 SetExceptionMask(mask); 2885 end; 2886 end else 2887 Result := nil; 2888end; 2889 2890function EvaluateXPathExpression(const AExpressionString: DOMString; 2891 AContextNode: TDOMNode; AResolver: TXPathNSResolver): TXPathVariable; 2892var 2893 Scanner: TXPathScanner; 2894 Expression: TXPathExpression; 2895begin 2896 Scanner := TXPathScanner.Create(AExpressionString); 2897 try 2898 Expression := TXPathExpression.Create(Scanner, True, AResolver); 2899 try 2900 Result := Expression.Evaluate(AContextNode); 2901 finally 2902 Expression.Free; 2903 end; 2904 finally 2905 Scanner.Free; 2906 end; 2907end; 2908 2909end. 2910