xmlts.pp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  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. TempDoc.DocumentElement.Normalize;
  443. try
  444. // reference data must be parsed in non-validating mode because it contains DTDs
  445. // only when Notations need to be reported
  446. FParser.Options.Validate := False;
  447. FParser.ParseUri(outURI, RefDoc);
  448. try
  449. docNode := TempDoc.FirstChild;
  450. refNode := RefDoc.FirstChild;
  451. repeat
  452. if refNode = nil then
  453. begin
  454. if docNode <> nil then
  455. begin
  456. Inc(FFailCount);
  457. DiagnoseOut('Extra data: ' + docNode.NodeName + ' / ' + docNode.NodeValue);
  458. end;
  459. Exit;
  460. end;
  461. if docNode = nil then
  462. begin
  463. Inc(FFailCount);
  464. DiagnoseOut('Missing data: ' + refNode.NodeName + ' / ' + refNode.NodeValue);
  465. Exit;
  466. end;
  467. if refNode.NodeType = DOCUMENT_TYPE_NODE then
  468. begin
  469. if docNode.NodeType <> DOCUMENT_TYPE_NODE then
  470. begin
  471. Inc(FFailCount);
  472. DiagnoseOut('[ no doctype from parsing testcase ]');
  473. Exit;
  474. end;
  475. refMap := TDOMDocumentType(refNode).Notations;
  476. docMap := TDOMDocumentType(docNode).Notations;
  477. for I := 0 to refMap.Length-1 do
  478. begin
  479. refN := TDOMNotation(refMap[I]);
  480. docN := TDOMNotation(docMap.GetNamedItem(refMap[I].NodeName));
  481. if not Assigned(docN) then
  482. begin
  483. Inc(FFailCount);
  484. DiagnoseOut('missing notation declaration: ' + refN.NodeName);
  485. Exit;
  486. end;
  487. if (refN.PublicID <> docN.PublicID) or (refN.SystemID <> docN.SystemID) then
  488. begin
  489. Inc(FFailCount);
  490. DiagnoseOut('incorrect notation declaration: ' + refN.NodeName);
  491. Exit;
  492. end;
  493. end;
  494. refNode := refNode.NextSibling;
  495. docNode := docNode.NextSibling;
  496. Continue;
  497. end;
  498. if docNode.NodeType = DOCUMENT_TYPE_NODE then // skip DocType
  499. docNode := docNode.NextSibling;
  500. if not CompareNodes(docNode, refNode, FailMsg) then
  501. begin
  502. Inc(FFailCount);
  503. DiagnoseOut(FailMsg);
  504. Exit;
  505. end;
  506. docNode := docNode.NextSibling;
  507. refNode := refNode.NextSibling;
  508. until False;
  509. finally
  510. RefDoc.Free;
  511. end;
  512. except
  513. on E: Exception do
  514. begin
  515. Inc(FFailCount);
  516. DiagnoseOut('[ can''t read reference data: '+E.Message+' ]');
  517. end;
  518. end;
  519. finally
  520. TempDoc.Free;
  521. end;
  522. end;
  523. procedure TTestSuite.Diagnose(Element, Table: TDOMNode; Category: TDiagCategory;
  524. const Error: DOMString);
  525. var
  526. tr, td, txt, tmp: TDOMNode;
  527. s: DOMString;
  528. begin
  529. tr := FTemplate.CreateElement('tr');
  530. if Assigned(Element) then // column 1: section/chapter, if known
  531. begin
  532. s := TDOMElement(Element)['SECTIONS'];
  533. td := FTemplate.CreateElement('td');
  534. td.AppendChild(FTemplate.CreateTextNode(s));
  535. tr.AppendChild(td);
  536. end;
  537. td := FTemplate.CreateElement('td'); // column 2: test ID
  538. td.AppendChild(FTemplate.CreateTextNode(FTestID));
  539. tr.AppendChild(td);
  540. // third column is description
  541. if Assigned(Element) then
  542. begin
  543. td := FTemplate.CreateElement('td');
  544. txt := Element.FirstChild;
  545. while Assigned(txt) do
  546. begin
  547. td.AppendChild(txt.CloneNode(true, FTemplate));
  548. txt := txt.NextSibling;
  549. end;
  550. tr.AppendChild(td);
  551. end;
  552. // fourth column is reason
  553. td := FTemplate.CreateElement('td');
  554. if Element = nil then
  555. s := Error
  556. else if Category <> dcInfo then
  557. begin
  558. if Error <> '' then
  559. begin
  560. if FValError <> '' then
  561. s := '(error) ' + Error
  562. else
  563. s := '(fatal) ' + Error;
  564. end
  565. else
  566. s := '[wrongly accepted]';
  567. end
  568. else // informative
  569. begin
  570. if Error <> '' then
  571. s := Error
  572. else
  573. s := '[accepted]';
  574. end;
  575. // TODO: use &nbsp if text is empty
  576. txt := FTemplate.CreateTextNode(s);
  577. if (Category <> dcPass) and (Category <> dcInfo) then
  578. begin
  579. tmp := FTemplate.CreateElement('em');
  580. tmp.AppendChild(txt);
  581. txt := tmp;
  582. TDOMElement(td)['bgcolor'] := '#ffaacc';
  583. end;
  584. td.AppendChild(txt);
  585. tr.AppendChild(td);
  586. table.AppendChild(tr);
  587. end;
  588. procedure TTestSuite.DiagnoseOut(const ErrorMsg: DOMString);
  589. var
  590. tr, td, txt: TDOMNode;
  591. begin
  592. tr := FTemplate.CreateElement('tr');
  593. td := FTemplate.CreateElement('td');
  594. td.AppendChild(FTemplate.CreateTextNode(FTestID));
  595. tr.AppendChild(td);
  596. td := FTemplate.CreateElement('td');
  597. txt := FTemplate.CreateElement('em');
  598. txt.AppendChild(FTemplate.CreateTextNode(ErrorMsg));
  599. td.AppendChild(txt);
  600. TDOMElement(td)['bgcolor'] := '#ffaacc';
  601. tr.AppendChild(td);
  602. table_output.AppendChild(tr);
  603. end;
  604. procedure Canonicalize(node: TDOMNode);
  605. var
  606. child, work: TDOMNode;
  607. Frag: TDOMDocumentFragment;
  608. begin
  609. child := node.FirstChild;
  610. while Assigned(child) do
  611. begin
  612. if child.NodeType = CDATA_SECTION_NODE then
  613. begin
  614. work := node.OwnerDocument.CreateTextNode(child.NodeValue);
  615. node.ReplaceChild(work, child);
  616. child := work;
  617. end
  618. else if child.NodeType = COMMENT_NODE then
  619. begin
  620. work := child.NextSibling;
  621. node.RemoveChild(child);
  622. child := work;
  623. Continue;
  624. end
  625. else if child.NodeType = ENTITY_REFERENCE_NODE then
  626. begin
  627. Frag := node.OwnerDocument.CreateDocumentFragment;
  628. try
  629. work := child.FirstChild;
  630. while Assigned(work) do
  631. begin
  632. Frag.AppendChild(work.CloneNode(true));
  633. work := work.NextSibling;
  634. end;
  635. work := Frag.FirstChild; // references may be nested
  636. if work = nil then
  637. work := Child.PreviousSibling;
  638. node.ReplaceChild(Frag, child);
  639. child := work;
  640. finally
  641. Frag.Free;
  642. end;
  643. Continue;
  644. end;
  645. if child.HasChildNodes then
  646. Canonicalize(child);
  647. child := child.NextSibling;
  648. end;
  649. end;
  650. function TTestSuite.CompareNodes(actual, correct: TDOMNode;
  651. out Msg: string): Boolean;
  652. var
  653. actAtts, refAtts: TDOMNamedNodeMap;
  654. actList, refList: TDOMNodeList;
  655. I: Integer;
  656. s1, s2: DOMString;
  657. begin
  658. Msg := '';
  659. Result := False;
  660. if actual.NodeType <> correct.NodeType then
  661. FmtStr(Msg, 'actual.NodeType (%d) != correct.NodeType (%d)', [actual.NodeType, correct.NodeType])
  662. else if actual.NodeName <> correct.NodeName then
  663. FmtStr(Msg, 'actual.NodeName (%s) != correct.NodeName (%s)', [actual.NodeName, correct.NodeName])
  664. else if actual.NodeValue <> correct.NodeValue then
  665. FmtStr(Msg, 'actual.NodeValue (%s) != correct.NodeValue (%s)', [actual.NodeValue, correct.NodeValue]);
  666. if Msg <> '' then
  667. Exit;
  668. if actual.NodeType = ELEMENT_NODE then
  669. begin
  670. // first, compare attributes
  671. actAtts := actual.Attributes;
  672. refAtts := correct.Attributes;
  673. if actAtts.Length <> refAtts.Length then
  674. begin
  675. FmtStr(Msg, 'Element ''%s'': attributes.length (%d) != %d', [actual.NodeName, actAtts.Length, refAtts.Length]);
  676. Exit;
  677. end;
  678. for I := 0 to actAtts.Length -1 do
  679. begin
  680. s1 := refAtts.GetNamedItem(actAtts[I].NodeName).NodeValue;
  681. s2 := actAtts[I].NodeValue;
  682. if s1 <> s2 then
  683. begin
  684. FmtStr(Msg, 'Element ''%s'', attribute ''%s'': actual.AttValue (%s) != correct.AttValue (%s)', [actual.NodeName, actAtts[I].NodeName, s2, s1]);
  685. Exit;
  686. end;
  687. end;
  688. // next, compare children
  689. actList := actual.ChildNodes;
  690. refList := correct.ChildNodes;
  691. try
  692. if actList.Count <> refList.Count then
  693. begin
  694. FmtStr(Msg, 'Element ''%s'': actual.ChildNodeCount (%d) != correct.ChildNodeCount (%d)', [actual.NodeName, actList.Count, refList.Count]);
  695. Exit;
  696. end;
  697. for I := 0 to actList.Count -1 do
  698. if not CompareNodes(actList[I], refList[I], Msg) then
  699. Exit;
  700. finally
  701. actList.Free;
  702. refList.Free;
  703. end;
  704. end;
  705. Result := True;
  706. end;
  707. var
  708. i: Integer;
  709. s: string;
  710. SuiteName, ReportName, TemplateName: string;
  711. Validation: Boolean;
  712. begin
  713. writeln('FCL driver for OASIS/NIST XML Test Suite');
  714. writeln('Copyright (c) 2006 by Sergei Gorelkin');
  715. TemplateName := ExtractFilePath(ParamStr(0)) + 'template.xml';
  716. if ParamCount < 2 then
  717. begin
  718. writeln;
  719. writeln('Usage: ', ParamStr(0), ' <suite> <report> [-t template][-v]');
  720. writeln(' -t: specify report template');
  721. writeln(' -v: validating mode');
  722. Exit;
  723. end;
  724. SuiteName := ExpandFilename(ParamStr(1));
  725. ReportName := ExpandFilename(ParamStr(2));
  726. i := 3;
  727. Validation := False;
  728. while i <= ParamCount do
  729. begin
  730. s := Lowercase(ParamStr(i));
  731. if s = '-v' then
  732. Validation := True
  733. else if s = '-t' then
  734. TemplateName := ExpandFileName(ParamStr(i+1));
  735. Inc(i);
  736. end;
  737. with TTestSuite.Create do
  738. try
  739. FSuiteName := SuiteName;
  740. FValidating := Validation;
  741. LoadTemplate(TemplateName);
  742. if Assigned(FTemplate) then
  743. begin
  744. Run(FSuiteName);
  745. HandleTemplatePIs(FTemplate.DocumentElement);
  746. writeln('Writing report to: ', ReportName);
  747. WriteXMLFile(FTemplate, ReportName);
  748. end;
  749. finally
  750. Free;
  751. end;
  752. end.