unit xpathmain;

interface

{$I XpDefine.inc}

uses
  TestFramework,
  XpBase,
  XpDOM;

type
  TXpXPathTests = class(TTestCase)
    protected
      FDoc : TXpObjModel;

      function LoadDoc(const aName : string) : TXpObjModel;
      function LoadMem(XMLStr : string) : TXpObjModel;

      procedure OnFunctionAvail(oOwner        : TObject;
                                sFunctionName : DOMString;
                            var bAvailable    : Boolean);
        { Used to test DOM's OnFunctionAvailable event. }
    public
    published
      { Illegal expressions. }
      procedure testMissingRFrame;

      { Abbreviated & Unabbreviated expressions. }
      procedure testSelectChildElement;
      procedure testSelectChildElementAll;
      procedure testSelectAllTextNodes;
      procedure testSelectAllNodes;
      procedure testSelectNamedAttribute;
      procedure testSelectAllAttributes;
      procedure testSelectDescElements;
      procedure testSelectAncestor;
      procedure testSelectAncestor2;
      procedure testSelectAncestorOrSelf;
      procedure testSelectDescOrSelf;
      procedure testSelectSelf;
      procedure testSelectSelfIf;
      procedure testSelectDescOfChild;
      procedure testSelectGrandchildren;
      procedure testSelectDocRoot;
      procedure testSelectAllDesc;
      procedure testSelectAllDescWithParent;
      procedure testSelectFirstChild;
      procedure testSelectLastChild;
      procedure testSelectNextToLastChild;
      procedure testSelectAllButFirstChild;
      procedure testSelectFollowingSibling;
      procedure testSelectNextSibling;
      procedure testSelectPrevSibling;
      procedure testSelectSpecificElement;
      procedure testSelectSpecificElementStage2;
      procedure testSelectElementWithAttrib;
      procedure testSelectSpecificElementWithAttrib;
      procedure testSelectSpecificElementIfHasAttrib;
      procedure testSelectChildIfHasChildWithStringValue;
      procedure testSelectChildIfHasChild;
      procedure testSelectOr;
      procedure testSelectOrLast;
      procedure testSelectParent;
      procedure testSelectParentAttrib;
      procedure testSelectFirstDesc;
      procedure testSelectAllByPos;

      { Operators }
      procedure testDiv;
      procedure testAnd;
      procedure testEqualityExpr;

      { Functions }
      procedure testFuncBoolean;
      procedure testFuncCeiling;
      procedure testFuncConcat;
      procedure testFuncContains;
      procedure testFuncCount;
      procedure testFuncFalse;
      procedure testFuncFloor;
      procedure testFuncFunctionAvailable;
      procedure testFuncGenerateID;
      procedure testFuncID;
      procedure testFuncLang;
      procedure testFuncLast;
      procedure testFuncLocalName;
      procedure testFuncName;
      procedure testFuncNamespaceURI;
      procedure testFuncNormalizeSpace;
      procedure testFuncNot;
      procedure testFuncNumber;
      procedure testFuncPosition;
      procedure testFuncRound;
      procedure testFuncStartsWith;
      procedure testFuncString;
      procedure testFuncStringLength;
      procedure testFuncSubstring;
      procedure testFuncSubstringAfter;
      procedure testFuncSubstringBefore;
      procedure testFuncSum;
      procedure testFuncSystemProperty;
      procedure testFuncTranslate;
      procedure testFuncTrue;
      procedure testFuncUnparsedEntityURI;

      { Other tests }
      procedure testCheckDecimalSep;
  end;

implementation

uses
  sysutils;

const
  cs4 = '4';
  csCatalog = 'catalog';
  csChrome = 'chrome';
  csDocCountries = 'countries.xml';
  csDocID = 'id.xml';
  csDocLug = 'lugnuts.xml';
  csDocLugLang = 'lugnutslang.xml';
  csDocLugLocal = 'lugnutslocal.xml';
  csDocMemo = 'memo.xml';
  csFinish = 'finish';
  csItem = 'item';
  csLocking = 'locking';
  csName = 'name';
  csQuantity = 'quantity';
  csThreadSizeList = 'threadsizelist';
  csWondernutChrome = 'Wondernut Chrome';
  csYes = 'yes';
  csYogu = 'Yogu';

{===TXpXPathTests====================================================}
function TXpXPathTests.LoadDoc(const aName : string) : TXpObjModel;
begin
  Result := TXpObjModel.Create(nil);
  try
{$IFDEF WIN32}
    Result.LoadDataSource('..\data\' + aName);
{$ENDIF}
{$IFDEF LINUX}
    Result.LoadDataSource('../data/' + aName);
{$ENDIF}
  except
    Result.Free;
    Result := nil;
  end;
end;
{--------}
function TXpXPathTests.LoadMem(XMLStr : string) : TXpObjModel;
begin
  Result := TXpObjModel.Create(nil);
  try
    Result.LoadMemory(XMLStr[1], Length(XMLStr));
  except
    Result.Free;
    Result := nil;
  end;
end;
{--------}
procedure TXpXPathTests.TestMissingRFrame;
var
  aList : TXpNodeList;
  aNode : TXpElement;
  XMLStr : string;
begin
  aList := nil;
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<content>' +
    '  <datasets>' +
    '    <name link="a1" >xxx</name>' +
    '    <name link="a2">yyy</name>' +
    '    <name link="b1">zzz</name>' +
    '  </datasets>' +
    '</content>';

  FDoc := LoadMem(XMLStr);
  try
    Check(Assigned(FDoc) and
          (FDoc.ErrorCount = 0), 'Could not load XMLStr: ' + XMLStr);
    aNode := FDoc.DocumentElement;

    try
      aList := aNode.SelectNodes('//*[starts-with(@link,"b1")');
      { NOTE: Ideally, we would see an exception in this case. However, the
        SelectNodes method hides any exceptions. No cleanup is performed
        hence we have one node in the result list. }
      Assert(aList.Length = 1);
    except
      on E:Exception do begin
        Assert(E is TXpDOMException, 'Unexpected exception type: ' + E.ClassName);
        Assert(TXpDOMException(E).Code = ecExpectedToken,
               Format('Unexpected exception code: %d',
                      [ord(TXpDOMException(E).Code)]));
      end;
    end;
    { NOTE: Ideally, we would see an exception in this case. However, the
      SelectNodes method hides any exceptions. No cleanup is performed
      hence we have one node in the result list. }
//    Assert(ExceptRaised, 'Exception not raised for missing RFRAME');
  finally
    aList.Free;
    FDoc.Free;
  end;

end;
{--------}
procedure TXpXPathTests.OnFunctionAvail(oOwner        : TObject;
                                        sFunctionName : DOMString;
                                    var bAvailable    : Boolean);
begin
  bAvailable := (sFunctionName = 'detonate');
end;
{--------}
procedure TXpXPathTests.testSelectChildElement;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aList := FDoc.DocumentElement.SelectNodes('child::item');
    CheckEquals(3, aList.Length, 'Unabbreviated');
    aNode := TXpElement(aList.Item(0));
    aList.Free;

    aList := aNode.SelectNodes('child::wheelcompatibility');
    CheckEquals(2, aList.Length, 'Unabbreviated 2');
    aList.Free;

    { Unabbrev. fail }
    aList := FDoc.DocumentElement.SelectNodes('child::items');
    CheckEquals(0, aList.Length, 'Unabbreviated fail');
    aList.Free;

    { Abbrev. }
    aList := FDoc.DocumentElement.SelectNodes('item');
    CheckEquals(3, aList.Length, 'Abbreviated');
    aNode := TXpElement(aList.Item(0));
    aList.Free;

    aList := aNode.SelectNodes('child::wheelcompatibility');
    CheckEquals(2, aList.Length, 'Abbreviated 2');
    aList.Free;

    { Abbrev. fail }
    aList := FDoc.DocumentElement.SelectNodes('items');
    CheckEquals(0, aList.Length, 'Abbreviated fail');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectChildElementAll;
var
  aList : TXpNodeList;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aList := FDoc.DocumentElement.SelectNodes('child::*');
    CheckEquals(3, aList.Length, 'Unabbreviated');
    aList.Free;

    { Abbrev. }
    aList := FDoc.DocumentElement.SelectNodes('*');
    CheckEquals(3, aList.Length, 'Abbreviated');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAllTextNodes;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocMemo);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocMemo);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.LastChild.FirstChild);
    aList := aNode.SelectNodes('child::text()');
    CheckEquals(2, aList.Length, 'Unabbreviated');
    aList.Free;

    { Abbrev. }
    aList := aNode.SelectNodes('text()');
    CheckEquals(2, aList.Length, 'Abbreviated');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAllNodes;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocMemo);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocMemo);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.LastChild.FirstChild);
    aList := aNode.SelectNodes('child::node()');
    CheckEquals(3, aList.Length, 'Unabbreviated');
    aList.Free;

    { Abbrev. }
    aList := aNode.SelectNodes('node()');
    CheckEquals(3, aList.Length, 'Abbreviated');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectNamedAttribute;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('attribute::finish');
    CheckEquals(1, aList.Length, 'Unabbreviated');
    CheckEquals(csChrome, aList.Item(0).NodeValue, 'Unabbrev. wrong attribute value');
    aList.Free;

    { Abbrev. }
    aList := aNode.SelectNodes('@finish');
    CheckEquals(1, aList.Length, 'Abbreviated');
    CheckEquals(csChrome, aList.Item(0).NodeValue, 'Abbrev. wrong attribute value');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAllAttributes;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('attribute::*');
    CheckEquals(4, aList.Length, 'Unabbreviated');
    CheckEquals(csName, aList.Item(0).NodeName, 'Unabbrev. wrong attribute value 1');
    CheckEquals(csFinish, aList.Item(1).NodeName, 'Unabbrev. wrong attribute value 2');
    CheckEquals(csLocking, aList.Item(2).NodeName, 'Unabbrev. wrong attribute value 3');
    CheckEquals(csQuantity, aList.Item(3).NodeName, 'Unabbrev. wrong attribute value 4');
    aList.Free;

    { Abbrev. }
    aList := aNode.SelectNodes('@*');
    CheckEquals(4, aList.Length, 'Abbreviated');
    CheckEquals(csName, aList.Item(0).NodeName, 'Abbrev. wrong attribute value 1');
    CheckEquals(csFinish, aList.Item(1).NodeName, 'Abbrev. wrong attribute value 2');
    CheckEquals(csLocking, aList.Item(2).NodeName, 'Abbrev. wrong attribute value 3');
    CheckEquals(csQuantity, aList.Item(3).NodeName, 'Abbrev. wrong attribute value 4');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectDescElements;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('descendant::threadsize');
    CheckEquals(7, aList.Length, 'Unabbreviated');
    aList.Free;

    { Unabbrev. fail }
    aList := aNode.SelectNodes('descendant::locking');
    CheckEquals(0, aList.Length, 'Unabbreviated fail');
    aList.Free;

    { Abbrev. }
    aList := aNode.SelectNodes('.//threadsize');
    CheckEquals(7, aList.Length, 'Abbreviated');
    aList.Free;

    { Abbrev. fail }
    aList := aNode.SelectNodes('.//locking');
    CheckEquals(0, aList.Length, 'Abbreviated fail');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAncestor;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { First, go to a leaf node. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('.//make');
    CheckEquals(2, aList.Length, 'Find bottom node');
    aNode := TXpElement(aList.Item(0));
    aList.Free;

    aList := aNode.SelectNodes('ancestor::item');
    CheckEquals(1, aList.Length, 'Unabbreviated');
    aList.Free;

    aList := aNode.SelectNodes('ancestor::catalog');
    CheckEquals(1, aList.Length, 'Unabbreviated');
    aList.Free;

    { Unabbrev. fail }
    aList := aNode.SelectNodes('ancestor::description');
    CheckEquals(0, aList.Length, 'Unabbreviated fail');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAncestor2;
var
  DOM : TXpObjModel;
  oContextNode,
  oNode : TXpNode;
  XMLStr : string;
begin
  XMLStr :=
    '<doc>' + #13#10 +        {level code = 1 }
    '  <sec>' + #13#10 +      {             1.1 }
    '    <sec>' + #13#10 +    {             1.1.1 }
    '      <sec>' + #13#10 +  {             1.1.1.1 }
    '        <p>' + #13#10 +
    '        </p>' + #13#10 +
    '      </sec>' + #13#10 +
    '    </sec>' + #13#10 +
    '  </sec>' + #13#10 +
    '</doc>';
  DOM := TXpObjModel.Create(nil);
  DOM.NormalizeData := True;
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');

    oContextNode := DOM.Document.DocumentElement.SelectSingleNode('/doc/sec/sec/sec/p');
    Assert(oContextNode <> nil, 'test 3');
    CheckEquals('p', oContextNode.NodeName, 'test 4');

    oNode := oContextNode.SelectSingleNode('ancestor::sec[last()]');
    Assert(oNode <> nil, 'test 5');
    CheckEquals('1.1', oNode.LevelCode, 'test 6');

    oNode := oContextNode.SelectSingleNode('ancestor::sec[position()=2]');
    Assert(oNode <> nil, 'test 7');
    CheckEquals('1.1.1', oNode.LevelCode, 'test 8');

    oNode := oContextNode.SelectSingleNode('ancestor::sec[2]');
    Assert(oNode <> nil, 'test 9');
    CheckEquals('1.1.1', oNode.LevelCode, 'test 10');

    oNode := oContextNode.SelectSingleNode('ancestor::sec[1]');
    Assert(oNode <> nil, 'test 11');
    CheckEquals('1.1.1.1', oNode.LevelCode, 'test 12');

    oNode := DOM.Document.DocumentElement.SelectSingleNode('/doc/sec/sec/sec/p/ancestor::sec[1]');
    Assert(oNode <> nil, 'test 13');
    CheckEquals('1.1.1.1', oNode.LevelCode, 'test 14');

    oNode := oContextNode.SelectSingleNode('ancestor::sec[3]');
    Assert(oNode <> nil, 'test 15');
    CheckEquals('1.1', oNode.LevelCode, 'test 16');

  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAncestorOrSelf;
var
  aList : TXpNodeList;
  aNode : TXpElement;
  aStr : string;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { First, go to a leaf node. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('.//make');
    CheckEquals(2, aList.Length, 'Find bottom node');
    aNode := TXpElement(aList.Item(0));
    aList.Free;

    aList := aNode.SelectNodes('ancestor-or-self::make');
    CheckEquals(1, aList.Length, 'Unabbreviated');
    aList.Free;

    aList := aNode.SelectNodes('ancestor-or-self::catalog');
    CheckEquals(1, aList.Length, 'Unabbreviated');
    aList.Free;

    { Unabbrev. fail }
    aList := aNode.SelectNodes('ancestor-or-self::description');
    CheckEquals(0, aList.Length, 'Unabbreviated fail');
    aList.Free;
  finally
    FDoc.Free;
  end;

  aStr := '<Root><L1><L2/></L1></Root>';
  FDoc := TXpObjModel.Create(nil);
  try
    Assert(FDoc.LoadMemory(aStr[1], Length(aStr)), 'Load fail 2');
    { Get element L2. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild);
    CheckEquals('L2', aNode.NodeName, 'position 1a');

    aList := aNode.SelectNodes('ancestor-or-self::*[1]');
    CheckEquals(1, aList.Length, 'position 1b');
    CheckEquals('L2', aList.Item(0).NodeName, 'position 1c');
    aList.Free;

    aList := aNode.SelectNodes('ancestor-or-self::*[last()]');
    CheckEquals(1, aList.Length, 'position 1d');
    CheckEquals('Root', aList.Item(0).NodeName, 'position 1e');
    aList.Free;

    aList := aNode.SelectNodes('ancestor-or-self::*]');
    CheckEquals(3, aList.Length, 'position 1f');
    aList.Free;

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectDescOrSelf;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('descendant-or-self::threadsize');
    CheckEquals(7, aList.Length, 'Unabbreviated');
    aList.Free;

    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('descendant-or-self::item');
    CheckEquals(1, aList.Length, 'Unabbreviated 2');
    aList.Free;

    { Unabbrev. fail }
    aList := aNode.SelectNodes('descendant-or-self::locking');
    CheckEquals(0, aList.Length, 'Unabbreviated fail');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectSelf;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('.');
    CheckEquals(1, aList.Length, 'test length');
    CheckEquals(csItem, aList.Item(0).NodeName, 'test name');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectSelfIf;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('self::item');
    CheckEquals(1, aList.Length, 'success, test length');
    CheckEquals(csItem, aList.Item(0).NodeName, 'succes, test name');
    aList.Free;

    aList := aNode.SelectNodes('self::description');
    CheckEquals(0, aList.Length, 'success, test length');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectDescOfChild;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement);
    aList := aNode.SelectNodes('child::item/descendant::make');
    CheckEquals(6, aList.Length, 'Unabbrev.');
    aList.Free;

    aList := aNode.SelectNodes('child::items/descendant::make');
    CheckEquals(0, aList.Length, 'Unabbrev. 2');
    aList.Free;

    aList := aNode.SelectNodes('child::item/descendant::makes');
    CheckEquals(0, aList.Length, 'Unabbrev. 3');
    aList.Free;

    { Abbrev. }
    aNode := TXpElement(FDoc.DocumentElement);
    aList := aNode.SelectNodes('item//make');
    CheckEquals(6, aList.Length, 'Abbrev.');
    aList.Free;

    aList := aNode.SelectNodes('items//make');
    CheckEquals(0, aList.Length, 'Abbrev. 2');
    aList.Free;

    aList := aNode.SelectNodes('item//makes');
    CheckEquals(0, aList.Length, 'Abbrev. 3');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectGrandchildren;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement);
    aList := aNode.SelectNodes('child::*/child::threadsizelist');
    CheckEquals(3, aList.Length, 'Unabbrev.');
    aList.Free;

    aList := aNode.SelectNodes('child::*/child::make');
    CheckEquals(0, aList.Length, 'Unabbrev. 2');
    aList.Free;

    { Abbrev. }
    aNode := TXpElement(FDoc.DocumentElement);
    aList := aNode.SelectNodes('*/threadsizelist');
    CheckEquals(3, aList.Length, 'Abbrev.');
    aList.Free;

    aList := aNode.SelectNodes('*/make');
    CheckEquals(0, aList.Length, 'Abbrev. 2');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectDocRoot;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0),
        'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild);
    aList := aNode.SelectNodes('/');
    CheckEquals(1, aList.Length, 'Unabbrev. length');
    CheckEquals(DOCUMENT_NODE, aList.Item(0).NodeType,
                'Failed to select document root');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAllDesc;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { Move down to some low-level node that is below the nodes we seek. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild);
    aList := aNode.SelectNodes('/descendant::item');
    CheckEquals(3, aList.Length, 'Unabbrev.');
    aList.Free;

    { Abbrev. }
    aList := aNode.SelectNodes('//item');
    CheckEquals(3, aList.Length, 'Abbrev.');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAllDescWithParent;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { Move down to some low-level node. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild);
    aList := aNode.SelectNodes('/descendant::wheelcompatibility/child::model');
    CheckEquals(6, aList.Length, 'Unabbrev.');
    aList.Free;

    aList := aNode.SelectNodes('/descendant::wheelcompatibility/child::models');
    CheckEquals(0, aList.Length, 'Unabbrev. fail');
    aList.Free;

    { Abbrev. }
    aList := aNode.SelectNodes('//wheelcompatibility/model');
    CheckEquals(6, aList.Length, 'Abbrev.');
    aList.Free;

    aList := aNode.SelectNodes('//wheelcompatibility/models');
    CheckEquals(0, aList.Length, 'Abbrev. fail');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectFirstChild;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild.NextSibling);
    CheckEquals(csThreadSizeList, aNode.nodeName, 'Invalid starting point');
    aList := aNode.SelectNodes('child::threadsize[position()=1]');
    CheckEquals(1, aList.Length, 'Unabbrev.');
    CheckEquals('3/8-24', aList.Item(0).Text, 'Unabbrev: Invalid child text');
    aList.Free;

    { Abbrev. }
    aList := aNode.SelectNodes('threadsize[1]');
    CheckEquals(1, aList.Length, 'Abbrev.');
    CheckEquals('3/8-24', aList.Item(0).Text, 'Abbrev: invalid child text');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectLastChild;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild.NextSibling);
    CheckEquals(csThreadSizeList, aNode.nodeName, 'Invalid starting point');
    aList := aNode.SelectNodes('child::threadsize[position()=last()]');
    CheckEquals(1, aList.Length, 'Unabbrev.');
    CheckEquals('19mm x 1.5', aList.Item(0).Text, 'Unabbrev: invalid child text');
    aList.Free;

    { Abbrev. }
    aList := aNode.SelectNodes('threadsize[last()]');
    CheckEquals(1, aList.Length, 'Unabbrev.');
    CheckEquals('19mm x 1.5', aList.Item(0).Text, 'Abbrev: invalid child text');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectNextToLastChild;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild.NextSibling);
    CheckEquals(csThreadSizeList, aNode.nodeName, 'Invalid starting point');
    aList := aNode.SelectNodes('child::threadsize[position()=last()-1]');
    CheckEquals(1, aList.Length, 'Unabbrev.');
    CheckEquals('12mm x 1.5', aList.Item(0).Text, 'Invalid child text');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAllButFirstChild;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild.NextSibling);
    CheckEquals(csThreadSizeList, aNode.nodeName, 'Invalid starting point');
    aList := aNode.SelectNodes('child::threadsize[position()>1]');
    CheckEquals(6, aList.Length, 'Unabbrev.');
    CheckEquals('7/16-20', aList.Item(0).Text, 'Invalid child text 1');
    CheckEquals('1/2-20', aList.Item(1).Text, 'Invalid child text 2');
    CheckEquals('10mm x 1.25', aList.Item(2).Text, 'Invalid child text 3');
    CheckEquals('12mm x 1.25', aList.Item(3).Text, 'Invalid child text 4');
    CheckEquals('12mm x 1.5', aList.Item(4).Text, 'Invalid child text 5');
    CheckEquals('19mm x 1.5', aList.Item(5).Text, 'Invalid child text 6');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectFollowingSibling;
var
  aNode : TXpElement;
  bValue : Boolean;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0),
        'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { catalog/item/threadsizelist }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild.NextSibling);
    bValue := aNode.SelectBoolean('(following-sibling::shipping or following-sibling::price or following-sibling::wheelcompatibility)');
    Assert(bValue, 'test 1');

    bValue := aNode.SelectBoolean('(following-sibling::sklarp or following-sibling::price or following-sibling::wheelcompatibility)');
    Assert(bValue, 'test 2');

    bValue := aNode.SelectBoolean('(following-sibling::sklarp or following-sibling::glink or following-sibling::wheelcompatibility)');
    Assert(bValue, 'test 3');

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectNextSibling;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('following-sibling::item[position()=1]');
    CheckEquals(1, aList.Length, 'Unabbrev.');
    CheckEquals('Wondernut Blue Fuzzy',
                aList.Item(0).Attributes.Item(0).NodeValue,
                'Invalid sibling');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectPrevSibling;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.LastChild);
    aList := aNode.SelectNodes('preceding-sibling::item[position()=1]');
    CheckEquals(1, aList.Length, 'Unabbrev.');
    CheckEquals('Wondernut Blue Fuzzy',
                aList.Item(0).Attributes.Item(0).NodeValue,
                'Invalid sibling');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectSpecificElement;
var
  aList : TXpNodeList;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { Select the third price element in the DOM. }
    aList := FDoc.DocumentElement.SelectNodes('/descendant::price[position()=3]');
    CheckEquals(1, aList.Length, 'Unabbrev.');
    CheckEquals('5',
                aList.Item(0).Attributes.Item(0).NodeValue,
                'Invalid sibling');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectSpecificElementStage2;
var
  aList : TXpNodeList;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { Select the Make element of the 2nd wheel compatibility element of the
      1st item. }
    aList := FDoc.DocumentElement.SelectNodes('/child::catalog' +
                                              '/child::item[position()=2]' +
                                              '/child::wheelcompatibility[position()=2]' +
                                              '/child::make');
    CheckEquals(1, aList.Length, 'Unabbrev.');
    CheckEquals('Zoospokes',
                aList.Item(0).Text,
                'Unabbrev: Invalid node');
    aList.Free;

    { Abbrev. }
    { Select the Make element of the 2nd wheel compatibility element of the
      1st item. }
    aList := FDoc.DocumentElement.SelectNodes('/catalog' +
                                              '/item[2]' +
                                              '/wheelcompatibility[2]' +
                                              '/make');
    CheckEquals(1, aList.Length, 'Abbrev.');
    CheckEquals('Zoospokes',
                aList.Item(0).Text,
                'Abbrev: invalid node');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectElementWithAttrib;
var
  aList : TXpNodeList;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aList := FDoc.DocumentElement.SelectNodes('child::item[attribute::locking="no"]');
    CheckEquals(2, aList.Length, 'Unabbrev.');
    aList.Free;

    aList := FDoc.DocumentElement.SelectNodes('child::item[attribute::locking="yes"]');
    CheckEquals(1, aList.Length, 'Unabbrev. 2');
    aList.Free;

    { Abbrev. }
    aList := FDoc.DocumentElement.SelectNodes('item[@locking="no"]');
    CheckEquals(2, aList.Length, 'Abbrev.');
    aList.Free;

    aList := FDoc.DocumentElement.SelectNodes('item[@locking="yes"]');
    CheckEquals(1, aList.Length, 'Abbrev. 2');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectSpecificElementWithAttrib;
var
  aList : TXpNodeList;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { Select first item with locking = "no". }
    aList := FDoc.DocumentElement.SelectNodes('child::item[attribute::locking="no"]' +
                                              '[position()=1]');
    CheckEquals(1, aList.Length, 'Unabbrev. 1');
    CheckEquals('Wondernut Blue Fuzzy', aList.Item(0).Attributes.Item(0).NodeValue,
                'Unabbrev. node value 1');
    aList.Free;

    { Select second item with locking = "no". }
    aList := FDoc.DocumentElement.SelectNodes('child::item[attribute::locking="no"]' +
                                              '[position()=2]');
    CheckEquals(1, aList.Length, 'Unabbrev. 2');
    CheckEquals('Basic Lugnut', aList.Item(0).Attributes.Item(0).NodeValue,
                'Unabbrev. node value 2');
    aList.Free;

    { Abbrev. }
    { Select first item with locking = "no". }
    aList := FDoc.DocumentElement.SelectNodes('item[@locking="no"][1]');
    CheckEquals(1, aList.Length, 'Abbrev. 1');
    CheckEquals('Wondernut Blue Fuzzy', aList.Item(0).Attributes.Item(0).NodeValue,
                'Abbrev. node value 1');
    aList.Free;

    { Select second item with locking = "no". }
    aList := FDoc.DocumentElement.SelectNodes('item[@locking="no"][2]');
    CheckEquals(1, aList.Length, 'Abbrev. 2');
    CheckEquals('Basic Lugnut', aList.Item(0).Attributes.Item(0).NodeValue,
                'Abbrev. node value 2');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectSpecificElementIfHasAttrib;
var
  aList : TXpNodeList;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { Select 2nd item if locking attribute is "no" }
    aList := FDoc.DocumentElement.SelectNodes('child::item[position()=2]' +
                                              '[attribute::locking="no"]');
    CheckEquals(1, aList.Length, 'Unabbrev. 1');
    CheckEquals('Wondernut Blue Fuzzy', aList.Item(0).Attributes.Item(0).NodeValue,
                'Unabbrev. node value 1');
    aList.Free;

    { Select 1st item if locking attribute is "no" }
    aList := FDoc.DocumentElement.SelectNodes('child::item[position()=1]' +
                                              '[attribute::locking="no"]');
    CheckEquals(0, aList.Length, 'Unabbrev. 2');
    aList.Free;

    { Abbrev. }
    { Select 2nd item if locking attribute is "no" }
    aList := FDoc.DocumentElement.SelectNodes('item[2][@locking="no"]');
    CheckEquals(1, aList.Length, 'Abbrev. 1');
    CheckEquals('Wondernut Blue Fuzzy', aList.Item(0).Attributes.Item(0).NodeValue,
                'Unabbrev. node value 1');
    aList.Free;

    { Select 1st item if locking attribute is "no" }
    aList := FDoc.DocumentElement.SelectNodes('item[1][@locking="no"]');
    CheckEquals(0, aList.Length, 'Abbrev. 2');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectChildIfHasChildWithStringValue;
var
  aList : TXpNodeList;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { Select the items having a keyword in their keyword list with text equal to
      'anti-theft'. }
    aList := FDoc.DocumentElement.SelectNodes('child::item/child::keywordlist'+
                                              '[child::keyword=' +
                                              QuotedStr('anti-theft') + ']');
    CheckEquals(1, aList.Length, 'Unabbrev. 1');
    aList.Free;

    { Select the items having a keyword in their keyword list with text equal to
      'duralloy'. }
    aList := FDoc.DocumentElement.SelectNodes('child::item/child::keywordlist'+
                                              '[child::keyword=' +
                                              QuotedStr('duralloy') + ']');
    CheckEquals(0, aList.Length, 'Unabbrev. 2');
    aList.Free;

    { Select the items having a keyword in their keyword list with text equal to
      'lug nut'. }
    aList := FDoc.DocumentElement.SelectNodes('child::item/child::keywordlist'+
                                              '[child::keyword=' +
                                              QuotedStr('lug nut') + ']');
    CheckEquals(3, aList.Length, 'Unabbrev. 3');
    aList.Free;

    { Abbrev. }
    { Select the items having a keyword in their keyword list with text equal to
      'anti-theft'. }
    aList := FDoc.DocumentElement.SelectNodes('item/keywordlist'+
                                              '[keyword=' +
                                              QuotedStr('anti-theft') + ']');
    CheckEquals(1, aList.Length, 'Abbrev. 1');
    aList.Free;

    { Select the items having a keyword in their keyword list with text equal to
      'duralloy'. }
    aList := FDoc.DocumentElement.SelectNodes('item/keywordlist'+
                                              '[keyword=' +
                                              QuotedStr('duralloy') + ']');
    CheckEquals(0, aList.Length, 'Abbrev. 2');
    aList.Free;

    { Select the items having a keyword in their keyword list with text equal to
      'lug nut'. }
    aList := FDoc.DocumentElement.SelectNodes('item/keywordlist'+
                                              '[keyword=' +
                                              QuotedStr('lug nut') + ']');
    CheckEquals(3, aList.Length, 'Abbrev. 3');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectChildIfHasChild;
var
  aList : TXpNodeList;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    { Select the items having one or more keywords in their keyword list. }
    aList := FDoc.DocumentElement.SelectNodes('child::item/child::keywordlist'+
                                              '[child::keyword]');
    CheckEquals(3, aList.Length, 'Unabbrev. 1');
    aList.Free;

    { Select the items having one or more keyword lists within their keyword
      list. }
    aList := FDoc.DocumentElement.SelectNodes('child::item/child::keywordlist'+
                                              '[child::keywordlist]');
    CheckEquals(0, aList.Length, 'Unabbrev. 2');
    aList.Free;

    { Abbrev. }
    { Select the items having one or more keywords in their keyword list. }
    aList := FDoc.DocumentElement.SelectNodes('item/keywordlist[keyword]');
    CheckEquals(3, aList.Length, 'Abbrev. 1');
    aList.Free;

    { Select the items having one or more keyword lists within their keyword
      list. }
    aList := FDoc.DocumentElement.SelectNodes('item/keywordlist[keywordlist]');
    CheckEquals(0, aList.Length, 'Abbrev. 2');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectOr;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Position to the first item. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);

    { Unabbrev. }
    { Select the wheel and car compatibility elements of the current node. }
    aList := aNode.SelectNodes('child::*[self::wheelcompatibility ' +
                               'or self::carcompatibility]');
    CheckEquals(4, aList.Length, 'Unabbrev. 1');
    aList.Free;

    { Select the people compatibility and car compatibility elements of the
      current node. }
    aList := aNode.SelectNodes('child::*[self::peoplecompatibility ' +
                               'or self::carcompatibility]');
    CheckEquals(2, aList.Length, 'Unabbrev. 2');
    aList.Free;

    { Select the bird and mud compatibility elements of the
      current node. }
    aList := aNode.SelectNodes('child::*[self::birdcompatibility ' +
                               'or self::mudcompatibility]');
    CheckEquals(0, aList.Length, 'Unabbrev. 3');
    aList.Free;

    { Abbrev. }
    aNode := TXpElement(FDoc.DocumentElement);

    { Select the items containing wheel and car compatibility elements. }
    aList := aNode.SelectNodes('item[wheelcompatibility ' +
                               'or carcompatibility]');
    CheckEquals(3, aList.Length, 'Abbrev. 1');
    aList.Free;

    { Select the items containing people and car compatibility elements. }
    aList := aNode.SelectNodes('item[peoplecompatibility ' +
                               'or carcompatibility]');
    CheckEquals(3, aList.Length, 'Abbrev. 2');
    aList.Free;

    { Select the items containing bird and mud compatibility elements. }
    aList := aNode.SelectNodes('item[birdcompatibility ' +
                               'or mudcompatibility]');
    CheckEquals(0, aList.Length, 'Abbrev. 3');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectOrLast;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  try
    FDoc := LoadDoc(csDocLug);
    Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

    { Position to the first item. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);

    { Unabbrev. }
    { Select the wheel and car compatibility elements of the current node. }
    aList := aNode.SelectNodes('child::*[self::wheelcompatibility ' +
                               'or self::carcompatibility]' +
                               '[position()=last()]');
    CheckEquals(1, aList.Length, 'Unabbrev. 1');
    CheckEquals('carcompatibility', aList.Item(0).NodeName,
                'Unabbrev. NodeName 1');
    CheckEquals(csYogu, aList.Item(0).Attributes.Item(0).NodeValue,
                'Unabbrev. Attrib value 1');
    aList.Free;

    { Select the people compatibility and car compatibility elements of the
      current node. }
    aList := aNode.SelectNodes('child::*[self::peoplecompatibility ' +
                               'or self::carcompatibility]' +
                               '[position()=last()]');
    CheckEquals(1, aList.Length, 'Unabbrev. 2');
    CheckEquals('carcompatibility', aList.Item(0).NodeName,
                'Unabbrev. NodeName 2');
    CheckEquals(csYogu, aList.Item(0).Attributes.Item(0).NodeValue,
                'Unabbrev. Attrib value 2');
    aList.Free;

    { Select the bird and mud compatibility elements of the
      current node. }
    aList := aNode.SelectNodes('child::*[self::birdcompatibility ' +
                               'or self::mudcompatibility]' +
                               '[position()=last()]');
    CheckEquals(0, aList.Length, 'Unabbrev. 3');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectParent;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Abbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild);
    aList := aNode.SelectNodes('..');
    CheckEquals(1, aList.Length, 'Abbrev. length');
    CheckEquals(csItem, aList.Item(0).NodeName, 'Abbrev. nodename');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectParentAttrib;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Abbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild.FirstChild);
    aList := aNode.SelectNodes('../@name');
    CheckEquals(1, aList.Length, 'Abbrev. length');
    CheckEquals(csWondernutChrome, aList.Item(0).NodeValue, 'Abbrev. nodevalue');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectFirstDesc;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Abbrev. }
    aNode := TXpElement(FDoc.DocumentElement);
    aList := aNode.SelectNodes('/descendant::item[1]');
    CheckEquals(1, aList.Length, 'Unabbrev. length');
    CheckEquals(csWondernutChrome, aList.Item(0).Attributes.Item(0).NodeValue,
                'Unabbrev. nodevalue');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testSelectAllByPos;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Abbrev. }
    aNode := TXpElement(FDoc.DocumentElement);
    aList := aNode.SelectNodes('//threadsize[1]');
    CheckEquals(3, aList.Length, 'Abbrev. length');
    CheckEquals('3/8-24', aList.Item(0).Text, 'Abbrev. 1 text');
    CheckEquals('10mm x 1.25', aList.Item(1).Text, 'Abbrev. 2 text');
    CheckEquals('3/8-24', aList.Item(2).Text, 'Abbrev. 3 text');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{====================================================================}


{===Operator tests===================================================}
procedure TXpXPathTests.testDiv;
var
  sTxt : string;
begin
  sTxt := '<Root Rate="10"/>' ;
  FDoc := TXpObjModel.Create(nil);
  try
    Assert(FDoc.LoadMemory(sTxt[1], Length(sTxt)), 'Load failure');

    CheckEquals('0.1',
                FDoc.Document.DocumentElement.SelectString('/Root/@Rate div 100'),
                'Test 1');
    CheckEquals('0.1',
                FDoc.Document.DocumentElement.SelectString('(/Root/@Rate) div 100'),
                'Test 2');
    CheckEquals('0.11',
                FDoc.Document.DocumentElement.SelectString('(/Root/@Rate+1) div 100'),
                'Test 3');
    CheckEquals('0.1',
                FDoc.Document.DocumentElement.SelectString('(/Root/@Rate+0) div 100'),
                'Test 4');
  { TODO:: Need to handle bad grammar in a better way. }
  //  CheckEquals('',
  //              FDoc.Document.DocumentElement.SelectString('(/Root/@Rate+) div 100'),
  //              'Test 5');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testAnd;
var
  aList : TXpNodeList;
  aNode : TXpElement;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<content>' +
    '  <header lang="de">test1' +
    '  </header>' +
    '  <header lang="us">test1' +
    '  </header>' +
    '</content>';

  FDoc := LoadMem(XMLStr);
  try
    Check(Assigned(FDoc) and
          (FDoc.ErrorCount = 0), 'Could not load XMLStr: ' + XMLStr);
    aNode := FDoc.DocumentElement;

    aList := aNode.SelectNodes('//header[.="test1" $and$ @lang="de"]');
    try
      CheckEquals(1, aList.Length, 'Test 1');
      CheckEquals('de', TXpElement(aList.Item(0)).GetAttribute('lang'), 'test 2');
    finally
      aList.Free;
    end;
  finally
    FDoc.Free;
  end;

end;
{--------}
procedure TXpXPathTests.testEqualityExpr;
  { Bug 3517 }
var
  Node : TXpNode;
  Nodes : TXpNodeList;
  XMLStr : string;
begin

  XMLStr :=
    '<?xml version="1.0" encoding="ISO-8859-1"?>' +
    '<TEXTE>' +
    '  <CONTENT MODIFYDATE="17.07.2002 14:50">TESTFILE</CONTENT>' +
    '  <TEXTELEMENT>' +
    '    <DLGID>165</DLGID>' +
    '    <DLGID>843</DLGID>' +
    '    <SHORTLINKTEXT>F1</SHORTLINKTEXT>' +
    '  </TEXTELEMENT>' +
    '  <TEXTELEMENT>' +
    '    <DLGID>170</DLGID>' +
    '    <DLGID>200</DLGID>' +
    '    <SHORTLINKTEXT>F2</SHORTLINKTEXT>' +
    '  </TEXTELEMENT>' +
    '  <TEXTELEMENT>' +
    '    <DLGID>10</DLGID>' +
    '    <DLGID>943</DLGID>' +
    '    <SHORTLINKTEXT>F3</SHORTLINKTEXT>' +
    '  </TEXTELEMENT>' +
    '  <TEXTELEMENT>' +
    '    <DLGID>8</DLGID>' +
    '    <DLGID>150</DLGID>' +
    '    <DLGID>154</DLGID>' +
    '    <SHORTLINKTEXT>F4</SHORTLINKTEXT>' +
    '  </TEXTELEMENT>' +
    '  <TEXTELEMENT>' +
    '    <DLGID>9</DLGID>' +
    '    <DLGID>11</DLGID>' +
    '    <DLGID>943</DLGID>' +
    '    <SHORTLINKTEXT>F5</SHORTLINKTEXT>' +
    '  </TEXTELEMENT>' +
    '</TEXTE>';

  FDoc := TXpObjModel.Create(nil);
  FDoc.RaiseErrors := False;
  try
    FDoc.LoadMemory(XMLStr[1], Length(XMLStr));
    CheckEquals(0, FDoc.Errors.Count, 'Error during doc load: ' + FDoc.Errors.Text);
    Nodes := FDoc.DocumentElement.SelectNodes('/TEXTE/TEXTELEMENT[DLGID=9]');
    try
      CheckEquals(1, Nodes.Length, 'More nodes matched than expected');
      Node := FDoc.DocumentElement.ChildNodes.Item(5);
      Assert(Node = Nodes.Item(0), 'Did not find expected node');
    finally
      Nodes.Free;
    end;
  finally
    FDoc.Free;
  end;
end;
{====================================================================}

{===Function tests===================================================}
procedure TXpXPathTests.testFuncBoolean;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    { Test boolean expression. }
    Check(FDoc.DocumentElement.SelectBoolean('boolean(true())'), 'boolean(true)');
    Check(not FDoc.DocumentElement.SelectBoolean('boolean(false())'), 'boolean(false)');

    { Test numeric expression. }
    Check(FDoc.DocumentElement.SelectBoolean('boolean(1)'), 'boolean(1)');
    Check(not FDoc.DocumentElement.SelectBoolean('boolean(0)'), 'boolean(0)');

    { Test string expression. }
    aList := TXpElement(FDoc.DocumentElement.FirstChild).SelectNodes
                                                   ('descendant::threadsize[1]');
    CheckEquals(1, aList.Length, 'boolean string expression get node');
    aNode := TXpElement(aList.Item(0));
    aList.Free;

    Check(aNode.SelectBoolean('boolean(string(self::)="3/8-24")'),
          'boolean(string(self::)="3/8-24")');
    Check(not aNode.SelectBoolean('boolean(string(self::)="12mm x 1.25")'),
          'boolean(string(self::)="12mm x 1.25")');

    { Test conversion of attribute value to boolean. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    Check(anode.SelectBoolean('boolean(@name)'), 'boolean(@name)');

    { Test nodeset expression. }
    Check(FDoc.DocumentElement.SelectBoolean('boolean(//item)'), 'boolean(//item)');
    Check(not FDoc.DocumentElement.SelectBoolean('boolean(//items)'), 'boolean(//items)');

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncCeiling;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    Check(FDoc.DocumentElement.SelectNumber('ceiling(1.0)') = 1, 'ceiling(1.0)');
    Check(FDoc.DocumentElement.SelectNumber('ceiling(1.6)') = 2, 'ceiling(1.6)');
    Check(FDoc.DocumentElement.SelectNumber('ceiling(17 div 3)') = 6, 'ceiling(17 div 3)');
    Check(FDoc.DocumentElement.SelectNumber('ceiling(-3.0)') = -3, 'ceiling(-3.0)');
    Check(FDoc.DocumentElement.SelectNumber('ceiling(-8.2)') = -8, 'ceiling(-8.2)');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncConcat;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    CheckEquals('chromeyes4', aNode.SelectString('concat(@finish, @locking, @quantity)'),
                'Concat 1');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncContains;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    Check(aNode.SelectBoolean('contains(@name, "Wondernut")'), 'Contains 1');
    Check(not aNode.SelectBoolean('contains(@name, "Blob")'), 'Contains 2');
    Check(aNode.SelectBoolean('contains(@name, " Chro")'), 'Contains 3');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncCount;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    Check(FDoc.DocumentElement.SelectNumber('count(//item)') = 3, 'Count 1');
    Check(FDoc.DocumentElement.SelectNumber('count(//items)') = 0, 'Count 2');
    Check(FDoc.DocumentElement.SelectNumber('count(//threadsize)') = 18, 'Count 3');
    { Count the # of attributes in the first items element. }
    Check(TXpElement(FDoc.DocumentElement.FirstChild).SelectNumber('count(@*)') = 4,
          'Count 4');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncFalse;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    Check(not FDoc.DocumentElement.SelectBoolean('false()'));
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncFloor;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    Check(FDoc.DocumentElement.SelectNumber('floor(1.0)') = 1, 'floor(1.0)');
    Check(FDoc.DocumentElement.SelectNumber('floor(1.6)') = 1, 'floor(1.0)');
    Check(FDoc.DocumentElement.SelectNumber('floor(17 div 3)') = 5, 'floor(17 div 3)');
    Check(FDoc.DocumentElement.SelectNumber('floor(-3.0)') = -3, 'floor(-3.0)');
    Check(FDoc.DocumentElement.SelectNumber('floor(-8.2)') = -9, 'floor(-8.2)');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncFunctionAvailable;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    { Test system functions. }
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''generate-id'')'), 'fail 1');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''boolean'')'), 'boolean');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''ceiling'')'), 'ceiling');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''concat'')'), 'concat');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''contains'')'), 'contains');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''count'')'), 'count');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''false'')'), 'false');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''floor'')'), 'floor');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''function-available'')'), 'functionavail');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''generate-id'')'), 'generateID');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''id'')'), 'ID');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''lang'')'), 'lang');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''last'')'), 'last');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''local-name'')'), 'localname');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''name'')'), 'name');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''namespace-uri'')'), 'namespaceURI');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''normalize-space'')'), 'normalizespace');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''not'')'), 'not');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''number'')'), 'number');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''position'')'), 'position');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''round'')'), 'round');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''starts-with'')'), 'startswith');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''string'')'), 'string');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''string-length'')'), 'stringlength');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''substring'')'), 'substring');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''substring-after'')'), 'substringafter');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''substring-before'')'), 'substringbefore');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''sum'')'), 'sum');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''system-property'')'), 'systemproperty');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''translate'')'), 'translate');
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''true'')'), 'true');

    { Test for extensions. }
    FDoc.OnFunctionAvailable := OnFunctionAvail;
    Check(FDoc.DocumentElement.SelectBoolean('function-available(''detonate'')'), 'extension 1');
    Check(not FDoc.DocumentElement.SelectBoolean('function-available(''peck'')'), 'extension 2');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncGenerateID;
var
  anID : DOMString;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    { If argument is omitted then the target node is the context node. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    anID := aNode.SelectString('generate-id(.)');
    CheckNotEquals('', anID, 'Invalid ID');
    CheckEquals(anID, aNode.SelectString('generate-id()'), 'Context node');

    { If the input node set contains more than one node then the target node
      is the one that is first in document order. }
    CheckEquals(anID, FDoc.DocumentElement.SelectString('generate-id(item)'), 'First node');

    CheckNotEquals(anID, TXpElement(FDoc.DocumentElement.LastChild).SelectString
                           ('generate-id(.)'), 'Last node')

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncID;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocID);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocID);
  try
    aList := FDoc.DocumentElement.SelectNodes('id(''24'')');
    CheckEquals(1, aList.Length, 'ID 1');
    Check(FDoc.DocumentElement.FirstChild = aList.Item(0), 'Wrong node selected 1');
    aList.Free;

    aList := FDoc.DocumentElement.SelectNodes('id(''19'')');
    CheckEquals(1, aList.Length, 'ID 2');
    Check(FDoc.DocumentElement.FirstChild.NextSibling.NextSibling = aList.Item(0), 'Wrong node selected 2');
    aList.Free;

    aList := FDoc.DocumentElement.SelectNodes('id(''22'')');
    CheckEquals(1, aList.Length, 'ID 3');
    Check(FDoc.DocumentElement.FirstChild.NextSibling.NextSibling.NextSibling = aList.Item(0), 'Wrong node selected 3');
    aList.Free;

    aList := FDoc.DocumentElement.SelectNodes('id(''24 10'')');
    CheckEquals(2, aList.Length, 'ID 4');
    Check(FDoc.DocumentElement.FirstChild = aList.Item(0), 'Wrong node selected 4');
    aList.Free;

    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('id(@a)');
    CheckEquals(1, aList.Length, 'ID 5');
    aList.Free;

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncLang;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLugLang);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLugLang);
  try
    aNode := TXpElement(FDoc.Document.DocumentElement.FirstChild.FirstChild);
    Check(aNode.SelectBoolean('lang("fr")'), 'fr-descrip');
    Check(not aNode.SelectBoolean('lang("en")'), 'en-descrip');
    aNode := TXpElement(aNode.ParentNode);
    Check(aNode.SelectBoolean('lang("en")'), 'en-item');
    Check(aNode.SelectBoolean('lang("EN-ca")'), 'en-ca-item');
    Check(not aNode.SelectBoolean('lang("en-us")'), 'en-us-item');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncLast;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocCountries);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocCountries);
  try
    aNode := FDoc.Document.DocumentElement;
    aList := aNode.SelectNodes('country[last()]');
    CheckEquals(1, aList.Length, 'length - test 1');
    CheckEquals('Nigeria', aList.Item(0).Attributes.Item(0).NodeValue, 'value - test1');
    aList.Free;

    aList := aNode.SelectNodes('country[@continent="Europe"][last()]');
    CheckEquals(1, aList.Length, 'length - test 2');
    CheckEquals('Poland', aList.Item(0).Attributes.Item(0).NodeValue, 'value - test2');
    aList.Free;

    aList := aNode.SelectNodes('country[@continent="Europe"][last() - 1]');
    CheckEquals(1, aList.Length, 'length - test 3');
    CheckEquals('Italy', aList.Item(0).Attributes.Item(0).NodeValue, 'value - test3');
    aList.Free;

    aList := aNode.SelectNodes('country[@continent="Africa"][position() != last()]');
    CheckEquals(2, aList.Length, 'length - test 4');
    CheckEquals('Egypt', aList.Item(0).Attributes.Item(0).NodeValue, 'value - test4a');
    CheckEquals('Libya', aList.Item(1).Attributes.Item(0).NodeValue, 'value - test4b');
    aList.Free;
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncLocalName;
var
  oNode : TXpElement;
  oFirstItem : TXpElement;
begin
  FDoc := LoadDoc(csDocLugLocal);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLugLocal);
  try
    { Root node - return empty string since root node does not have an expanded
      name. }
    oNode := FDoc.Document.DocumentElement;
    CheckEquals('', oNode.SelectString('local-name(/)'), 'root');

    { Element - return element name, after any colon }
    oFirstItem := TXpElement(oNode.FirstChild.NextSibling);
    CheckEquals('item', oFirstItem.SelectString('local-name()'), 'element 1');

    { Element - empty node set }
    CheckEquals('', oFirstItem.SelectString('local-name(turkeyfeathers)'), 'element 2');

    { Attribute - return attribute name, after any colon }
    CheckEquals('name', oFirstItem.SelectString('local-name(@*)'), 'attrib');

    { Text - return empty string }
    CheckEquals('', oFirstItem.SelectString('local-name(description::text)'), 'text');

    { Processing instruction - return the target used in the PI to identify the
        application for which it is intended }
    { TODO:: At the time of this writing, I do not know how to select this type
      of node. }

    { Comment - return empty string }
    { TODO:: At the time of this writing, I do not know how to select this type
      of node. }

    { Namespace - The namespace prefix; or the empty string if this is the
        default namespace }
    CheckEquals('', oNode.SelectString('local-name(@*)'), 'default namespace');
    CheckEquals('db', oFirstItem.SelectString('local-name(@xmlns:db)'), 'namespace');

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncName;
var
  oNode : TXpElement;
  oFirstItem : TXpElement;
begin
  FDoc := LoadDoc(csDocLugLocal);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLugLocal);
  try
    { Root node - return empty string since root node does not have an expanded
      name. }
    oNode := FDoc.Document.DocumentElement;
    CheckEquals('', oNode.SelectString('name(/)'), 'root');

    { Element - return entire element name }
    oFirstItem := TXpElement(oNode.FirstChild.NextSibling);
    CheckEquals('db:item', oFirstItem.SelectString('name()'), 'element 1');

    { Element - empty node set }
    CheckEquals('', oFirstItem.SelectString('name(turkeyfeathers)'), 'element 2');

    { Attribute - return entire attribute name }
    CheckEquals('db:name', oFirstItem.SelectString('name(@db:name)'), 'attrib');

    { Text - return empty string }
    CheckEquals('', oFirstItem.SelectString('name(description::text)'), 'text');

    { Processing instruction - return the target used in the PI to identify the
        application for which it is intended }
    { TODO:: At the time of this writing, I do not know how to select this type
      of node. }

    { Comment - return empty string }
    { TODO:: At the time of this writing, I do not know how to select this type
      of node. }

    { Namespace - The namespace prefix; or the empty string if this is the
        default namespace }
    CheckEquals('', oNode.SelectString('name(@*)'), 'default namespace');
    CheckEquals('db', oFirstItem.SelectString('name(@xmlns:db)'), 'namespace');

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncNamespaceURI;
const
  csURIdefault = 'http://www.turbopower.com';
  csURIdb = 'http://www.turbopower.com/db';
var
  oNode : TXpElement;
  oFirstItem : TXpElement;
begin
  FDoc := LoadDoc(csDocLugLocal);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLugLocal);
  try
    { Root node - return empty string since root node does not have an expanded
      name. }
    oNode := FDoc.Document.DocumentElement;
    CheckEquals('', oNode.SelectString('namespace-uri(/)'), 'root');

    { Element - return namespace URI }
    oFirstItem := TXpElement(oNode.FirstChild.NextSibling);
    CheckEquals(csURIdb, oFirstItem.SelectString('namespace-uri()'), 'element 1');
    CheckEquals(csURIdefault, oNode.SelectString('namespace-uri()'), 'doc element');

    { Element - empty node set }
    CheckEquals('', oFirstItem.SelectString('namespace-uri(turkeyfeathers)'), 'element 2');

    { Attribute - return namespace URI }
    CheckEquals(csURIdb, oFirstItem.SelectString('namespace-uri(@db:name)'), 'attrib 1');
    CheckEquals(csURIdefault, oFirstItem.SelectString('namespace-uri(@name)'), 'attrib 2');

    { Text - return empty string }
    CheckEquals('', oFirstItem.SelectString('namespace-uri(description::text)'), 'text');

    { Namespace - return empty string }
    CheckEquals('', oNode.SelectString('namespace-uri(@*)'), 'default namespace');
    CheckEquals('', oFirstItem.SelectString('namespace-uri(@xmlns:db)'), 'namespace');

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncNormalizeSpace;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.Document.DocumentElement;
    CheckEquals('', aNode.SelectString('normalize-space("")'), 'test 1');
    CheckEquals('', aNode.SelectString('normalize-space(" ")'), 'test 2');
    CheckEquals('', aNode.SelectString('normalize-space("  ")'), 'test 3');
    CheckEquals('test', aNode.SelectString('normalize-space(" test")'), 'test 4');
    CheckEquals('test', aNode.SelectString('normalize-space("test ")'), 'test 5');
    CheckEquals('test', aNode.SelectString('normalize-space("  test  ")'), 'test 6');

    CheckEquals('test test', aNode.SelectString('normalize-space("  test test  ")'), 'test 1');

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncNot;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.Document.DocumentElement;
    Check(not aNode.SelectBoolean('not(true())'), 'test 1');
    Check(aNode.SelectBoolean('not(false())'), 'test 2');

    { Does current node have any children? }
    Check(not aNode.SelectBoolean('not(node())'), 'test 3');
    Check(TXpElement(aNode.FirstChild.LastChild).SelectBoolean('not(node())'), 'test 4');

    { Current node has no parent? }
    Check(not TXpElement(aNode.FirstChild.LastChild).SelectBoolean('not(parent::*)'), 'test 6');

    { Note: Could implement more tests as shown on pg. 497 of XSLT Programmer's
      Reference. }

  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncNumber;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.Document.DocumentElement;
    CheckEquals(12.3, aNode.SelectNumber('number(12.3)'), 0.0, 'test 1');
    CheckEquals(12.3, aNode.SelectNumber('number("12.3")'), 0.0, 'test 2');
    CheckEquals(1.0, aNode.SelectNumber('number(true())'), 0.0, 'test 3');
    CheckEquals(0.0, aNode.SelectNumber('number(false())'), 0.0, 'test 4');
    CheckEquals(0.0, aNode.SelectNumber('number("")'), 0.0, 'test 5');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncPosition;
var
  aList : TXpNodeList;
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);

  try
    { Unabbrev. }
    aNode := TXpElement(FDoc.DocumentElement.FirstChild);
    aList := aNode.SelectNodes('following-sibling::item[position()=1]');
    CheckEquals(1, aList.Length, 'Unabbrev.');
    CheckEquals('Wondernut Blue Fuzzy',
                aList.Item(0).Attributes.Item(0).NodeValue,
                'Invalid sibling');
    aList.Free;
  finally
    FDoc.Free;
  end;

  FDoc := LoadDoc(csDocCountries);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocCountries);
  try
    aNode := FDoc.DocumentElement;
    aList := aNode.SelectNodes('country[@continent="Africa"][position() != last()]');
    CheckEquals(2, aList.Length, 'length - test 4');
    CheckEquals('Egypt', aList.Item(0).Attributes.Item(0).NodeValue, 'value - test4a');
    CheckEquals('Libya', aList.Item(1).Attributes.Item(0).NodeValue, 'value - test4b');
    aList.Free;
  finally
    FDoc.Free;
  end;

end;
{--------}
procedure TXpXPathTests.testFuncRound;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.DocumentElement;
    CheckEquals(1, aNode.SelectInteger('round(1)'), 'test 1');
    CheckEquals(1, aNode.SelectInteger('round(1.1)'), 'test 2');
    CheckEquals(1, aNode.SelectInteger('round(1.49)'), 'test 3');
    CheckEquals(1, aNode.SelectInteger('round(1.499)'), 'test 4');
    CheckEquals(2, aNode.SelectInteger('round(1.5)'), 'test 5');
    CheckEquals(2, aNode.SelectInteger('round(1.51)'), 'test 6');
    CheckEquals(2, aNode.SelectInteger('round(1.99)'), 'test 7');
    CheckEquals(2, aNode.SelectInteger('round(2)'), 'test 8');
    CheckEquals(0, aNode.SelectInteger('round(.5)'), 'test 9');
    CheckEquals(0, aNode.SelectInteger('round(-.5)'), 'test 10');
    CheckEquals(-1, aNode.SelectInteger('round(-1.49)'), 'test 11');
    CheckEquals(-2, aNode.SelectInteger('round(-1.5)'), 'test 12');
    CheckEquals(-2, aNode.SelectInteger('round(-1.6)'), 'test 13');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncStartsWith;
var
  aList : TXpNodeList;
  aNode : TXpElement;
  XMLStr : string;
begin
  FDoc := LoadDoc(csDocLug);
  try
    Check(Assigned(FDoc) and
          (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
    aNode := FDoc.DocumentElement;

    Check(aNode.SelectBoolean('starts-with("$17.30","$")'), 'test 1');
    Check(aNode.SelectBoolean('starts-with("$17.30","$17.")'), 'test 2');

    Check(not aNode.SelectBoolean('starts-with("b1", "a")'), 'test 2a');

    { If the 2nd string is empty then return true. }
    Check(aNode.SelectBoolean('starts-with("$17.30","")'), 'test 3');

    { If the first string is empty, return True only if the second string is
      empty. }
    Check(aNode.SelectBoolean('starts-with("","")'), 'test 4');
    Check(not aNode.SelectBoolean('starts-with("","$")'), 'test 5');

    { If the 2nd string is longer than the first then the result is always
      false. }
    Check(not aNode.SelectBoolean('starts-with("abc","abcd")'), 'test 6');

    Check(aNode.SelectBoolean('starts-with("abcdef","abc")'), 'test 7');
    Check(not aNode.SelectBoolean('starts-with("abcdef","ABC")'), 'test 8');
  finally
    FDoc.Free;
  end;

  XMLStr :=
    '<?xml version="1.0"?>' +
    '<content>' +
    '  <datasets>' +
    '    <name link="a1" >xxx</name>' +
    '    <name link="a2">yyy</name>' +
    '    <name link="b1">zzz</name>' +
    '  </datasets>' +
    '</content>';

  FDoc := LoadMem(XMLStr);
  try
    Check(Assigned(FDoc) and
          (FDoc.ErrorCount = 0), 'Could not load XMLStr: ' + XMLStr);
    aNode := FDoc.DocumentElement;

    aList := aNode.SelectNodes('//*[starts-with(@link,"a")]');
    try
      CheckEquals(2, aList.Length, 'Test 9');
      CheckEquals('a1', TXpElement(aList.Item(0)).GetAttribute('link'), 'test 10');
      CheckEquals('a2', TXpElement(aList.Item(1)).GetAttribute('link'), 'test 11');
    finally
      aList.Free;
    end;

    aList := aNode.SelectNodes('//*[starts-with(@link,"a1")]');
    try
      CheckEquals(1, aList.Length, 'Test 12');
      CheckEquals('a1', TXpElement(aList.Item(0)).GetAttribute('link'), 'test 13');
    finally
      aList.Free;
    end;

    aList := aNode.SelectNodes('//*[starts-with(@link,"a2")]');
    try
      CheckEquals(1, aList.Length, 'Test 14');
      CheckEquals('a2', TXpElement(aList.Item(0)).GetAttribute('link'), 'test 15');
    finally
      aList.Free;
    end;

    aList := aNode.SelectNodes('//*[starts-with(@link,"b")]');
    try
      CheckEquals(1, aList.Length, 'Test 16');
      CheckEquals('b1', TXpElement(aList.Item(0)).GetAttribute('link'), 'test 17');
    finally
      aList.Free;
    end;

    aList := aNode.SelectNodes('//*[starts-with(@link,"b1")]');
    try
      CheckEquals(1, aList.Length, 'Test 18');
      CheckEquals('b1', TXpElement(aList.Item(0)).GetAttribute('link'), 'test 19');
    finally
      aList.Free;
    end;


    aList := aNode.SelectNodes('//*[starts-with(@link,"b1")');
    try
      CheckEquals(1, aList.Length, 'Test 18');
      CheckEquals('b1', TXpElement(aList.Item(0)).GetAttribute('link'), 'test 19');
    finally
      aList.Free;
    end;

  finally
    FDoc.Free;
  end;

end;
{--------}
procedure TXpXPathTests.testFuncString;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.DocumentElement;
    CheckEquals('false', aNode.SelectString('string(false()))'), 'test 1');
    CheckEquals('true', aNode.SelectString('string(true())'), 'test 2');
    CheckEquals('zoom!', aNode.SelectString('string("zoom!")'), 'test 3');
    CheckEquals('-1', aNode.SelectString('string(-1)'), 'test 4');
    CheckEquals('0', aNode.SelectString('string(0)'), 'test 5');
    CheckEquals('1', aNode.SelectString('string(1)'), 'test 6');
    CheckEquals('1.101', aNode.SelectString('string(1.101)'), 'test 7');
    CheckEquals('-1.999', aNode.SelectString('string(-1.999)'), 'test 8');
    { TODO:: Could add tests for node sets. }
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncStringLength;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.DocumentElement;
    CheckEquals(3, aNode.SelectInteger('string-length("123")'), 'test 1');
    CheckEquals(0, aNode.SelectInteger('string-length("")'), 'test 2');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncSubstring;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.Document.DocumentElement;
    CheckEquals(':', aNode.SelectString('substring("final:out", 6, 1)'), 'test1');
    CheckEquals('out', aNode.SelectString('substring("final:out", 7)'), 'test2');
    CheckEquals('', aNode.SelectString('substring("final:out", 10, 10)'), 'test3');
    CheckEquals('final:out', aNode.SelectString('substring("final:out", 1, 100)'), 'test4');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncSubstringAfter;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.Document.DocumentElement;
    CheckEquals('yes', aNode.SelectString('substring-after("print=yes","=")'),
                'test 1');
    CheckEquals('s', aNode.SelectString('substring-after("print=yes","ye")'),
                'test 2');
    CheckEquals('', aNode.SelectString('substring-after("print=yes", "yes")'),
                'test 3');
    CheckEquals('', aNode.SelectString('substring-after("print=yes", "no")'),
                'test 4');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncSubstringBefore;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.Document.DocumentElement;
    CheckEquals('print', aNode.SelectString('substring-before("print=yes","=")'),
                'test 1');
    CheckEquals('pri', aNode.SelectString('substring-before("print=yes","nt")'),
                'test 2');
    CheckEquals('', aNode.SelectString('substring-before("print=yes", "print")'),
                'test 3');
    CheckEquals('', aNode.SelectString('substring-before("print=yes", "no")'),
                'test 4');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncSum;
var
  DOM  : TXpObjModel;
  TempStr : string;
begin
  TempStr := '<results group="A">' +
                '<match>' +
                  '<date>10-Jun-98</date>' +
                  '<team score="2">Brazil</team>' +
                  '<team score="1">Scotland</team>' +
                '</match>' +
                '<match>' +
                  '<date>10-Jun-98</date>' +
                  '<team score="2">Morocco</team>' +
                  '<team score="2">Norway</team>' +
                '</match>' +
                '<match>' +
                  '<date>16-Jun-98</date>' +
                  '<team score="1">Scotland</team>' +
                  '<team score="1">Norway</team>' +
                '</match>' +
                '<match>' +
                  '<date>16-Jun-98</date>' +
                  '<team score="3">Brazil</team>' +
                  '<team score="0">Morocco</team>' +
                '</match>' +
                '<match>' +
                  '<date>23-Jun-98</date>' +
                  '<team score="1">Brazil</team>' +
                  '<team score="2">Norway</team>' +
                '</match>' +
                '<match>' +
                  '<date>23-Jun-98</date>' +
                  '<team score="0">Scotland</team>' +
                  '<team score="3">Morocco</team>' +
                '</match>' +
             '</results>';
  DOM := TXpObjModel.Create(nil);
  try
    DOM.LoadMemory(TempStr[1], Length(TempStr));
    Assert(DOM.DocumentElement.SelectNumber('sum(//team/@score)') = 18,
           'Didn''t XPath:SUM() correctly');
    { If the nodeset is empty then must return zero. }
    Assert(DOM.DocumentElement.SelectInteger('sum(//team2/@score)') = 0,
           'Empty nodelist case failed.');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncSystemProperty;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    aNode := FDoc.Document.DocumentElement;
    CheckEquals(XpXSLImplementation, aNode.SelectString('system-property("xsl:version")'), 'version');
    CheckEquals(XpVendor, aNode.SelectString('system-property("xsl:vendor")'), 'vendor');
    CheckEquals(XpVendorURL, aNode.SelectString('system-property("xsl:vendor-url")'), 'vendor-url');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncTranslate;
var
  aNode : TXpElement;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    { Translate attribute name to uppercase. }
    aNode := TXpElement(FDoc.Document.DocumentElement.FirstChild);
    CheckEquals('WONDERNUT CHROME',
                aNode.SelectString('translate(@name,"abcdefghijklmnopqrstuvwxyz","ABCDEFGHIJKLMNOPQRSTUVWXYZ")'),
                                   'Uppercase');
    { Remove whitespace }
    { Note: We do not recognize &#xx inside of strings. }
//    CheckEquals('WondernutChrome',
//                aNode.SelectString('translate(@name,"&#20;&#x9;&#xA;&#xD;()-","")'),
//                                   'Remove whitespace');
    CheckEquals('WondernutChrome',
                aNode.SelectString('translate(@name," ","")'),
                                   'Remove whitespace');
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.testFuncTrue;
begin
  FDoc := LoadDoc(csDocLug);
  Check(Assigned(FDoc) and (FDoc.ErrorCount = 0), 'Could not load document ' + csDocLug);
  try
    Check(FDoc.DocumentElement.SelectBoolean('true()'));
  finally
    FDoc.Free;
  end;
end;
{--------}
procedure TXpXPathTests.TestFuncUnparsedEntityURI;
var
  bFileClosed : Boolean;
  DOM : TXpObjModel;
  DTDFile : System.Text;
  Nodes : TXpNodeList;
  oDTD : TXpDocumentType;
  oNode : TXpNode;
  XMLStr : string;
begin

  { Test a document with an internal DTD. }
  XMLStr :=
  '<?xml version="1.0"?>' +
  '<!DOCTYPE doc [' +
  '  <!ELEMENT doc (#PCDATA)>' +
  '  <!ENTITY weather-map SYSTEM "weather.jpg" NDATA JPEG>' +
  ']>' +
  '<doc>Long live and perspire.</doc>';

  Nodes := nil;
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'load fail 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    { Find the DTD. }
    Nodes := DOM.Document.GetChildNodesByNodeType(DOCUMENT_TYPE_NODE);
    Assert(Nodes.Length = 1, 'test 3');
    oNode := TXpDocumentType(Nodes.Item(0));
    Assert(oNode is TXpDocumentType, 'test 4');
    oDTD := TXpDocumentType(oNode);

    { Search the DTD for an entity declaration. }
    XMLStr := oDTD.SelectString('unparsed-entity-uri("weather-map")');
    CheckEquals('weather.jpg', XMLStr, 'test 5');
  finally
    Nodes.Free;
    DOM.Free;
  end;

  { Test a document with an external DTD. }
  { First, create the external DTD. }
  XMLStr :=
  '<!ELEMENT doc (#PCDATA)>' + #13#10 +
  '<!ENTITY weather-map SYSTEM "weather.jpg" NDATA JPEG>';

  System.AssignFile(DTDFile, 'test.dtd');
  bFileClosed := False;
  try
    System.Rewrite(DTDFile);
    writeln(DTDFile, XMLStr);
    System.CloseFile(DTDFile);
    bFileClosed := True;

    XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE doc  SYSTEM "test.dtd">' +
    '<doc>Long live and perspire.</doc>';

    Nodes := nil;
    DOM := TXpObjModel.Create(nil);
    DOM.RaiseErrors := False;
    try
      Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'load fail 6');
      Assert(DOM.Errors.Count = 0, 'test 7');
      { Find the DTD. }
      Nodes := DOM.Document.GetChildNodesByNodeType(DOCUMENT_TYPE_NODE);
      Assert(Nodes.Length = 1, 'test 8');
      oNode := Nodes.Item(0);
      Assert(oNode is TXpDocumentType, 'test 9');
      oDTD := TXpDocumentType(oNode);

      { Search the DTD for an entity declaration. }
      XMLStr := oDTD.SelectString('unparsed-entity-uri("weather-map")');
      CheckEquals('weather.jpg', XMLStr, 'test 10');
    finally
      Nodes.Free;
      DOM.Free;
    end;
  finally
    if not bFileClosed then
      System.CloseFile(DTDFile);
    DeleteFile('test.dtd');
  end;

end;
{--------}
procedure TXpXPathTests.testCheckDecimalSep;
begin

end;
{====================================================================}


initialization
  RegisterTest('XPath Tests', TXpXpathTests);
end.
