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