xmlts.pp 22 KB

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