xmlts.pp 21 KB

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