xmlts.pp 22 KB

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