xmlts.pp 22 KB

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