xmlts.pp 22 KB

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