xmlts.pp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. FCL test runner for OASIS/NIST XML test suite
  4. It is somewhat based on 'harness.js' script
  5. (see http://xmlconf.sourceforge.net)
  6. Copyright (c) 2006 by Sergei Gorelkin, [email protected]
  7. See the file COPYING.FPC, included in this distribution,
  8. for details about the copyright.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. **********************************************************************}
  13. program xmlts;
  14. {$IFDEF FPC}
  15. {$MODE OBJFPC}{$H+}
  16. {$ENDIF}
  17. {$APPTYPE CONSOLE}
  18. uses
  19. SysUtils,
  20. Classes,
  21. DOM,
  22. XMLRead,
  23. XMLWrite,
  24. UriParser;
  25. const
  26. harness = 'Pascal version';
  27. version = '0.0.1 alpha :)';
  28. parser = 'FCL XML parser';
  29. parserName = parser;
  30. os = 'Unknown OS';
  31. runtime = 'FPC RTL';
  32. type
  33. TDiagCategory = (dcInfo, dcNegfail, dcFail, dcPass);
  34. TTestSuite = class
  35. private
  36. FTemplate: TXMLDocument;
  37. FParser: TDOMParser;
  38. FPassed, FFailCount: Integer;
  39. FFalsePasses: Integer;
  40. FRootUri: string;
  41. FTemplateName: string;
  42. FSuiteName: string;
  43. FDoc: TXMLDocument;
  44. FValidating: Boolean;
  45. FSuiteTitle: DOMString;
  46. FState: DOMString;
  47. FSkipped: Integer;
  48. FTotal: Integer;
  49. table_valid: TDOMNode;
  50. table_output: TDOMNode;
  51. table_invalid: TDOMNode;
  52. table_not_wf: TDOMNode;
  53. table_informative: TDOMNode;
  54. FValError: string;
  55. FTestID: DOMString;
  56. procedure LoadTemplate(const Name: string);
  57. procedure HandleTemplatePIs(Element: TDOMNode);
  58. procedure Diagnose(Element, Table: TDOMNode; Category: TDiagCategory; const Error: DOMString);
  59. procedure DiagnoseOut(const ErrorMsg: DOMString);
  60. function CompareNodes(actual, correct: TDOMNode; out Msg: string): Boolean;
  61. procedure Canonicalize(node: TDOMNode);
  62. procedure ErrorHandler(Error: EXMLReadError);
  63. public
  64. constructor Create;
  65. procedure Run(const Tests: string);
  66. procedure RunTest(Element: TDOMElement);
  67. destructor Destroy; override;
  68. end;
  69. function GetBaseURI(Element: TDOMNode; const DocumentURI: string): string;
  70. var
  71. Ent: TDOMNode;
  72. Uri1, Uri2, s: WideString;
  73. begin
  74. case Element.NodeType of
  75. ELEMENT_NODE, TEXT_NODE, CDATA_SECTION_NODE,
  76. PROCESSING_INSTRUCTION_NODE, COMMENT_NODE, DOCUMENT_TYPE_NODE:
  77. if Assigned(Element.ParentNode)
  78. then Result := GetBaseURI(Element.ParentNode, DocumentURI)
  79. else Result := '';
  80. ATTRIBUTE_NODE: begin
  81. Result := '';
  82. if Assigned(TDomAttr(Element).OwnerElement) then
  83. begin
  84. Result := GetBaseURI(TDomAttr(Element).OwnerElement, DocumentURI);
  85. end;
  86. end;
  87. ENTITY_REFERENCE_NODE: begin
  88. Ent := Element.OwnerDocument.DocType.Entities.GetNamedItem(Element.NodeName);
  89. if Assigned(Ent) and (TDOMEntity(Ent).SystemID <> '') then
  90. begin
  91. Uri1 := TDOMEntity(Ent).SystemID;
  92. if IsAbsoluteURI(Uri1) then
  93. begin
  94. Result := Uri1;
  95. end else begin
  96. Uri2 := GetBaseURI(Element.ParentNode, DocumentUri);
  97. ResolveRelativeUri(Uri2, Uri1, s);
  98. Result := s;
  99. end;
  100. end
  101. else
  102. begin
  103. if Assigned(Element.ParentNode)
  104. then Result := GetBaseURI(Element.ParentNode, DocumentURI)
  105. else Result := '';
  106. end;
  107. end;
  108. DOCUMENT_NODE: Result := DocumentURI;
  109. else
  110. Result := '';
  111. end;
  112. end;
  113. { TTestSuite }
  114. constructor TTestSuite.Create;
  115. begin
  116. inherited Create;
  117. FParser := TDOMParser.Create;
  118. FParser.Options.PreserveWhitespace := True;
  119. end;
  120. procedure TTestSuite.ErrorHandler(Error: EXMLReadError);
  121. begin
  122. if Error.Severity = esError then
  123. begin
  124. if FValError = '' then // fetch the _first_ message
  125. FValError := Error.Message;
  126. { uncomment the line below to verify that the suite correctly handles
  127. exception raised from the handler }
  128. // Abort;
  129. end;
  130. end;
  131. procedure TTestSuite.LoadTemplate(const Name: string);
  132. var
  133. tables: TDOMNodeList;
  134. I: Integer;
  135. id: DOMString;
  136. el: TDOMElement;
  137. begin
  138. ReadXMLFile(FTemplate, Name);
  139. tables := FTemplate.DocumentElement.GetElementsByTagName('table');
  140. try
  141. for I := 0 to tables.Count-1 do
  142. begin
  143. el := TDOMElement(tables.Item[I]);
  144. id := el['id'];
  145. if id = 'valid' then
  146. table_valid := el
  147. else if ((id = 'invalid-negative') and FValidating) or ((id = 'invalid-positive') and not FValidating) then
  148. table_invalid := el
  149. else if id = 'valid-output' then
  150. table_output := el
  151. else if id = 'not-wf' then
  152. table_not_wf := el
  153. else if id = 'error' then
  154. table_informative := el;
  155. end;
  156. finally
  157. tables.Free;
  158. end;
  159. end;
  160. destructor TTestSuite.Destroy;
  161. begin
  162. FDoc.Free;
  163. FTemplate.Free;
  164. FParser.Free;
  165. inherited;
  166. end;
  167. procedure TTestSuite.HandleTemplatePIs(Element: TDOMNode);
  168. var
  169. Children: TDOMNodeList;
  170. Child: TDOMNode;
  171. NewChild: TDOMNode;
  172. Remove: Boolean;
  173. Index: Integer;
  174. Data: DOMString;
  175. begin
  176. Children := element.childNodes;
  177. Remove := False;
  178. Index := 0;
  179. repeat
  180. Child := Children.Item[Index];
  181. if Child = nil then Break;
  182. Inc(index);
  183. // inside a rejected <?if ...?>...<?endif?>
  184. if Remove and (child.nodeType <> PROCESSING_INSTRUCTION_NODE) then
  185. begin
  186. Element.removeChild(child);
  187. Dec(Index);
  188. Continue;
  189. end;
  190. if Child.hasChildNodes then
  191. begin
  192. HandleTemplatePIs(Child);
  193. Continue;
  194. end;
  195. if Child.nodeType <> PROCESSING_INSTRUCTION_NODE then
  196. Continue;
  197. Data := Child.NodeValue;
  198. if Child.NodeName = 'run-id' then
  199. begin
  200. if Data = 'name' then
  201. newChild := FTemplate.createTextNode(parser)
  202. else if Data = 'description' then
  203. newChild := FTemplate.createTextNode (parserName)
  204. else if Data = 'general-entities' then
  205. newChild := FTemplate.createTextNode('included')
  206. else if Data = 'parameter-entities' then
  207. newChild := FTemplate.createTextNode ('included')
  208. else if Data = 'type' then
  209. begin
  210. if FValidating then
  211. Data := 'Validating'
  212. else
  213. Data := 'Non-Validating';
  214. newChild := FTemplate.createTextNode(Data);
  215. end
  216. // ... test run description
  217. else if Data = 'date' then
  218. newChild := FTemplate.createTextNode(DateTimeToStr(Now))
  219. else if Data = 'harness' then
  220. newChild := FTemplate.createTextNode(harness)
  221. else if Data = 'java' then
  222. newChild := FTemplate.createTextNode(runtime)
  223. else if Data = 'os' then
  224. newChild := FTemplate.createTextNode(os)
  225. else if Data = 'testsuite' then
  226. newChild := FTemplate.createTextNode(FSuiteTitle)
  227. else if Data = 'version' then
  228. newChild := FTemplate.createTextNode(version)
  229. // ... test result info
  230. else if Data = 'failed' then
  231. newChild := FTemplate.createTextNode(IntToStr(FFailCount))
  232. else if Data = 'passed' then
  233. newChild := FTemplate.createTextNode(IntToStr(FPassed))
  234. else if Data = 'passed-negative' then
  235. newChild := FTemplate.createTextNode(IntToStr(FFalsePasses))
  236. else if Data = 'skipped' then
  237. newChild := FTemplate.createTextNode(IntToStr(FSkipped))
  238. else if Data = 'status' then
  239. newChild := FTemplate.createTextNode (FState);
  240. Element.replaceChild (newChild, child);
  241. Continue;
  242. end
  243. // if/endif don't nest, and always have the same parent
  244. // we rely on those facts here!
  245. else if Child.NodeName = 'if' then
  246. begin
  247. Remove := not (((Data = 'validating') and FValidating) or
  248. ((Data = 'nonvalidating') and not FValidating));
  249. element.removeChild(child);
  250. Dec(Index);
  251. Continue;
  252. end
  253. else if Child.NodeName = 'endif' then
  254. begin
  255. Remove := False;
  256. element.removeChild(child);
  257. Dec(Index);
  258. Continue;
  259. end;
  260. until False;
  261. Children.Free;
  262. end;
  263. procedure TTestSuite.Run(const Tests: string);
  264. var
  265. Cases: TDOMNodeList;
  266. I: Integer;
  267. begin
  268. FRootURI := FilenameToURI(Tests);
  269. ReadXMLFile(FDoc, Tests);
  270. FSuiteTitle := FDoc.DocumentElement['PROFILE'];
  271. Cases := FDoc.DocumentElement.GetElementsByTagName('TEST');
  272. writeln('Using test suite: ', Tests);
  273. writeln;
  274. writeln('Testing, validation = ', FValidating);
  275. try
  276. for I := 0 to Cases.Count-1 do
  277. RunTest(Cases.Item[I] as TDOMElement);
  278. I := Cases.Count;
  279. finally
  280. Cases.Free;
  281. end;
  282. FPassed := FTotal-FFailCount;
  283. Dec(FPassed, FSkipped);
  284. writeln('Found ', I, ' basic test cases.');
  285. writeln('Found ', FTotal, ' overall test cases.');
  286. writeln('Skipped: ', FSkipped);
  287. writeln('Passed: ', FPassed);
  288. writeln('Failed: ', FFailCount);
  289. writeln('Negative passes: ', FFalsePasses, ' (need examination).');
  290. writeln;
  291. if FPassed = 0 then
  292. FState := 'N/A'
  293. else if FPassed = FTotal then
  294. FState := 'CONFORMS (provisionally)'
  295. else
  296. FState := 'DOES NOT CONFORM';
  297. end;
  298. procedure TTestSuite.RunTest(Element: TDOMElement);
  299. var
  300. s: UTF8string;
  301. TestType: DOMString;
  302. TempDoc, RefDoc: TXMLDocument;
  303. table: TDOMNode;
  304. Positive: Boolean;
  305. outURI: UTF8string;
  306. FailMsg: string;
  307. docNode, refNode: TDOMNode;
  308. docMap, refMap: TDOMNamedNodeMap;
  309. docN, refN: TDOMNotation;
  310. I: Integer;
  311. root: UTF8String;
  312. begin
  313. FTestID := Element['ID'];
  314. TestType := Element['TYPE'];
  315. root := GetBaseURI(Element, FRootUri);
  316. ResolveRelativeURI(root, UTF8Encode(Element['URI']), s);
  317. table := nil;
  318. outURI := '';
  319. Positive := False;
  320. if TestType = 'not-wf' then
  321. table := table_not_wf
  322. else if TestType = 'error' then
  323. table := table_informative
  324. else if TestType = 'valid' then
  325. begin
  326. if Element.hasAttribute('OUTPUT') then
  327. ResolveRelativeURI(root, UTF8Encode(Element['OUTPUT']), outURI);
  328. table := table_valid;
  329. Positive := True;
  330. end
  331. else if TestType = 'invalid' then
  332. begin
  333. table := table_invalid;
  334. Positive := not FValidating;
  335. end;
  336. if TestType <> 'error' then
  337. begin
  338. Inc(FTotal);
  339. if outURI <> '' then Inc(FTotal);
  340. end;
  341. FailMsg := '';
  342. FValError := '';
  343. TempDoc := nil;
  344. try
  345. try
  346. FParser.Options.Validate := FValidating;
  347. FParser.OnError := {$IFDEF FPC}@{$ENDIF}ErrorHandler;
  348. FParser.ParseUri(s, TempDoc);
  349. except
  350. on E: Exception do
  351. if E.ClassType <> EAbort then
  352. FailMsg := E.Message;
  353. end;
  354. if table = table_informative then
  355. begin
  356. if FailMsg <> '' then
  357. Diagnose(element, table, dcInfo, '(fatal) ' + FailMsg)
  358. else if FValError <> '' then
  359. Diagnose(element, table, dcInfo, '(error) ' + FValError)
  360. else
  361. Diagnose(Element, table, dcInfo, '');
  362. Exit;
  363. end;
  364. if not Positive then // must have been failed
  365. begin
  366. if (FailMsg = '') and (FValError = '') then
  367. begin
  368. Inc(FFailCount);
  369. Diagnose(element, table, dcNegfail, '');
  370. end
  371. else // FailMsg <> '' or FValError <> '' -> actually failed
  372. begin
  373. if FailMsg <> '' then // Fatal error
  374. begin
  375. Inc(FFalsePasses);
  376. Diagnose(Element, table, dcPass, FailMsg);
  377. end
  378. else
  379. begin
  380. if table = table_not_wf then // validation error here is a test failure!
  381. begin
  382. Inc(FFailCount);
  383. Diagnose(Element, table, dcFail, FValError);
  384. end
  385. else
  386. begin
  387. Inc(FFalsePasses);
  388. Diagnose(Element, table, dcPass, FValError);
  389. end;
  390. end;
  391. end;
  392. Exit;
  393. end
  394. else // must have been succeeded
  395. if (FailMsg <> '') or (FValError <> '') then
  396. begin
  397. Inc(FFailCount);
  398. if FailMsg <> '' then
  399. Diagnose(Element, table, dcFail, FailMsg)
  400. else
  401. Diagnose(Element, table, dcFail, FValError);
  402. if (outURI <> '') and (FailMsg <> '') then
  403. begin
  404. Inc(FFailCount);
  405. DiagnoseOut('[ input failed, no output to test ]');
  406. end;
  407. Exit;
  408. end;
  409. if outURI = '' then Exit;
  410. Canonicalize(TempDoc);
  411. TempDoc.DocumentElement.Normalize;
  412. try
  413. // reference data must be parsed in non-validating mode because it contains DTDs
  414. // only when Notations need to be reported
  415. FParser.Options.Validate := False;
  416. FParser.ParseUri(outURI, RefDoc);
  417. try
  418. docNode := TempDoc.FirstChild;
  419. refNode := RefDoc.FirstChild;
  420. repeat
  421. if refNode = nil then
  422. begin
  423. if docNode <> nil then
  424. begin
  425. Inc(FFailCount);
  426. DiagnoseOut('Extra data: ' + docNode.NodeName + ' / ' + docNode.NodeValue);
  427. end;
  428. Exit;
  429. end;
  430. if docNode = nil then
  431. begin
  432. Inc(FFailCount);
  433. DiagnoseOut('Missing data: ' + refNode.NodeName + ' / ' + refNode.NodeValue);
  434. Exit;
  435. end;
  436. if refNode.NodeType = DOCUMENT_TYPE_NODE then
  437. begin
  438. if docNode.NodeType <> DOCUMENT_TYPE_NODE then
  439. begin
  440. Inc(FFailCount);
  441. DiagnoseOut('[ no doctype from parsing testcase ]');
  442. Exit;
  443. end;
  444. refMap := TDOMDocumentType(refNode).Notations;
  445. docMap := TDOMDocumentType(docNode).Notations;
  446. for I := 0 to refMap.Length-1 do
  447. begin
  448. refN := TDOMNotation(refMap[I]);
  449. docN := TDOMNotation(docMap.GetNamedItem(refMap[I].NodeName));
  450. if not Assigned(docN) then
  451. begin
  452. Inc(FFailCount);
  453. DiagnoseOut('missing notation declaration: ' + refN.NodeName);
  454. Exit;
  455. end;
  456. if (refN.PublicID <> docN.PublicID) or (refN.SystemID <> docN.SystemID) then
  457. begin
  458. Inc(FFailCount);
  459. DiagnoseOut('incorrect notation declaration: ' + refN.NodeName);
  460. Exit;
  461. end;
  462. end;
  463. refNode := refNode.NextSibling;
  464. docNode := docNode.NextSibling;
  465. Continue;
  466. end;
  467. if docNode.NodeType = DOCUMENT_TYPE_NODE then // skip DocType
  468. docNode := docNode.NextSibling;
  469. if not CompareNodes(docNode, refNode, FailMsg) then
  470. begin
  471. Inc(FFailCount);
  472. DiagnoseOut(FailMsg);
  473. Exit;
  474. end;
  475. docNode := docNode.NextSibling;
  476. refNode := refNode.NextSibling;
  477. until False;
  478. finally
  479. RefDoc.Free;
  480. end;
  481. except
  482. on E: Exception do
  483. begin
  484. Inc(FFailCount);
  485. DiagnoseOut('[ can''t read reference data: '+E.Message+' ]');
  486. end;
  487. end;
  488. finally
  489. TempDoc.Free;
  490. end;
  491. end;
  492. procedure TTestSuite.Diagnose(Element, Table: TDOMNode; Category: TDiagCategory;
  493. const Error: DOMString);
  494. var
  495. tr, td, txt, tmp: TDOMNode;
  496. s: DOMString;
  497. begin
  498. tr := FTemplate.CreateElement('tr');
  499. if Assigned(Element) then // column 1: section/chapter, if known
  500. begin
  501. s := TDOMElement(Element)['SECTIONS'];
  502. td := FTemplate.CreateElement('td');
  503. td.AppendChild(FTemplate.CreateTextNode(s));
  504. tr.AppendChild(td);
  505. end;
  506. td := FTemplate.CreateElement('td'); // column 2: test ID
  507. td.AppendChild(FTemplate.CreateTextNode(FTestID));
  508. tr.AppendChild(td);
  509. // third column is description
  510. if Assigned(Element) then
  511. begin
  512. td := FTemplate.CreateElement('td');
  513. txt := Element.FirstChild;
  514. while Assigned(txt) do
  515. begin
  516. td.AppendChild(txt.CloneNode(true, FTemplate));
  517. txt := txt.NextSibling;
  518. end;
  519. tr.AppendChild(td);
  520. end;
  521. // fourth column is reason
  522. td := FTemplate.CreateElement('td');
  523. if Element = nil then
  524. s := Error
  525. else if Category <> dcInfo then
  526. begin
  527. if Error <> '' then
  528. begin
  529. if FValError <> '' then
  530. s := '(error) ' + Error
  531. else
  532. s := '(fatal) ' + Error;
  533. end
  534. else
  535. s := '[wrongly accepted]';
  536. end
  537. else // informative
  538. begin
  539. if Error <> '' then
  540. s := Error
  541. else
  542. s := '[accepted]';
  543. end;
  544. // TODO: use &nbsp if text is empty
  545. txt := FTemplate.CreateTextNode(s);
  546. if (Category <> dcPass) and (Category <> dcInfo) then
  547. begin
  548. tmp := FTemplate.CreateElement('em');
  549. tmp.AppendChild(txt);
  550. txt := tmp;
  551. TDOMElement(td)['bgcolor'] := '#ffaacc';
  552. end;
  553. td.AppendChild(txt);
  554. tr.AppendChild(td);
  555. table.AppendChild(tr);
  556. end;
  557. procedure TTestSuite.DiagnoseOut(const ErrorMsg: DOMString);
  558. var
  559. tr, td, txt: TDOMNode;
  560. begin
  561. tr := FTemplate.CreateElement('tr');
  562. td := FTemplate.CreateElement('td');
  563. td.AppendChild(FTemplate.CreateTextNode(FTestID));
  564. tr.AppendChild(td);
  565. td := FTemplate.CreateElement('td');
  566. txt := FTemplate.CreateElement('em');
  567. txt.AppendChild(FTemplate.CreateTextNode(ErrorMsg));
  568. td.AppendChild(txt);
  569. TDOMElement(td)['bgcolor'] := '#ffaacc';
  570. tr.AppendChild(td);
  571. table_output.AppendChild(tr);
  572. end;
  573. procedure TTestSuite.Canonicalize(node: TDOMNode);
  574. var
  575. child, work: TDOMNode;
  576. Frag: TDOMDocumentFragment;
  577. begin
  578. child := node.FirstChild;
  579. while Assigned(child) do
  580. begin
  581. if child.NodeType = CDATA_SECTION_NODE then
  582. begin
  583. work := node.OwnerDocument.CreateTextNode(child.NodeValue);
  584. node.ReplaceChild(work, child);
  585. child := work;
  586. end
  587. else if child.NodeType = COMMENT_NODE then
  588. begin
  589. work := child.NextSibling;
  590. node.RemoveChild(child);
  591. child := work;
  592. Continue;
  593. end
  594. else if child.NodeType = ENTITY_REFERENCE_NODE then
  595. begin
  596. Frag := node.OwnerDocument.CreateDocumentFragment;
  597. try
  598. work := child.FirstChild;
  599. while Assigned(work) do
  600. begin
  601. Frag.AppendChild(work.CloneNode(true));
  602. work := work.NextSibling;
  603. end;
  604. work := Frag.FirstChild; // references may be nested
  605. if work = nil then
  606. work := Child.PreviousSibling;
  607. node.ReplaceChild(Frag, child);
  608. child := work;
  609. finally
  610. Frag.Free;
  611. end;
  612. Continue;
  613. end;
  614. if child.HasChildNodes then
  615. Canonicalize(child);
  616. child := child.NextSibling;
  617. end;
  618. end;
  619. function TTestSuite.CompareNodes(actual, correct: TDOMNode;
  620. out Msg: string): Boolean;
  621. var
  622. actAtts, refAtts: TDOMNamedNodeMap;
  623. actList, refList: TDOMNodeList;
  624. I: Integer;
  625. s1, s2: DOMString;
  626. begin
  627. Msg := '';
  628. Result := False;
  629. if actual.NodeType <> correct.NodeType then
  630. FmtStr(Msg, 'actual.NodeType (%d) != correct.NodeType (%d)', [actual.NodeType, correct.NodeType])
  631. else if actual.NodeName <> correct.NodeName then
  632. FmtStr(Msg, 'actual.NodeName (%s) != correct.NodeName (%s)', [actual.NodeName, correct.NodeName])
  633. else if actual.NodeValue <> correct.NodeValue then
  634. FmtStr(Msg, 'actual.NodeValue (%s) != correct.NodeValue (%s)', [actual.NodeValue, correct.NodeValue]);
  635. if Msg <> '' then
  636. Exit;
  637. if actual.NodeType = ELEMENT_NODE then
  638. begin
  639. // first, compare attributes
  640. actAtts := actual.Attributes;
  641. refAtts := correct.Attributes;
  642. if actAtts.Length <> refAtts.Length then
  643. begin
  644. FmtStr(Msg, 'Element ''%s'': attributes.length (%d) != %d', [actual.NodeName, actAtts.Length, refAtts.Length]);
  645. Exit;
  646. end;
  647. for I := 0 to actAtts.Length -1 do
  648. begin
  649. s1 := refAtts.GetNamedItem(actAtts[I].NodeName).NodeValue;
  650. s2 := actAtts[I].NodeValue;
  651. if s1 <> s2 then
  652. begin
  653. FmtStr(Msg, 'Element ''%s'', attribute ''%s'': actual.AttValue (%s) != correct.AttValue (%s)', [actual.NodeName, actAtts[I].NodeName, s2, s1]);
  654. Exit;
  655. end;
  656. end;
  657. // next, compare children
  658. actList := actual.ChildNodes;
  659. refList := correct.ChildNodes;
  660. try
  661. if actList.Count <> refList.Count then
  662. begin
  663. FmtStr(Msg, 'Element ''%s'': actual.ChildNodeCount (%d) != correct.ChildNodeCount (%d)', [actual.NodeName, actList.Count, refList.Count]);
  664. Exit;
  665. end;
  666. for I := 0 to actList.Count -1 do
  667. if not CompareNodes(actList[I], refList[I], Msg) then
  668. Exit;
  669. finally
  670. actList.Free;
  671. refList.Free;
  672. end;
  673. end;
  674. Result := True;
  675. end;
  676. var
  677. i: Integer;
  678. s: string;
  679. SuiteName, ReportName, TemplateName: string;
  680. Validation: Boolean;
  681. begin
  682. writeln('FCL driver for OASIS/NIST XML Test Suite');
  683. writeln('Copyright (c) 2006 by Sergei Gorelkin');
  684. TemplateName := ExtractFilePath(ParamStr(0)) + 'template.xml';
  685. if ParamCount < 2 then
  686. begin
  687. writeln;
  688. writeln('Usage: ', ParamStr(0), ' <suite> <report> [-t template][-v]');
  689. writeln(' -t: specify report template');
  690. writeln(' -v: validating mode');
  691. Exit;
  692. end;
  693. SuiteName := ExpandFilename(ParamStr(1));
  694. ReportName := ExpandFilename(ParamStr(2));
  695. i := 3;
  696. Validation := False;
  697. while i <= ParamCount do
  698. begin
  699. s := Lowercase(ParamStr(i));
  700. if s = '-v' then
  701. Validation := True
  702. else if s = '-t' then
  703. TemplateName := ExpandFileName(ParamStr(i+1));
  704. Inc(i);
  705. end;
  706. with TTestSuite.Create do
  707. try
  708. FSuiteName := SuiteName;
  709. FTemplateName := TemplateName;
  710. FValidating := Validation;
  711. LoadTemplate(FTemplateName);
  712. if Assigned(FTemplate) then
  713. begin
  714. Run(FSuiteName);
  715. HandleTemplatePIs(FTemplate.DocumentElement);
  716. writeln('Writing report to: ', ReportName);
  717. WriteXMLFile(FTemplate, ReportName);
  718. end;
  719. finally
  720. Free;
  721. end;
  722. end.