xmlts.pp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  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. 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.Item[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. docNode, refNode: TDOMNode;
  317. docMap, refMap: TDOMNamedNodeMap;
  318. docN, refN: TDOMNotation;
  319. I: Integer;
  320. root: UTF8String;
  321. begin
  322. FErrLine := -1;
  323. FErrCol := -1;
  324. FTestID := Element['ID'];
  325. TestType := Element['TYPE'];
  326. if Pos(WideChar('5'), Element['EDITION']) > 0 then
  327. begin
  328. Inc(FSkipped);
  329. Exit;
  330. end;
  331. root := GetBaseURI(Element, FRootUri);
  332. ResolveRelativeURI(root, UTF8Encode(Element['URI']), s);
  333. table := nil;
  334. outURI := '';
  335. Positive := False;
  336. if TestType = 'not-wf' then
  337. table := table_not_wf
  338. else if TestType = 'error' then
  339. table := table_informative
  340. else if TestType = 'valid' then
  341. begin
  342. if Element.hasAttribute('OUTPUT') then
  343. ResolveRelativeURI(root, UTF8Encode(Element['OUTPUT']), outURI);
  344. table := table_valid;
  345. Positive := True;
  346. end
  347. else if TestType = 'invalid' then
  348. begin
  349. table := table_invalid;
  350. Positive := not FValidating;
  351. end;
  352. if TestType <> 'error' then
  353. begin
  354. Inc(FTotal);
  355. if outURI <> '' then Inc(FTotal);
  356. end;
  357. FailMsg := '';
  358. FValError := '';
  359. TempDoc := nil;
  360. try
  361. try
  362. FParser.Options.Validate := FValidating;
  363. FParser.Options.Namespaces := (Element['NAMESPACE'] <> 'no');
  364. FParser.OnError := {$IFDEF FPC}@{$ENDIF}ErrorHandler;
  365. FParser.ParseUri(s, TempDoc);
  366. except
  367. on E: Exception do
  368. if E.ClassType <> EAbort then
  369. begin
  370. FailMsg := E.Message;
  371. FValError := '';
  372. end;
  373. end;
  374. if table = table_informative then
  375. begin
  376. if FailMsg <> '' then
  377. Diagnose(element, table, dcInfo, '(fatal) ' + FailMsg)
  378. else if FValError <> '' then
  379. Diagnose(element, table, dcInfo, '(error) ' + FValError)
  380. else
  381. Diagnose(Element, table, dcInfo, '');
  382. Exit;
  383. end;
  384. if not Positive then // must have been failed
  385. begin
  386. if (FailMsg = '') and (FValError = '') then
  387. begin
  388. Inc(FFailCount);
  389. Diagnose(element, table, dcNegfail, '');
  390. end
  391. else // FailMsg <> '' or FValError <> '' -> actually failed
  392. begin
  393. if FailMsg <> '' then // Fatal error
  394. begin
  395. { outside not-wf category it is a test failure }
  396. if table <> table_not_wf then
  397. begin
  398. Inc(FFailCount);
  399. Diagnose(Element, table, dcFail, FailMsg);
  400. end
  401. else
  402. begin
  403. Inc(FFalsePasses);
  404. Diagnose(Element, table, dcPass, FailMsg);
  405. end;
  406. end
  407. else
  408. begin
  409. { outside invalid category it is a test failure }
  410. if table = table_not_wf then
  411. begin
  412. Inc(FFailCount);
  413. Diagnose(Element, table, dcFail, FValError);
  414. end
  415. else
  416. begin
  417. Inc(FFalsePasses);
  418. Diagnose(Element, table, dcPass, FValError);
  419. end;
  420. end;
  421. end;
  422. Exit;
  423. end
  424. else // must have been succeeded
  425. if (FailMsg <> '') or (FValError <> '') then
  426. begin
  427. Inc(FFailCount);
  428. if FailMsg <> '' then
  429. Diagnose(Element, table, dcFail, FailMsg)
  430. else
  431. Diagnose(Element, table, dcFail, FValError);
  432. if (outURI <> '') and (FailMsg <> '') then
  433. begin
  434. Inc(FFailCount);
  435. DiagnoseOut('[ input failed, no output to test ]');
  436. end;
  437. Exit;
  438. end;
  439. if outURI = '' then Exit;
  440. TempDoc.DocumentElement.Normalize;
  441. try
  442. // reference data must be parsed in non-validating mode because it contains DTDs
  443. // only when Notations need to be reported
  444. FParser.Options.Validate := False;
  445. FParser.ParseUri(outURI, RefDoc);
  446. try
  447. docNode := TempDoc.FirstChild;
  448. refNode := RefDoc.FirstChild;
  449. repeat
  450. if refNode = nil then
  451. begin
  452. if docNode <> nil then
  453. begin
  454. Inc(FFailCount);
  455. DiagnoseOut('Extra data: ' + docNode.NodeName + ' / ' + docNode.NodeValue);
  456. end;
  457. Exit;
  458. end;
  459. if docNode = nil then
  460. begin
  461. Inc(FFailCount);
  462. DiagnoseOut('Missing data: ' + refNode.NodeName + ' / ' + refNode.NodeValue);
  463. Exit;
  464. end;
  465. if refNode.NodeType = DOCUMENT_TYPE_NODE then
  466. begin
  467. if docNode.NodeType <> DOCUMENT_TYPE_NODE then
  468. begin
  469. Inc(FFailCount);
  470. DiagnoseOut('[ no doctype from parsing testcase ]');
  471. Exit;
  472. end;
  473. refMap := TDOMDocumentType(refNode).Notations;
  474. docMap := TDOMDocumentType(docNode).Notations;
  475. for I := 0 to refMap.Length-1 do
  476. begin
  477. refN := TDOMNotation(refMap[I]);
  478. docN := TDOMNotation(docMap.GetNamedItem(refMap[I].NodeName));
  479. if not Assigned(docN) then
  480. begin
  481. Inc(FFailCount);
  482. DiagnoseOut('missing notation declaration: ' + refN.NodeName);
  483. Exit;
  484. end;
  485. if (refN.PublicID <> docN.PublicID) or (refN.SystemID <> docN.SystemID) then
  486. begin
  487. Inc(FFailCount);
  488. DiagnoseOut('incorrect notation declaration: ' + refN.NodeName);
  489. Exit;
  490. end;
  491. end;
  492. refNode := refNode.NextSibling;
  493. docNode := docNode.NextSibling;
  494. Continue;
  495. end;
  496. if docNode.NodeType = DOCUMENT_TYPE_NODE then // skip DocType
  497. docNode := docNode.NextSibling;
  498. if not CompareNodes(docNode, refNode, FailMsg) then
  499. begin
  500. Inc(FFailCount);
  501. DiagnoseOut(FailMsg);
  502. Exit;
  503. end;
  504. docNode := docNode.NextSibling;
  505. refNode := refNode.NextSibling;
  506. until False;
  507. finally
  508. RefDoc.Free;
  509. end;
  510. except
  511. on E: Exception do
  512. begin
  513. Inc(FFailCount);
  514. DiagnoseOut('[ can''t read reference data: '+E.Message+' ]');
  515. end;
  516. end;
  517. finally
  518. TempDoc.Free;
  519. end;
  520. end;
  521. procedure TTestSuite.Diagnose(Element, Table: TDOMNode; Category: TDiagCategory;
  522. const Error: DOMString);
  523. var
  524. tr, td, txt, tmp: TDOMNode;
  525. s: DOMString;
  526. begin
  527. tr := FTemplate.CreateElement('tr');
  528. if Assigned(Element) then // column 1: section/chapter, if known
  529. begin
  530. s := TDOMElement(Element)['SECTIONS'];
  531. td := FTemplate.CreateElement('td');
  532. td.AppendChild(FTemplate.CreateTextNode(s));
  533. tr.AppendChild(td);
  534. end;
  535. td := FTemplate.CreateElement('td'); // column 2: test ID
  536. td.AppendChild(FTemplate.CreateTextNode(FTestID));
  537. tr.AppendChild(td);
  538. // third column is description
  539. if Assigned(Element) then
  540. begin
  541. td := FTemplate.CreateElement('td');
  542. txt := Element.FirstChild;
  543. while Assigned(txt) do
  544. begin
  545. td.AppendChild(txt.CloneNode(true, FTemplate));
  546. txt := txt.NextSibling;
  547. end;
  548. tr.AppendChild(td);
  549. end;
  550. // fourth column is reason
  551. td := FTemplate.CreateElement('td');
  552. if Element = nil then
  553. s := Error
  554. else if Category <> dcInfo then
  555. begin
  556. if Error <> '' then
  557. begin
  558. if FValError <> '' then
  559. s := '(error) ' + Error
  560. else
  561. s := '(fatal) ' + Error;
  562. end
  563. else
  564. s := '[wrongly accepted]';
  565. end
  566. else // informative
  567. begin
  568. if Error <> '' then
  569. s := Error
  570. else
  571. s := '[accepted]';
  572. end;
  573. // TODO: use &nbsp if text is empty
  574. txt := FTemplate.CreateTextNode(s);
  575. if (Category <> dcPass) and (Category <> dcInfo) then
  576. begin
  577. tmp := FTemplate.CreateElement('em');
  578. tmp.AppendChild(txt);
  579. txt := tmp;
  580. TDOMElement(td)['bgcolor'] := '#ffaacc';
  581. end;
  582. td.AppendChild(txt);
  583. tr.AppendChild(td);
  584. table.AppendChild(tr);
  585. end;
  586. procedure TTestSuite.DiagnoseOut(const ErrorMsg: DOMString);
  587. var
  588. tr, td, txt: TDOMNode;
  589. begin
  590. tr := FTemplate.CreateElement('tr');
  591. td := FTemplate.CreateElement('td');
  592. td.AppendChild(FTemplate.CreateTextNode(FTestID));
  593. tr.AppendChild(td);
  594. td := FTemplate.CreateElement('td');
  595. txt := FTemplate.CreateElement('em');
  596. txt.AppendChild(FTemplate.CreateTextNode(ErrorMsg));
  597. td.AppendChild(txt);
  598. TDOMElement(td)['bgcolor'] := '#ffaacc';
  599. tr.AppendChild(td);
  600. table_output.AppendChild(tr);
  601. end;
  602. procedure Canonicalize(node: TDOMNode);
  603. var
  604. child, work: TDOMNode;
  605. Frag: TDOMDocumentFragment;
  606. begin
  607. child := node.FirstChild;
  608. while Assigned(child) do
  609. begin
  610. if child.NodeType = CDATA_SECTION_NODE then
  611. begin
  612. work := node.OwnerDocument.CreateTextNode(child.NodeValue);
  613. node.ReplaceChild(work, child);
  614. child := work;
  615. end
  616. else if child.NodeType = COMMENT_NODE then
  617. begin
  618. work := child.NextSibling;
  619. node.RemoveChild(child);
  620. child := work;
  621. Continue;
  622. end
  623. else if child.NodeType = ENTITY_REFERENCE_NODE then
  624. begin
  625. Frag := node.OwnerDocument.CreateDocumentFragment;
  626. try
  627. work := child.FirstChild;
  628. while Assigned(work) do
  629. begin
  630. Frag.AppendChild(work.CloneNode(true));
  631. work := work.NextSibling;
  632. end;
  633. work := Frag.FirstChild; // references may be nested
  634. if work = nil then
  635. work := Child.PreviousSibling;
  636. node.ReplaceChild(Frag, child);
  637. child := work;
  638. finally
  639. Frag.Free;
  640. end;
  641. Continue;
  642. end;
  643. if child.HasChildNodes then
  644. Canonicalize(child);
  645. child := child.NextSibling;
  646. end;
  647. end;
  648. function TTestSuite.CompareNodes(actual, correct: TDOMNode;
  649. out Msg: string): Boolean;
  650. var
  651. actAtts, refAtts: TDOMNamedNodeMap;
  652. actList, refList: TDOMNodeList;
  653. I: Integer;
  654. s1, s2: DOMString;
  655. begin
  656. Msg := '';
  657. Result := False;
  658. if actual.NodeType <> correct.NodeType then
  659. FmtStr(Msg, 'actual.NodeType (%d) != correct.NodeType (%d)', [actual.NodeType, correct.NodeType])
  660. else if actual.NodeName <> correct.NodeName then
  661. FmtStr(Msg, 'actual.NodeName (%s) != correct.NodeName (%s)', [actual.NodeName, correct.NodeName])
  662. else if actual.NodeValue <> correct.NodeValue then
  663. FmtStr(Msg, 'actual.NodeValue (%s) != correct.NodeValue (%s)', [actual.NodeValue, correct.NodeValue]);
  664. if Msg <> '' then
  665. Exit;
  666. if actual.NodeType = ELEMENT_NODE then
  667. begin
  668. // first, compare attributes
  669. actAtts := actual.Attributes;
  670. refAtts := correct.Attributes;
  671. if actAtts.Length <> refAtts.Length then
  672. begin
  673. FmtStr(Msg, 'Element ''%s'': attributes.length (%d) != %d', [actual.NodeName, actAtts.Length, refAtts.Length]);
  674. Exit;
  675. end;
  676. for I := 0 to actAtts.Length -1 do
  677. begin
  678. s1 := refAtts.GetNamedItem(actAtts[I].NodeName).NodeValue;
  679. s2 := actAtts[I].NodeValue;
  680. if s1 <> s2 then
  681. begin
  682. FmtStr(Msg, 'Element ''%s'', attribute ''%s'': actual.AttValue (%s) != correct.AttValue (%s)', [actual.NodeName, actAtts[I].NodeName, s2, s1]);
  683. Exit;
  684. end;
  685. end;
  686. // next, compare children
  687. actList := actual.ChildNodes;
  688. refList := correct.ChildNodes;
  689. try
  690. if actList.Count <> refList.Count then
  691. begin
  692. FmtStr(Msg, 'Element ''%s'': actual.ChildNodeCount (%d) != correct.ChildNodeCount (%d)', [actual.NodeName, actList.Count, refList.Count]);
  693. Exit;
  694. end;
  695. for I := 0 to actList.Count -1 do
  696. if not CompareNodes(actList[I], refList[I], Msg) then
  697. Exit;
  698. finally
  699. actList.Free;
  700. refList.Free;
  701. end;
  702. end;
  703. Result := True;
  704. end;
  705. var
  706. i: Integer;
  707. s: string;
  708. SuiteName, ReportName, TemplateName: string;
  709. Validation: Boolean;
  710. begin
  711. writeln('FCL driver for OASIS/NIST XML Test Suite');
  712. writeln('Copyright (c) 2006 by Sergei Gorelkin');
  713. TemplateName := ExtractFilePath(ParamStr(0)) + 'template.xml';
  714. if ParamCount < 2 then
  715. begin
  716. writeln;
  717. writeln('Usage: ', ParamStr(0), ' <suite> <report> [-t template][-v]');
  718. writeln(' -t: specify report template');
  719. writeln(' -v: validating mode');
  720. Exit;
  721. end;
  722. SuiteName := ExpandFilename(ParamStr(1));
  723. ReportName := ExpandFilename(ParamStr(2));
  724. i := 3;
  725. Validation := False;
  726. while i <= ParamCount do
  727. begin
  728. s := Lowercase(ParamStr(i));
  729. if s = '-v' then
  730. Validation := True
  731. else if s = '-t' then
  732. TemplateName := ExpandFileName(ParamStr(i+1));
  733. Inc(i);
  734. end;
  735. with TTestSuite.Create do
  736. try
  737. FSuiteName := SuiteName;
  738. FValidating := Validation;
  739. LoadTemplate(TemplateName);
  740. if Assigned(FTemplate) then
  741. begin
  742. Run(FSuiteName);
  743. HandleTemplatePIs(FTemplate.DocumentElement);
  744. writeln('Writing report to: ', ReportName);
  745. WriteXMLFile(FTemplate, ReportName);
  746. end;
  747. finally
  748. Free;
  749. end;
  750. end.