xmlts.pp 22 KB

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