unit DOMmain;

interface
uses
  TestFramework,
  SysUtils,
  Classes,
{$IFDEF WIN32}
  Windows,
{$ENDIF}
  XpParser,
  XpDOM,
  XpBase;

type
  TXpDOMTests = class(TTestCase)
    protected
      FList : TList;
        { List used to hold validation error codes. }
    public
      procedure OnInvalidDoc(oOwner : TObject; wCode : Integer;
                             oNode : TXpNode; var bStop : Boolean);
        { To be used when debugging a validation test. }
      procedure Setup; override;
      procedure Teardown; override;
    published
      procedure TestAttListCase;
      procedure TestAttListCase2;
      procedure TestDocGetElementsByTagName;
      procedure TestEndOfLineHandling;
      procedure TestGetChildNodesByNodeType;
      procedure TestGetElementsByTagName;
      procedure TestGetElementsByTagNameNS;
      procedure TestGetElementsByTagNameNS2;
      procedure TestIgnoreSectionWellFormed;
      procedure TestIgnoreSectionMalformed;
      procedure TestNamedNodeMapGetNamedItemNS;
      procedure TestNextSibling;
      procedure TestNodeIsAfter;
      procedure TestQuotes;
      procedure TestStreamRelativePath;
      procedure TestSplitText;
      procedure TestValidate1;
      procedure TestValidate2;
      procedure TestValidate3;
      procedure TestValidate4;
      procedure TestValidate5;
        { Issue 1224: Verify that required group with one optional element
          is handled properly when the optional element is not present
          in the document. }
      procedure TestValidate6;
      procedure TestValidate7;
      procedure TestValidate8;
      procedure TestValidate9;
      procedure TestValidate10;
      procedure TestValidate11;
      procedure TestValidate12;
      procedure TestValidate13;
      procedure TestValidate14;
        { Issue 4119: Verify that an error is raised when a zero or one
          optional element occurs more than once in the document. }
      procedure TestValidate15;
        { Issue 4119: Verify that an error is raised when multi-repeat element
          is followed by an unexpected element. }
      procedure TestValidate16;
        { Issue 4120: Verify that optional repeat elements validate correctly. }
  end;

implementation

uses
  Dialogs,
  Forms,
{$IFDEF LINUX}
  uProgress;
{$ELSE}
  uProgressW;
{$ENDIF}

{===TXpDOMTests=======================================================}
procedure TXpDOMTests.Setup;
begin
  inherited;
  FList := TList.Create;
end;
{--------}
procedure TXpDOMTests.Teardown;
begin
  FList.Free;
  inherited;
end;
{--------}
procedure TXpDOMTests.OnInvalidDoc(oOwner : TObject; wCode : Integer;
                                   oNode : TXpNode; var bStop : Boolean);
begin
  FList.Add(Pointer(wCode));
//  ShowMessage(Format('Error code: %d, Node: %s', [wCode, oNode.NodeName]));
end;
{--------}
procedure TXpDOMTests.TestAttListCase;
var
  DOM : TXpObjModel;
  Element : TXpElement;
  XMLStr : string;
begin
  XMLStr :=
    '<!DOCTYPE Root [' +
    '   <!ELEMENT Root (L1*)>' +
    '   <!ELEMENT L1   EMPTY>' +
    '   <!ATTLIST L1   Val CDATA "0">' +
    ']>' +
    '<Root>' +
    '  <L1 val="1"/>' +
    '  <L1/>' +
    '</Root>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  DOM.Document.IgnoreCase := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'load fail 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Element := TXpElement(DOM.DocumentElement.FirstChild);
    CheckEquals(2, Element.Attributes.Length, 'attr 1a');
    CheckEquals('1', Element.GetAttribute('val'), 'attr 1b');
    CheckEquals('0', Element.GetAttribute('Val'), 'attr 1c');

    Element := TXpElement(DOM.DocumentElement.LastChild);
    CheckEquals(1, Element.Attributes.Length, 'attr 2a');
    CheckEquals('', Element.GetAttribute('val'), 'attr 2b');
    CheckEquals('0', Element.GetAttribute('Val'), 'attr 1c');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestAttListCase2;
var
  DOM : TXpObjModel;
  Element : TXpElement;
  XMLStr : string;
begin
  { SPW : This test case will always fail until issue 1269 is resolved. }
  Exit;
  XMLStr :=
    '<!DOCTYPE Root [' +
    '   <!ELEMENT Root (L1*)>' +
    '   <!ELEMENT L1   EMPTY>' +
    '   <!ATTLIST L1   Val CDATA "2">' +
    '   <!ATTLIST L1   val CDATA "0">' +
    ']>' +
    '<Root>' +
    '  <L1 val="1"/>' +
    '  <L1/>' +
    '</Root>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  DOM.Document.IgnoreCase := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'load fail 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Element := TXpElement(DOM.DocumentElement.FirstChild);
    CheckEquals(2, Element.Attributes.Length, 'attr 1a');
    CheckEquals('1', Element.GetAttribute('val'), 'attr 1b');
    CheckEquals('2', Element.GetAttribute('Val'), 'attr 1c');

    Element := TXpElement(DOM.DocumentElement.LastChild);
    CheckEquals(2, Element.Attributes.Length, 'attr 2a');
    CheckEquals('0', Element.GetAttribute('val'), 'attr 2b');
    CheckEquals('2', Element.GetAttribute('Val'), 'attr 2c');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestDocGetElementsByTagName;
var
  DOM : TXpObjModel;
  Pages : TXpNodeList;
begin
  Pages := nil;
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
{$IFDEF WIN32}
    Assert(DOM.LoadDataSource('..\Data\DOM\control1.xml'), 'test 1');
{$ENDIF}
{$IFDEF LINUX}
    Assert(DOM.LoadDataSource('../data/dom/control1.xml'), 'test 1');
{$ENDIF}
    Assert(DOM.Errors.Count = 0, 'test 2');
    { Verify that case-sensitive search is case-sensitive. }
    DOM.Document.IgnoreCase := False;
    Pages := DOM.Document.GetElementsByTagName
               ('ServerMachineName');
    CheckEquals(0, Pages.Length, 'test 3');
    Pages.Free;
    Pages := nil;

    { Verify that case-insensitive search is case-insensitive. }
    DOM.Document.IgnoreCase := True;
    Pages := DOM.Document.GetElementsByTagName
               ('SeRveRMachineName');
    CheckEquals(1, Pages.Length, 'test 4');
    Pages.Free;
    Pages := nil;
  finally
    Pages.Free;
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestEndOfLineHandling;

{ This test is related to bug 2410 and the changes made to correct it. }

const
  cSourceText = 'SELECT   X.ID,' + #13#10 +
                'X.NAME,' + #13#10 +
                'X.REGION,' + #13#10 +
                'ORDER BY X.SITEID' + #13#10;
  cLFSourceText = 'SELECT   X.ID,' + #10 +
                  'X.NAME,' + #10 +
                  'X.REGION,' + #10 +
                  'ORDER BY X.SITEID' + #10;
var
  DOM : TXpObjModel;
  Elem : TXpElement;
begin
  DOM := TXpObjModel.Create(nil);
  try
    { First, we will try this with LineBreakCharReplace set to False.
      This should give us text that matches the source text. }
    DOM.LineBreakCharReplace := False;

    Elem := DOM.Document.CreateElement('FIELD');
    try
      Elem.CreateChildText(cSourceText);
      DOM.Document.ForceOwnerDocument(Elem);
      DOM.Document.AppendChild(Elem);
    finally
      Elem.Release;
    end;

    Assert(DOM.DocumentElement.ChildNodes.Item(0).XMLDocument = cSourceText,
           'No LineBreakCharReplace test failed');
    DOM.ClearDocument;

    { Now lets try the same test with a document that uses LF (#10)
      instead of CRLF as end-of-line markers. Again, the resulting
      text should match the source text -- cLFSourceText in this case.}
    Elem := DOM.Document.CreateElement('FIELD');
    try
      Elem.CreateChildText(cLFSourceText);
      DOM.Document.ForceOwnerDocument(Elem);
      DOM.Document.AppendChild(Elem);
    finally
      Elem.Release;
    end;

    Assert(DOM.DocumentElement.ChildNodes.Item(0).XMLDocument = cLFSourceText,
           'No LineBreakCharReplace w/LF source test failed');
    DOM.ClearDocument;

    { Now we will try the first two tests with LineBreakCharReplace =
      True. Both tests should give us a result that matches the first
      source (sSourceText) since the source for the first test has
      end-of-lines that match the default ones for the OS and the end-
      of-lines in the second test will be converted to match the
      default for the OS. }
    DOM.LineBreakCharReplace := True;

    Elem := DOM.Document.CreateElement('FIELD');
    try
      Elem.CreateChildText(cSourceText);
      DOM.Document.ForceOwnerDocument(Elem);
      DOM.Document.AppendChild(Elem);
    finally
      Elem.Release;
    end;

    Assert(DOM.DocumentElement.ChildNodes.Item(0).XMLDocument = cSourceText,
           'LineBreakCharReplace test failed');
    DOM.ClearDocument;

    { ...LF source }
    Elem := DOM.Document.CreateElement('FIELD');
    try
      Elem.CreateChildText(cLFSourceText);
      DOM.Document.ForceOwnerDocument(Elem);
      DOM.Document.AppendChild(Elem);
    finally
      Elem.Release;
    end;

    Assert(DOM.DocumentElement.ChildNodes.Item(0).XMLDocument = cSourceText,
           'LineBreakCharReplace w/LB source test failed');
    DOM.ClearDocument;

  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestGetChildNodesByNodeType;
var
//  bFileClosed : Boolean;
  DOM : TXpObjModel;
//  DTDFile : System.Text;
  Nodes : TXpNodeList;
//  oDocType : 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 := Nodes.Item(0);
    Assert(oNode is TXpDocumentType, 'test 4');
    Nodes.Free;

    { Search the DTD for an entity declaration. }
    Nodes := oNode.GetChildNodesByNodeType(ENTITY_DECL_NODE);
    Assert(Nodes.Length = 1, 'test 5');
    oNode := Nodes.Item(0);
    Assert(oNode is TXpDTDEntity, 'test 6');
  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 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 := Nodes.Item(0);
      Assert(oNode is TXpDocumentType, 'test 4');}

      { External DTD? }
{      if TXpDocumentType(
      Nodes := oNode.GetChildNodesByNodeType(ENTITY_DECL_NODE);
      Assert(Nodes.Length = 1, 'test 5');
      oNode := Nodes.Item(0);
      Assert(oNode is TXpDTDEntity, 'test 6');
    finally
      Nodes.Free;
      DOM.Free;
    end;
  finally
    if not bFileClosed then
      System.CloseFile(DTDFile);
    DeleteFile('test.dtd');
  end;}

end;
{--------}
procedure TXpDOMTests.TestGetElementsByTagName;
var
  DOM : TXpObjModel;
  Pages : TXpNodeList;
begin
  Pages := nil;
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
{$IFDEF WIN32}
    Assert(DOM.LoadDataSource('..\Data\DOM\control1.xml'), 'test 1');
{$ENDIF}
{$IFDEF LINUX}
    Assert(DOM.LoadDataSource('../data/dom/control1.xml'), 'test 1');
{$ENDIF}
    Assert(DOM.Errors.Count = 0, 'test 2');
    { Verify that case-sensitive search is case-sensitive. }
    DOM.Document.IgnoreCase := False;
    Pages := DOM.Document.DocumentElement.GetElementsByTagName
               ('PaGe');
    CheckEquals(0, Pages.Length, 'test 3');
    Pages.Free;
    Pages := nil;

    { Verify that case-insensitive search is case-insensitive. }
    DOM.Document.IgnoreCase := True;
    Pages := DOM.Document.DocumentElement.GetElementsByTagName
               ('PaGe');
    CheckEquals(5, Pages.Length, 'test 4');
    Pages.Free;
    Pages := nil;
  finally
    Pages.Free;
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestGetElementsByTagNameNS;
var
  DOM : TXpObjModel;
  Pages : TXpNodeList;
begin
  Pages := nil;
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
{$IFDEF WIN32}
    Assert(DOM.LoadDataSource('..\Data\DOM\control1.xml'), 'test 1');
{$ENDIF}
{$IFDEF LINUX}
    Assert(DOM.LoadDataSource('../data/dom/control1.xml'), 'test 1');
{$ENDIF}
    Assert(DOM.Errors.Count = 0, 'test 2');
    Pages := DOM.Document.GetElementsByTagNameNS
               ('urn:modes-web-server:controls', 'page');
    CheckEquals(5, Pages.Length, 'test 3');
    Pages.Free;
    Pages := nil;

    Pages := DOM.Document.GetElementsByTagNameNS
               ('', 'mws:*');
    CheckEquals(66, Pages.Length, 'test 4');
    Pages.Free;
    Pages := nil;
  finally
    Pages.Free;
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestGetElementsByTagNameNS2;
  { Ref: Bug 2589 }
var
  aRoot : TXpElement;
  DOM   : TXpObjModel;
  aAreas,
  aSchematics : TXpNodeList;
  i, j : Integer;
begin
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  aAreas := nil;
  try
{$IFDEF WIN32}
    Assert(DOM.LoadDataSource('..\Data\pdmagic.xml'), 'test 1');
{$ENDIF}
{$IFDEF LINUX}
    Assert(DOM.LoadDataSource('../data/pdmagic.xml'), 'test 1');
{$ENDIF}
    Assert(DOM.Errors.Count = 0, 'test 2');

    aRoot := DOM.Document.DocumentElement;
    aSchematics := aRoot.GetElementsByTagNameNS('http//www.pdmagic.com/',
                                               'Schematic' );
    try
      Assert(aSchematics.Length = 2, 'Should be 2 Schematic nodes');

      for i := 0 to aSchematics.length - 1 do begin
        Assert(aSchematics.Item(i).LocalName = 'Schematic',
               'Matched wrong Schematic node');
        try
          aAreas := TXpElement(aSchematics.Item(i)).GetElementsByTagNameNS
                      ('http//www.pdmagic.com/', 'Area');
          for j := 0 to aAreas.Length - 1 do
            Assert(aAreas.Item(j).LocalName = 'Area', 'Matched wrong area nodes.');
        finally
          aAreas.Free;
        end;
      end;  { for }
    finally
      aSchematics.Free;
    end;
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestIgnoreSectionWellFormed;
var
  DOM : TXpObjModel;
  oNode : TXpNode;
begin
  { Verify that a well-formed IGNORE section is parsed into a text node. }
  DOM := TXpObjModel.Create(nil);
  try
{$IFDEF WIN32}
    Assert(DOM.LoadDatasource('..\Data\DOM\ignorewf.xml'), 'test 1a');
{$ENDIF}
{$IFDEF LINUX}
    Assert(DOM.LoadDatasource('../data/dom/ignorewf.xml'), 'test 1a');
{$ENDIF}
    { Should have a text node in the external DTD. }
    Assert(DOM.Document.DocType.ExternalDTD <> nil, 'test 1b');
    Assert(DOM.Document.DocType.ExternalDTD.ChildNodes.Length = 2, 'test 1c');
    { Get the parent of the text node. It should be a conditional ignore
      section. }
    oNode := DOM.Document.DocType.ExternalDTD.LastChild;
    Assert(oNode is TXpDTDConditional, 'test 1d');
    Assert(TXpDTDConditional(oNode).CondType = CONDITIONAL_IGNORE, 'test 1e');
    Assert(oNode.FirstChild is TXpText, 'test 1f');
  finally
    DOM.Free;
  end;

  { Verify that a well-formed IGNORE section is parsed into a text node. }
  DOM := TXpObjModel.Create(nil);
  try
{$IFDEF WIN32}
    Assert(DOM.LoadDatasource('..\Data\DOM\ignorewf2.xml'), 'test 2a');
{$ENDIF}
{$IFDEF LINUX}
    Assert(DOM.LoadDatasource('../data/dom/ignorewf2.xml'), 'test 2a');
{$ENDIF}
    { Should have a text node in the external DTD. }
    Assert(DOM.Document.DocType.ExternalDTD <> nil, 'test 2b');
    Assert(DOM.Document.DocType.ExternalDTD.ChildNodes.Length = 2, 'test 2c');
    { Get the parent of the text node. It should be a conditional ignore
      section. }
    oNode := DOM.Document.DocType.ExternalDTD.LastChild;
    Assert(oNode is TXpDTDConditional, 'test 2d');
    Assert(TXpDTDConditional(oNode).CondType = CONDITIONAL_IGNORE, 'test 2e');
    Assert(oNode.FirstChild is TXpText, 'test 2f');
  finally
    DOM.Free;
  end;

end;
{--------}
procedure TXpDOMTests.TestIgnoreSectionMalformed;
  { Verify that a malformed IGNORE section raises an exception. }
var
  DOM : TXpObjModel;
begin
  { Verify that a malformed IGNORE section raises an exception. }
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
{$IFDEF WIN32}
    Assert(not DOM.LoadDatasource('..\Data\DOM\ignoremf.xml'), 'test 1a');
{$ENDIF}
{$IFDEF LINUX}
    Assert(not DOM.LoadDatasource('../Data/DOM/ignoremf.xml'), 'test 1a');
{$ENDIF}
    Assert(DOM.Errors.Count = 1, 'test 1b');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestNamedNodeMapGetNamedItemNS;
const
  csAttr1 = 'attrib1';
var
  DOM : TXpObjModel;
  oMap : TXpNamedNodeMap;
  oNode : TXpNode;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<root xmlns:a="sean.htm">' +
    '  <element1 a:attrib1="attrib1"/>' +
    '</root>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');

    { Can we find the attribute on element1 using a case-sensitive
      comparison? }
    DOM.Document.IgnoreCase := False;
    oMap := TXpElement(DOM.Document.DocumentElement.FirstChild).Attributes;
    oNode := oMap.GetNamedItemNS('sean.htm', csAttr1);
    Assert(oNode <> nil, 'test 3');
    Assert(oNode.NodeValue = csAttr1, 'test 4');
    oNode.Release;

    oNode := oMap.GetNamedItemNS('Sean.htm', csAttr1);
    Assert(oNode = nil, 'test 5');

    oNode := oMap.GetNamedItemNS('sean.htm', 'Attrib1');
    Assert(oNode = nil, 'test 6');

    { Using a case-insensitive comparison? }
    DOM.Document.IgnoreCase := True;
    oNode := oMap.GetNamedItemNS('sean.htm', csAttr1);
    Assert(oNode <> nil, 'test 7');
    Assert(oNode.NodeValue = csAttr1, 'test 8');
    oNode.Release;

    oNode := oMap.GetNamedItemNS('Sean.htm', csAttr1);
    Assert(oNode <> nil, 'test 9');
    Assert(oNode.NodeValue = csAttr1, 'test 10');
    oNode.Release;

    oNode := oMap.GetNamedItemNS('sean.htm', 'Attrib1');
    Assert(oNode <> nil, 'test 11');
    Assert(oNode.NodeValue = csAttr1, 'test 12');
    oNode.Release;
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestNextSibling;
var
  DOM : TXpObjModel;
  oNode : TXpNode;
  XMLStr : string;
begin
  XMLStr :=
    '<UPDATE>' + #13#10 +
    '  <MAIL>' + #13#10 +
    '    <SUBJECT>test 1</SUBJECT>' + #13#10 +
    '  </MAIL>' + #13#10 +
    '  <MAIL>' + #13#10 +
    '    <SUBJECT>test 2</SUBJECT>' + #13#10 +
    '  </MAIL>' + #13#10 +
    '</UPDATE>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    DOM.NormalizeData := False;
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    oNode := DOM.Document.SelectSingleNode('//UPDATE/MAIL');
    CheckEquals('MAIL', oNode.NodeName, 'test 3');
    repeat
      oNode := oNode.NextSibling;
    until oNode.NodeType = ELEMENT_NODE;
    Assert(oNode <> nil, 'test4');
    CheckEquals('MAIL', oNode.NodeName, 'test 5');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestNodeIsAfter;
var
  DOM : TXpObjModel;
  oNode1, oNode2 : TXpNode;
  XMLStr : string;
begin
  XMLStr :=
    '<root>' + #13#10 +
    '  <n1>' + #13#10 +
    '    <n11>' + #13#10 +
    '    </n11>' + #13#10 +
    '    <n12>' + #13#10 +
    '    </n12>' + #13#10 +
    '    <n13>' + #13#10 +
    '    </n13>' + #13#10 +
    '    <n14>' + #13#10 +
    '    </n14>' + #13#10 +
    '    <n15>' + #13#10 +
    '    </n15>' + #13#10 +
    '    <n16>' + #13#10 +
    '      <n161>' + #13#10 +
    '      </n161>' + #13#10 +
    '      <n162>' + #13#10 +
    '      </n162>' + #13#10 +
    '      <n163>' + #13#10 +
    '      </n163>' + #13#10 +
    '      <n164>' + #13#10 +
    '        <n1641>' + #13#10 +
    '          <n16411>' + #13#10 +
    '          </n16411>' + #13#10 +
    '          <n16412>' + #13#10 +
    '            <n164121>' + #13#10 +
    '            </n164121>' + #13#10 +
    '            <n164122>' + #13#10 +
    '            </n164122>' + #13#10 +
    '            <n164123>' + #13#10 +
    '              <n1641231>' + #13#10 +
    '              </n1641231>' + #13#10 +
    '            </n164123>' + #13#10 +
    '          </n16412>' + #13#10 +

    '          <n16413>' + #13#10 +
    '            <n164131>' + #13#10 +
    '            </n164131>' + #13#10 +
    '            <n164132>' + #13#10 +
    '            </n164132>' + #13#10 +
    '            <n164133>' + #13#10 +
    '              <n1641331>' + #13#10 +
    '              </n1641331>' + #13#10 +
    '            </n164133>' + #13#10 +
    '          </n16413>' + #13#10 +


    '        </n1641>' + #13#10 +
    '      </n164>' + #13#10 +
    '    </n16>' + #13#10 +
    '  </n1>' + #13#10 +
    '  <n2>' + #13#10 +
    '  </n2>' + #13#10 +
    '  <n2>' + #13#10 +
    '  </n2>' + #13#10 +
    '</root>';

  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    DOM.NormalizeData := True;
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');

    { Find the nodes whose level code is to be compared. }
    oNode1 := DOM.Document.DocumentElement.SelectSingleNode('n1/n16/n164/n1641/n16412/n164123/n1641231');
    Assert(oNode1 <> nil, 'test 3');
    CheckEquals('1.1.6.4.1.2.3.1', oNode1.LevelCode, 'test 4');

    oNode2 := DOM.Document.DocumentElement.SelectSingleNode('n1/n16/n164/n1641/n16413/n164133');
    Assert(oNode2 <> nil, 'test 5');
    CheckEquals('1.1.6.4.1.3.3', oNode2.LevelCode, 'test 6');

    Check(oNode2.IsAfter(oNode1), 'test 7');
    Check(not oNode1.IsAfter(oNode2), 'test 8');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestQuotes;
const
  csAttrib = 'attrib';
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<Element attrib="OK"/>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1a');
    Assert(DOM.Errors.Count = 0, 'test 1b');
    CheckEquals('OK', DOM.DocumentElement.GetAttribute(csAttrib));
  finally
    DOM.Free;
  end;


  XMLStr :=
    '<Element attrib=''OK''/>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 2a');
    Assert(DOM.Errors.Count = 0, 'test 2b');
    CheckEquals('OK', DOM.DocumentElement.GetAttribute(csAttrib));
  finally
    DOM.Free;
  end;

  XMLStr :=
    '<Element attrib="Works ''OK'' too"/>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 3a');
    Assert(DOM.Errors.Count = 0, 'test 3b');
    CheckEquals('Works ''OK'' too', DOM.DocumentElement.GetAttribute(csAttrib));
  finally
    DOM.Free;
  end;

  XMLStr :=
    '<Element attrib=''Should be "OK" here''/>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 4a');
    Assert(DOM.Errors.Count = 0, 'test 4b');
    CheckEquals('Should be "OK" here', DOM.DocumentElement.GetAttribute(csAttrib));
  finally
    DOM.Free;
  end;

  XMLStr :=
    '<Element attrib=''Should be &quot;OK&quot; here''/>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 4a');
    Assert(DOM.Errors.Count = 0, 'test 4b');
    CheckEquals('Should be "OK" here', DOM.DocumentElement.GetAttribute(csAttrib));
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestStreamRelativePath;
var
  aStream    : TMemoryStream;
  SR         : TSearchRec;
  FilePath   : string;
  FindResult : Boolean;
  DOM        : TXpObjModel;
  ParsedOK   : Boolean;
  FailedTest : Boolean;
  Log        : TStringList;
  LogName    : string;
  Idx        : Integer;
  FileName   : string;
  FileCount  : Integer;
  Dlg        : TDlgProgress;
  SavDir     : string;
begin
  ParsedOK := False;
  LogName := '';
  Log := nil;
  FailedTest := False;
{$IFDEF LINUX}
  FilePath := '../data/StrmRelPath/';
{$ELSE}
  FilePath := '..\data\StrmRelPath\';
{$ENDIF}
  FindResult := FindFirst(FilePath + '*.xml', faAnyFile, SR) = 0;
  try
    Log := TStringList.Create;
    FileCount := 0;
    if FindResult then
      repeat
        inc(FileCount);
      until FindNext(SR) <> 0;
    SysUtils.FindClose(SR);

    { Position to directory containing document so that relative
      path for external entity will resolve correctly. }
    SavDir := GetCurrentDir;
    SetCurrentDir(FilePath);
    Dlg := TDlgProgress.Create(nil);
    try
      Dlg.Show;
      Dlg.Max := FileCount;
      Application.ProcessMessages;
{$IFDEF LINUX}
      LogName := '/tmp/StreamRelPathErrors.txt';
{$ELSE}
      LogName := 'c:\StreamRelPathErrors.txt';
{$ENDIF}
      FindResult := FindFirst('*.xml', faAnyFile, SR) = 0;
      if FindResult then begin
        repeat
          if AnsiCompareText(ExtractFileExt(Sr.Name), '.xml') = 0 then begin
            {Update progressbar}
            Dlg.FileName := FileName;
            Dlg.Position := Dlg.Position + 1;

            {Verify that XML document is OK}
            DOM := TXpObjModel.Create(nil);
            try
              FileName := Sr.Name;
              try
                aStream := TMemoryStream.Create;
                try
                  aStream.LoadFromFile(FileName);
                  aStream.Position := 0;
                  ParsedOK := DOM.LoadMemory(aStream.Memory^, aStream.Size);
                finally
                  aStream.Free;
                end;
              except
                on E:Exception do
                  ParsedOK := False;
              end;
              if not ParsedOK then begin
                FailedTest := True;
                {Store error messages}
                Log.Add('-----------------------------------------------');
                Log.Add('  Error(s) parsing : ' + FileName);
                Log.Add('');
                for Idx := 0 to Pred(DOM.Errors.Count) do
                  Log.Add(DOM.Errors.Strings[Idx]);
                Log.Add('');
                Log.Add('-----------------------------------------------');
                Log.Add('');
                Log.Add('');
                Log.SaveToFile(LogName);

                Dlg.AddError('-----------------------------------------------');
                Dlg.AddError('  Error(s) parsing : ' + FileName);
                Dlg.AddError('');
                for Idx := 0 to Pred(DOM.Errors.Count) do
                  Dlg.AddError(DOM.Errors.Strings[Idx]);
                Dlg.AddError('');
                Dlg.AddError('-----------------------------------------------');
                Dlg.AddError('');
                Dlg.AddError('');
              end;
            finally
              DOM.Free;
            end;
          end;
        until FindNext(SR) <> 0;
      end else
        Fail('Docs not found');
    finally
      SetCurrentDir(SavDir);
      Dlg.Hide;
      Dlg.Free;
    end;
  finally
    Log.Free;
    SysUtils.FindClose(SR);
  end;
  Assert(not FailedTest, 'At least one doc didn''t parse well. See ' +
                         LogName + 'for info');
end;
{--------}
procedure TXpDOMTests.TestSplitText;
var
  bExceptRaised : Boolean;
  oNewText,
  oText  : TXpText;
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<root>12345678901</root>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.Document.DocumentElement.ChildNodes.Length = 1, 'test 3');
    oText := TXpText(DOM.Document.DocumentElement.FirstChild);
    Assert(oText is TXpText, 'test 4');

    { Verify that SplitText will raise an exception if index is out of
      bounds. }
    bExceptRaised := False;
    try
      oText.SplitText(-1);
    except
      bExceptRaised := True;
    end;
    Assert(bExceptRaised, 'test 5');

    bExceptRaised := False;
    try
      oText.SplitText(12);
    except
      bExceptRaised := True;
    end;
    Assert(bExceptRaised, 'test 6');

    { Split the text node. }
    oNewText := oText.SplitText(5);
    Assert(oNewText is TXpText, 'test 7');
    Assert(oNewText <> oText, 'test 8');
    Assert(DOM.Document.DocumentElement.ChildNodes.Length = 2, 'test 9');
    Assert(DOM.Document.DocumentElement.FirstChild = oText, 'test 10');
    Assert(oText.Data = '12345', 'test 11');
    Assert(oNewText.Data = '678901', 'test 12');
    oNewText.Release;
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate1;
var
  DOM : TXpObjModel;
begin
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
{$IFDEF WIN32}
    Assert(DOM.LoadDatasource('..\Data\DOM\rfq153.xml'), 'test 1');
{$ENDIF}
{$IFDEF LINUX}
    Assert(DOM.LoadDatasource('../data/dom/rfq153.xml'), 'test 1');
{$ENDIF}
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate2;
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE TopLevel [' +
    '<!ELEMENT TopLevel (ValidationError*)*>' +
    '<!ATTLIST TopLevel code CDATA #REQUIRED details CDATA #REQUIRED>' +
    '<!ELEMENT ValidationError EMPTY>' +
    '<!ATTLIST ValidationError code CDATA #REQUIRED details CDATA #REQUIRED>' +
    ']>' +
    '<TopLevel code="a" Caaa="a" details="a"/>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(not DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate3;
var
  DOM : TXpObjModel;
begin
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
{$IFDEF WIN32}
    Assert(DOM.LoadDataSource('..\Data\DOM\disystemer.xml'), 'test 1');
{$ENDIF}
{$IFDEF LINUX}
    Assert(DOM.LoadDataSource('../data/dom/disystemer.xml'), 'test 1');
{$ENDIF}
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(not DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate4;
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE TopLevel [' +
    '<!ELEMENT TopLevel (NextLevela+, NextLevelb+)>' +
    '<!ELEMENT NextLevela EMPTY>' +
    '<!ELEMENT NextLevelb EMPTY>' +
    ']>' +
    '<TopLevel>' +
    '    <NextLevela/>' +
    '    <!-- comment -->' +
    '    <NextLevelb/>' +
    '    <NextLevelb/>' +
    '</TopLevel>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate5;
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE test [' +
    '<!ELEMENT test (testB*)>' +
    ']>' +
    '<test/>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.ValidateDocument, 'Test 3: probably broke DTD grouping');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate6;
  { Issue 1209 }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0" encoding="UTF-8"?>' +
    '<!DOCTYPE DOCUMENT [' +
    '<!ELEMENT DOCUMENT ((message, person) | error)>' +
    '<!ELEMENT message EMPTY>' +
    '<!ELEMENT person EMPTY>' +
    '<!ELEMENT error EMPTY>' +
    ']>' +
    '<DOCUMENT>' +
    '  <message/>' +
    '  <person/>' +
    '</DOCUMENT>';

  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;

  XMLStr :=
    '<?xml version="1.0" encoding="UTF-8"?>' +
    '<!DOCTYPE DOCUMENT [' +
    '<!ELEMENT DOCUMENT ((message, person) | error)>' +
    '<!ELEMENT message EMPTY>' +
    '<!ELEMENT person EMPTY>' +
    '<!ELEMENT error EMPTY>' +
    ']>' +
    '<DOCUMENT>' +
    '  <error/>' +
    '</DOCUMENT>';

  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 4');
    Assert(DOM.Errors.Count = 0, 'test 5');
    Assert(DOM.ValidateDocument, 'test 6');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate7;
  { Issue 1209. Validate DTD where doc contains PI. }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE doc [' +
    '<!ELEMENT doc (foo)+>' +
    '<!ELEMENT foo EMPTY>' +
    ']>' +
    '<doc>' +
    '<foo/>' +
    '<?bar xyz?>' +
    '<foo/>' +
    '</doc>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;

  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE doc [' +
    '  <!ELEMENT doc ((p+|sec+), advice?)>' +
    '  <!ELEMENT sec (st, p+)>' +
    '  <!ELEMENT p (#PCDATA|emph|l)*>' +
    '  <!ELEMENT l (st, li+)>' +
    '  <!ELEMENT li (p)+>' +
    '  <!ELEMENT st (#PCDATA|emph)*>' +
    '  <!ELEMENT emph (#PCDATA)>' +
    '  <!ELEMENT advice (warning+|recommend+)>' +
    '  <!ELEMENT recommend (#PCDATA)>' +
    '  <!ELEMENT warning (#PCDATA)> ]>' +
    '<doc>' +
    '<?pub pos="0"?>' +
    '  <p>This is<?pub pos="7"?> sample text with an <emph>emphasis</emph> for demonstration purposes. It is used to illustrate:</p>' +
    '  <p><l><st>List of <emph>details</emph> for component integration</st>' +
    '    <!-- a nested list within a paragraph -->' +
    '    <li>' +
    '      <p>The <emph>different</emph> nodes <?pub "123"?>and how they relate to the DOM nodes</p>' +
    '      <p>How the document is composed of text and nodes.</p>' +
    '    </li>' +
    '    <li>' +
    '      <p>..</p>' +
    '    </li>' +
    '  </l></p>' +
    '  <p>This is sample text with an <emph>emphasis</emph> for demonstration purposes. It is used to illustrate:</p>' +
    '  <p>Ok <emph>This is the end!</emph> bye!</p>' +
    '</doc>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  DOM.OnInvalidDocument := OnInvalidDoc;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 4');
    Assert(DOM.Errors.Count = 0, 'test 5');
    Assert(DOM.ValidateDocument, 'test 6');
  finally
    DOM.Free;
  end;

end;
{--------}
procedure TXpDOMTests.TestValidate8;
  { Issue 1209. Validate DTD where doc contains comments. }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE doc [' +
    '  <!ELEMENT doc ((p+|sec+), advice)>' +
    '  <!ELEMENT sec (st, p+)>' +
    '  <!ELEMENT p (#PCDATA|emph|l)*>' +
    '  <!ELEMENT l (st, li+)>' +
    '  <!ELEMENT li (p)+>' +
    '  <!ELEMENT st (#PCDATA|emph)*>' +
    '  <!ELEMENT emph (#PCDATA)>' +
    '  <!ELEMENT advice (warning+|recommend+)>' +
    '  <!ELEMENT recommend (#PCDATA)>' +
    '  <!ELEMENT warning (#PCDATA)> ]>' +
    '<doc>' +
    '<!-- An annotated example -->' +
    '  <p>This is sample text with an <emph>emphasis</emph> for demonstration purposes. It is used to illustrate:</p>' +
    '  <p><l><st>List of <emph>details</emph> for component integration</st>' +
    '    <!-- a nested list within a paragraph -->' +
    '    <li>' +
    '      <p>The <emph>different</emph> nodes <?pub "123"?>and how they relate to the DOM nodes</p>' +
    '      <p>How the document is composed of text and nodes.</p>' +
    '    </li>' +
    '    <li>' +
    '      <p>..</p>' +
    '    </li>' +
    '  </l></p>' +
    '  <p>This is sample text with an <emph>emphasis</emph> for demonstration purposes. It is used to illustrate:</p>' +
    '  <p>Ok <emph>This is the end!</emph> bye!</p>' +
    '  <advice>' +
    '     <warning>Niet in de buurt van kinderen</warning>' +
    '     <warning>Niet in de buurt van kinderen</warning>' +
    '  </advice>' +
    '</doc>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  DOM.OnInvalidDocument := OnInvalidDoc;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate9;
  { Issue 1209 again. }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE doc [' +
    '  <!ELEMENT doc (p*, container)>' +
    '  <!ELEMENT sec (st, p+)>' +
    '  <!ELEMENT p (#PCDATA|emph|l)*>' +
    '  <!ELEMENT l (st, li+)>' +
    '  <!ELEMENT li (p)+>' +
    '  <!ELEMENT st (#PCDATA|emph)*>' +
    '  <!ELEMENT emph (#PCDATA)>' +
    '  <!ELEMENT container (p+|sec+)> ]>' +
    '<doc>' +
    '  <p>This is sample text with an <emph>emphasis</emph> for demonstration purposes. It is used to illustrate:</p>' +
    '  <container>' +
    '     <p>Keep out of reach of children</p>' +
    '     <p>Keep out of reach of children</p>' +
    '  </container>' +
    '</doc>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  DOM.OnInvalidDocument := OnInvalidDoc;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate10;
  { Issue ??? again. }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE Test' +
    '[' +
    '  <!ELEMENT Test EMPTY >' +
    '  <!ATTLIST Test' +
    '    Att1 CDATA #IMPLIED' +
    '    Att2 CDATA #IMPLIED' +
    '    Att3 CDATA #IMPLIED' +
    '    Att4 CDATA #IMPLIED>' +
    ']>' +
    '<Test Att1="1" Att2="2" Att3="3" Att4="4"/>';

  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  DOM.OnInvalidDocument := OnInvalidDoc;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate11;
  { Issue 2338. }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE Test' +
    '[' +
    '<!ELEMENT Test (TestChild*) >' +
    '<!ATTLIST Test ' +
    'Status (OK|Bad) "OK"' +
    '>' +
    '' +
    '<!ELEMENT TestChild EMPTY >' +
    ']' +
    '>' +
    '<Test LookHere="ThisIsInvalid" Status="OK">' +
    '    <TestChild/>' +
    '</Test>';
  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  { Following line is not necessary since we expect this one to fail. }
//  DOM.OnInvalidDocument := OnInvalidDoc;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(not DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate12;
  { Issue 2538 - Problem with validating child group with choices. }
const
  Good1 = '<?xml version="1.0" encoding="ISO-8859-1"?>' +
          '<!DOCTYPE pnet_imessage_send [' +

          '<!ELEMENT pnet_imessage_send (cid,pw,vehicle_number,deliver,action?, (formdata | freeform_message))>' +
          '<!ELEMENT cid (#PCDATA)>' +
          '<!ELEMENT pw (#PCDATA)>' +
          '<!ELEMENT vehicle_number (#PCDATA)>' +

          '<!ELEMENT deliver (#PCDATA)>' +

          '<!ELEMENT action (action_type,reply_form_id?,urgent_reply)>' +
          '  <!ELEMENT action_type (#PCDATA)>' +
          '  <!ELEMENT reply_form_id (#PCDATA)>' +
          '  <!ELEMENT urgent_reply (#PCDATA)>' +

          '<!ELEMENT formdata (form_id,im_field*)>' +
          '<!ELEMENT freeform_message (#PCDATA)>' +

          '<!ELEMENT form_id (#PCDATA)>' +

          '<!ELEMENT im_field (question_number,data)>' +
          '  <!ELEMENT question_number (#PCDATA)>' +
          '  <!ELEMENT data (data_numeric | data_text | data_multiple-choice | data_password | data_time | data_date-time)>' +
          '    <!ELEMENT data_numeric (#PCDATA)>' +
          '    <!ELEMENT data_text (#PCDATA)>' +
          '    <!ELEMENT data_multiple-choice (mc_choicenum | mc_choicetext)>' +
          '      <!ELEMENT mc_choicenum (#PCDATA)>' +
          '      <!ELEMENT mc_choicetext (#PCDATA)>' +

          '    <!ELEMENT data_password (#PCDATA)>' +
          '    <!ELEMENT data_time (#PCDATA)>' +
          '    <!ELEMENT data_date-time (#PCDATA)>' +
          ']>' +

          '<pnet_imessage_send>' +
          '   <cid>20</cid>' +
          '   <pw>mypassword</pw>' +
          '   <vehicle_number>123</vehicle_number>' +
          '   <deliver>later</deliver>' +
          '   <freeform_message>Will you be late?</freeform_message>' +
          '</pnet_imessage_send>';
  Good2 = '<?xml version="1.0" encoding="ISO-8859-1"?>' +
          '<!DOCTYPE pnet_imessage_send [' +

          '<!ELEMENT pnet_imessage_send (cid,pw,vehicle_number,deliver,action?, (one | formdata | freeform_message))>' +
          '<!ELEMENT cid (#PCDATA)>' +
          '<!ELEMENT pw (#PCDATA)>' +
          '<!ELEMENT vehicle_number (#PCDATA)>' +

          '<!ELEMENT deliver (#PCDATA)>' +

          '<!ELEMENT action (action_type,reply_form_id?,urgent_reply)>' +
          '  <!ELEMENT action_type (#PCDATA)>' +
          '  <!ELEMENT reply_form_id (#PCDATA)>' +
          '  <!ELEMENT urgent_reply (#PCDATA)>' +

          '<!ELEMENT formdata (form_id,im_field*)>' +
          '<!ELEMENT freeform_message (#PCDATA)>' +

          '<!ELEMENT form_id (#PCDATA)>' +

          '<!ELEMENT im_field (question_number,data)>' +
          '  <!ELEMENT question_number (#PCDATA)>' +
          '  <!ELEMENT data (data_numeric | data_text | data_multiple-choice | data_password | data_time | data_date-time)>' +
          '    <!ELEMENT data_numeric (#PCDATA)>' +
          '    <!ELEMENT data_text (#PCDATA)>' +
          '    <!ELEMENT data_multiple-choice (mc_choicenum | mc_choicetext)>' +
          '      <!ELEMENT mc_choicenum (#PCDATA)>' +
          '      <!ELEMENT mc_choicetext (#PCDATA)>' +

          '    <!ELEMENT data_password (#PCDATA)>' +
          '    <!ELEMENT data_time (#PCDATA)>' +
          '    <!ELEMENT data_date-time (#PCDATA)>' +
          ']>' +

          '<pnet_imessage_send>' +
          '   <cid>20</cid>' +
          '   <pw>mypassword</pw>' +
          '   <vehicle_number>123</vehicle_number>' +
          '   <deliver>later</deliver>' +
          '   <freeform_message>Will you be late?</freeform_message>' +
          '</pnet_imessage_send>';
  Bad =   '<?xml version="1.0" encoding="ISO-8859-1"?>' +
          '<!DOCTYPE pnet_imessage_send [' +

          '<!ELEMENT pnet_imessage_send (cid,pw,vehicle_number,deliver,action?, (one | two | three))>' +
          '<!ELEMENT cid (#PCDATA)>' +
          '<!ELEMENT pw (#PCDATA)>' +
          '<!ELEMENT vehicle_number (#PCDATA)>' +

          '<!ELEMENT deliver (#PCDATA)>' +

          '<!ELEMENT action (action_type,reply_form_id?,urgent_reply)>' +
          '  <!ELEMENT action_type (#PCDATA)>' +
          '  <!ELEMENT reply_form_id (#PCDATA)>' +
          '  <!ELEMENT urgent_reply (#PCDATA)>' +

          '<!ELEMENT formdata (form_id,im_field*)>' +
          '<!ELEMENT freeform_message (#PCDATA)>' +

          '<!ELEMENT form_id (#PCDATA)>' +

          '<!ELEMENT im_field (question_number,data)>' +
          '  <!ELEMENT question_number (#PCDATA)>' +
          '  <!ELEMENT data (data_numeric | data_text | data_multiple-choice | data_password | data_time | data_date-time)>' +
          '    <!ELEMENT data_numeric (#PCDATA)>' +
          '    <!ELEMENT data_text (#PCDATA)>' +
          '    <!ELEMENT data_multiple-choice (mc_choicenum | mc_choicetext)>' +
          '      <!ELEMENT mc_choicenum (#PCDATA)>' +
          '      <!ELEMENT mc_choicetext (#PCDATA)>' +

          '    <!ELEMENT data_password (#PCDATA)>' +
          '    <!ELEMENT data_time (#PCDATA)>' +
          '    <!ELEMENT data_date-time (#PCDATA)>' +
          ']>' +

          '<pnet_imessage_send>' +
          '   <cid>20</cid>' +
          '   <pw>mypassword</pw>' +
          '   <vehicle_number>123</vehicle_number>' +
          '   <deliver>later</deliver>' +
          '   <freeform_message>Will you be late?</freeform_message>' +
          '</pnet_imessage_send>';
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  DOM := TXpObjModel.Create(nil);
  try
    XMLStr := Good1;
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'Can''t load test 1');
    Assert(DOM.ValidateDocument, 'Good1 document not validated');

    DOM.ClearDocument;
    XMLStr := Good2;
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'Can''t load test 2');
    Assert(DOM.ValidateDocument, 'Good2 document not validated');

    DOM.ClearDocument;
    DOM.RaiseErrors := True;
    XMLStr := Bad;
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'Can''t load test 3');
    Assert(not DOM.ValidateDocument, 'Bad document validated');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate13;
  { Issue xxxx. }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE HEADER_PACKING_UNITS_ITEM [' +
    '<!ELEMENT HEADER_PACKING_UNITS_ITEM (	ID_NO_BEGIN|' +
    '					ID_NO_END|' +
    '					SYMBOL|' +
    '					HEADER_ITEMIZED_MARKS_DETAIL*)*>' +
    '' +
    '<!ELEMENT ID_NO_BEGIN (#PCDATA)>' +
    '<!ELEMENT ID_NO_END (#PCDATA)>' +
    '<!ELEMENT SYMBOL (#PCDATA)>' +
    '<!ELEMENT HEADER_ITEMIZED_MARKS_DETAIL (	ITEM_NO|' +
    '						MARKS )*>' +
    '<!ELEMENT ITEM_NO (#PCDATA)>' +
    '<!ELEMENT MARKS (#PCDATA)>' +
    ']>' +
    '' +
    '<HEADER_PACKING_UNITS_ITEM>' +
    '  <ID_NO_BEGIN>BEIGN</ID_NO_BEGIN>' +
    '' +
    '  <ID_NO_END>END</ID_NO_END>' +
    '  <SYMBOL>SYMBOL</SYMBOL>' +
    '' +
    '  <HEADER_ITEMIZED_MARKS_DETAIL>' +
    '    <ITEM_NO>001</ITEM_NO>' +
    '    <MARKS>ONE MORE MARKS</MARKS>' +
    '  </HEADER_ITEMIZED_MARKS_DETAIL>' +
    '' +
    '  <HEADER_ITEMIZED_MARKS_DETAIL>' +
    '    <ITEM_NO>002</ITEM_NO>' +
    '    <MARKS>ANOTHER MARK</MARKS>' +
    '  </HEADER_ITEMIZED_MARKS_DETAIL>' +
    '' +
    '  <HEADER_ITEMIZED_MARKS_DETAIL>' +
    '    <ITEM_NO>003</ITEM_NO>' +
    '    <MARKS>YET ANOTHER</MARKS>' +
    '  </HEADER_ITEMIZED_MARKS_DETAIL>' +
    '' +
    '</HEADER_PACKING_UNITS_ITEM>';

  DOM := TXpObjModel.Create(nil);
  DOM.RaiseErrors := False;
  DOM.OnInvalidDocument := OnInvalidDoc;
  try
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'test 1');
    Assert(DOM.Errors.Count = 0, 'test 2');
    Assert(DOM.ValidateDocument, 'test 3');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate14;
  { Issue 4119: Verify that an error is raised if an optional element
    is encountered more than once. }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE test [' +
    '<!ELEMENT test (testB?)>' +
    '<!ELEMENT testB EMPTY>' +
    ']>' +
    '<test>' +
    '  <testB/>' +
    '  <testB/>' +
    '</test>';
  DOM := TXpObjModel.Create(nil);
  try
    DOM.RaiseErrors := False;
    DOM.OnInvalidDocument := OnInvalidDoc;
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'Could not load document');
    Assert(DOM.Errors.Count = 0, 'Errors occurred when loading document');
    Assert(not DOM.ValidateDocument, 'Document is valid but shouldn''t be');
    CheckEquals(1, FList.Count, 'Unexpected error count');
    CheckEquals(V_ELEMENTCHILDNOTEXPECTED, Integer(FList[0]),
                'Unexpected error code');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate15;
  { Issue 4119: Verify that an error is raised when multi-repeat element
    is followed by an unexpected element. }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE test [' +
    '<!ELEMENT test (testB*)>' +
    '<!ELEMENT testB EMPTY>' +
    '<!ELEMENT testC EMPTY>' +
    ']>' +
    '<test>' +
    '  <testB/>' +
    '  <testB/>' +
    '  <testC/>' +
    '</test>';
  DOM := TXpObjModel.Create(nil);
  try
    DOM.RaiseErrors := False;
    DOM.OnInvalidDocument := OnInvalidDoc;
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)), 'Could not load document');
    Assert(DOM.Errors.Count = 0, 'Errors occurred when loading document');
    Assert(not DOM.ValidateDocument, 'Document is valid but shouldn''t be');
    CheckEquals(2, FList.Count, 'Unexpected error count');
    CheckEquals(V_ELEMENTNOTCONTENT, Integer(FList[0]),
                'Unexpected error code');
    CheckEquals(V_ELEMENTCHILDNOTEXPECTED, Integer(FList[1]),
                'Unexpected error code');
  finally
    DOM.Free;
  end;
end;
{--------}
procedure TXpDOMTests.TestValidate16;
  { Issue 4120: Verify that optional repeat elements validate correctly. }
var
  DOM : TXpObjModel;
  XMLStr : string;
begin
  XMLStr :=
    '<?xml version="1.0"?>' +
    '<!DOCTYPE Manifests [' +
    '  <!ELEMENT Manifests (MANIFEST*)>' +
    '  <!ELEMENT MANIFEST (    IT_NO|' +
    '                          IT_DATE|' +
    '                          ISSUER_CODE|' +
    '                          MASTER|' +
    '                          HOUSE_PREFIX|' +
    '                          HOUSE|' +
    '                          SUB_HOUSE|' +
    '                          QTY|' +
    '                          QTY_UOM|' +
    '                          CONTAINER* )*>' +
    '  <!ELEMENT CONTAINER ( LINE_NO|' +
    '                        CONTAINER_NO|' +
    '                        SEAL_NO )*>' +
    '  <!ELEMENT LINE_NO (#PCDATA)>' +
    '  <!ELEMENT CONTAINER_NO (#PCDATA)>' +
    '  <!ELEMENT SEAL_NO (#PCDATA)>' +
    ']>' +
    '<Manifests>' +
    '  <MANIFEST>' +
    '    <CONTAINER>' +
    '       <LINE_NO></LINE_NO>' +
    '       <CONTAINER_NO>APLU4874930</CONTAINER_NO>' +
    '    </CONTAINER>' +
    '    <CONTAINER>' +
    '       <CONTAINER_NO>APLU4878302</CONTAINER_NO>' +
    '    </CONTAINER>' +        
    '  </MANIFEST>' +
    '</Manifests>';
  DOM := TXpObjModel.Create(nil);
  try
    DOM.RaiseErrors := False;
    Assert(DOM.LoadMemory(XMLStr[1], Length(XMLStr)),
                          'Could not load document: ' + DOM.Errors.Text);
    Assert(DOM.Errors.Count = 0, 'Errors occurred when loading document');
    Assert(DOM.ValidateDocument, 'Document is invalid');
  finally
    DOM.Free;
  end;
end;
{=====================================================================}

initialization
  TestFramework.RegisterTest('DOM Tests', TXpDOMTests.Suite);

end.
