testgen.pp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  1. {**********************************************************************
  2. This file is part of the Free Component Library (FCL)
  3. Generates fpcunit code from w3.org XML test descriptions
  4. Copyright (c) 2008 by Sergei Gorelkin, [email protected]
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. program testgen;
  12. {$mode objfpc}{$H+}
  13. uses
  14. Classes, SysUtils, DOM, XMLRead, XMLWrite, URIParser;
  15. var
  16. cntr: Integer = 0;
  17. api: TXMLDocument;
  18. forced: Boolean = False;
  19. TestCount: Integer = 0;
  20. FailCount: Integer = 0;
  21. function PascalType(const s: WideString): string;
  22. begin
  23. if (s = 'DOMString') or (s = 'boolean') or (s = 'DOMError') or (s = 'double') then
  24. result := s
  25. else if s = 'int' then
  26. result := 'Integer'
  27. else if s = 'short' then
  28. result := 'SmallInt'
  29. else if s = 'Collection' then
  30. result := '_collection'
  31. else if s = 'List' then
  32. result := '_list'
  33. else if (Pos(WideString('DOM'), s) = 1) or (Pos(WideString('XPath'), s) = 1) then
  34. result := 'T' + s
  35. else
  36. result := 'TDOM'+s;
  37. end;
  38. function ReplaceQuotes(const s: WideString): string;
  39. var
  40. quoted: Boolean;
  41. begin
  42. quoted := (s[1] = '"') and (s[Length(s)] = '"');
  43. if quoted then
  44. result := UTF8Encode(Copy(s, 2, Length(s)-2))
  45. else
  46. result := UTF8Encode(s);
  47. result := StringReplace(result, '\"', '"', [rfReplaceAll]);
  48. result := StringReplace(result, '''', '''''', [rfreplaceAll]);
  49. result := StringReplace(result, '\n', '''#10''', [rfReplaceAll]);
  50. result := StringReplace(result, '\\', '\', [rfreplaceAll]);
  51. if quoted then
  52. result := '''' + result + '''';
  53. end;
  54. procedure AppendParam(var s: string; n: TDOMElement; const attName: DOMString);
  55. begin
  56. if n.HasAttribute(attName) then
  57. s := s + ReplaceQuotes(n[attName])
  58. else
  59. s := s + '''''';
  60. s := s + ', ';
  61. end;
  62. function FirstElement(n: TDOMNode): TDOMElement;
  63. var
  64. child: TDOMNode;
  65. begin
  66. child := n.FirstChild;
  67. while Assigned(child) and (child.nodeType <> ELEMENT_NODE) do
  68. child := child.NextSibling;
  69. result := TDOMElement(child);
  70. end;
  71. procedure GetChildElements(el: TDOMNode; List: TList);
  72. var
  73. child: TDOMNode;
  74. begin
  75. List.Clear;
  76. child := el.FirstChild;
  77. while Assigned(child) do
  78. begin
  79. if child.NodeType = ELEMENT_NODE then
  80. List.Add(child);
  81. child := child.NextSibling;
  82. end;
  83. end;
  84. procedure DumpUnprocessed(e: TDOMElement; dest: TStrings);
  85. var
  86. s: TStringStream;
  87. begin
  88. s := TStringStream.Create('');
  89. try
  90. writeXML(e, s);
  91. dest.Text := dest.Text + '(*****' + s.DataString + sLineBreak + '*)' + sLineBreak;
  92. finally
  93. s.Free;
  94. end;
  95. end;
  96. function CondToStr(e: TDOMElement; out r: string): Boolean;
  97. var
  98. tmp: string;
  99. child: TDOMNode;
  100. begin
  101. Result := True;
  102. if e.TagName = 'equals' then
  103. r := e['actual'] + ' = ' + ReplaceQuotes(e['expected'])
  104. else if e.TagName = 'notEquals' then
  105. r := e['actual'] + ' <> ' + ReplaceQuotes(e['expected'])
  106. else if e.TagName = 'less' then
  107. r := e['actual'] + ' < ' + ReplaceQuotes(e['expected'])
  108. else if e.TagName = 'greater' then
  109. r := e['actual'] + ' > ' + ReplaceQuotes(e['expected'])
  110. // casting to Pointer works for both objects and strings
  111. else if e.TagName = 'isNull' then
  112. r := 'Pointer(' + e['obj'] + ') = nil'
  113. else if e.TagName = 'notNull' then
  114. r := 'Assigned(Pointer('+e['obj']+'))'
  115. else if e.TagName = 'isTrue' then
  116. r := e['value']
  117. else if (e.TagName = 'notTrue') or (e.TagName = 'isFalse') then
  118. r := 'not ' + e['value']
  119. else if e.TagName = 'contentType' then
  120. r := 'ContentTypeIs('''+e['type']+''')'
  121. else if e.TagName = 'implementationAttribute' then
  122. begin
  123. r := 'implementationAttribute[''' + e['name'] + '''] = ' + e['value'];
  124. end
  125. else if e.TagName = 'contains' then
  126. begin
  127. if e['interface'] = 'DOMString' then
  128. r := 'Pos(WideString(' + replaceQuotes(e['str']) + '), ' + e['obj'] + ') > 0'
  129. else
  130. r := 'bad_condition(''contains intf=' + e['interface'] + ''')';
  131. end
  132. else if e.TagName = 'same' then
  133. begin
  134. // maybe it would be sufficient to just compare pointers, but let's emit a helper for now
  135. r := 'IsSame('+ e['expected'] + ', ' + e['actual'] + ')';
  136. end
  137. else if e.TagName = 'not' then
  138. begin
  139. child := e.FirstChild;
  140. while Assigned(child) do
  141. begin
  142. if child.nodeType = ELEMENT_NODE then
  143. begin
  144. if CondToStr(TDOMElement(child), tmp) then
  145. r := 'not ('+tmp+')';
  146. Break;
  147. end;
  148. child := child.NextSibling;
  149. end;
  150. end
  151. else if (e.TagName = 'and') or (e.TagName = 'or') then
  152. begin
  153. r := '';
  154. child := e.FirstChild;
  155. while Assigned(child) do
  156. begin
  157. if child.nodeType = ELEMENT_NODE then
  158. begin
  159. if CondToStr(TDOMElement(child), tmp) then
  160. begin
  161. if r <> '' then r := r + ' ' + e.TagName + ' ';
  162. r := r + '('+tmp+')';
  163. end;
  164. end;
  165. child := child.NextSibling;
  166. end;
  167. end
  168. else
  169. begin
  170. r := 'bad_condition(''' + e.TagName + ''')';
  171. Result := False;
  172. end;
  173. end;
  174. procedure ConvertTest(rootNode: TDOMElement; rslt: TStrings);
  175. var
  176. child, subchild: TDOMNode;
  177. n: DOMString;
  178. SuccessVarFlag: Boolean;
  179. FailFlag: Boolean;
  180. Inits, VarTypes: TStringList;
  181. function TypeOfVar(const varname: string): string;
  182. begin
  183. result := VarTypes.Values[varname];
  184. end;
  185. function IsCollection(node: TDOMElement): Boolean;
  186. var
  187. s: string;
  188. begin
  189. s := TypeOfVar(node['collection']);
  190. Result := (s = '_collection') or (s = '_list');
  191. end;
  192. procedure CastTo(node: TDOMElement; const typename: string);
  193. begin
  194. if (not node.HasAttribute('interface')) and
  195. node.HasAttribute('obj') and
  196. (TypeOfVar(node['obj']) <> PascalType(typename)) then
  197. node['interface'] := typename;
  198. end;
  199. function getobj(e: TDOMElement): string;
  200. var
  201. s: string;
  202. begin
  203. result := e['obj'];
  204. if e.HasAttribute('interface') then
  205. begin
  206. s := PascalType(e['interface']);
  207. if TypeOfVar(e['obj']) <> s then
  208. result := s+'('+result+')';
  209. end;
  210. end;
  211. function prop_call(e: TDOMElement): string;
  212. begin
  213. if e.HasAttribute('var') then
  214. Result := e['var'] + ' := ' + getobj(e) + '.' + e.TagName + ';'
  215. else
  216. Result := getobj(e) + '.' + e.TagName + ' := ' + ReplaceQuotes(e['value']) + ';';
  217. end;
  218. function func_call(e: TDOMElement; const args: array of DOMString; const rsltType: string=''): string;
  219. var
  220. I: Integer;
  221. begin
  222. if (rsltType <> '') and (TypeOfVar(e['var']) <> rsltType) then
  223. Result := rsltType + '(' + e['var'] + ')'
  224. else
  225. Result := e['var'];
  226. Result := Result + ' := ' + getobj(e) + '.' + e.TagName;
  227. if Length(args) > 0 then
  228. begin
  229. Result := Result + '(';
  230. for I := 0 to High(args) do
  231. begin
  232. Result := Result + ReplaceQuotes(e[args[I]]);
  233. if I <> High(args) then
  234. Result := Result + ', ';
  235. end;
  236. Result := Result + ')';
  237. end;
  238. Result := Result + ';';
  239. end;
  240. function method_call(e: TDOMElement; args: TDOMNodeList): string;
  241. var
  242. I: Integer;
  243. begin
  244. Result := getobj(e) + '.' + e.TagName;
  245. if args.Length > 0 then
  246. begin
  247. Result := Result + '(';
  248. for I := 0 to args.Length-1 do
  249. begin
  250. Result := Result + ReplaceQuotes(e[args[I].TextContent]);
  251. if I <> args.Length-1 then
  252. Result := Result + ', ';
  253. end;
  254. Result := Result + ')';
  255. end;
  256. Result := Result + ';';
  257. end;
  258. procedure FixKeywords(node: TDOMElement; const AttrName: DOMString);
  259. var
  260. v: DOMString;
  261. begin
  262. v := node[AttrName];
  263. if v = 'testName' then // clash with TTest.TestName property
  264. node[AttrName] := 'test_Name'
  265. else if v = 'implementation' then
  266. node[AttrName] := 'DOMImpl'
  267. else if v = 'type' then
  268. node[AttrName] := 'type_';
  269. end;
  270. procedure ConvertStatement(node: TDOMElement; const indent: string);
  271. var
  272. s: DOMString;
  273. cond: string;
  274. apinode: TDOMElement;
  275. arglist: TDOMNodeList;
  276. args: array of DOMString;
  277. I: Integer;
  278. begin
  279. FixKeywords(node, 'var');
  280. FixKeywords(node, 'obj');
  281. s := node.TagName;
  282. apinode := api.GetElementById(s);
  283. // If not found by name only, try prepending the interface name.
  284. // This enables support of same-named methods with different param lists on different objects
  285. if (apinode = nil) and node.HasAttribute('interface') then
  286. apinode := api.GetElementById(node['interface'] + '.' + s);
  287. if assigned(apinode) then
  288. begin
  289. // handle most of DOM API in consistent way
  290. arglist := apinode.GetElementsByTagName('arg');
  291. SetLength(args, arglist.Length);
  292. for I := 0 to arglist.Length-1 do
  293. args[I] := arglist[I].TextContent;
  294. if apinode['type'] = 'prop' then
  295. rslt.Add(indent + prop_call(node))
  296. else if apinode['type'] = 'method' then
  297. begin
  298. if apinode.HasAttribute('objtype') then
  299. CastTo(node, apinode['objtype']);
  300. rslt.Add(indent + method_call(node, arglist));
  301. end
  302. else
  303. begin
  304. if apinode.HasAttribute('result') then
  305. cond := PascalType(apinode['result'])
  306. else
  307. cond := '';
  308. if apinode.HasAttribute('objtype') then
  309. CastTo(node, apinode['objtype']);
  310. rslt.Add(indent + func_call(node, args, cond));
  311. if apinode['gc'] = 'yes' then
  312. rslt.Add(indent + 'GC(' + node['var'] + ');');
  313. end;
  314. Exit;
  315. end;
  316. // now, various hacks and workarounds
  317. // TODO: modify DOM to expose item() as function
  318. if s = 'item' then
  319. rslt.Add(indent + 'TDOMNode('+node['var'] + ') := ' + node['obj'] + '['+node['index']+'];')
  320. else if s = 'length' then
  321. begin
  322. if node['interface'] = 'DOMString' then
  323. rslt.Add(indent + node['var'] + ' := system.length(' + node['obj'] + ');')
  324. else
  325. rslt.Add(indent + func_call(node, []));
  326. end
  327. else if s = 'implementation' then
  328. begin
  329. if node.HasAttribute('obj') then
  330. rslt.Add(indent + node['var'] + ' := ' + node['obj'] + '.impl;')
  331. else
  332. rslt.Add(indent + node['var'] + ' := GetImplementation;');
  333. end
  334. else if s = 'hasFeature' then
  335. begin
  336. if node.hasAttribute('var') then
  337. begin
  338. // we don't have null strings, replace with an empty one
  339. if not node.hasAttribute('version') then
  340. node['version'] := '""';
  341. rslt.Add(indent + func_call(node, ['feature', 'version']))
  342. end
  343. else
  344. rslt.Add(indent + 'CheckFeature(' + ReplaceQuotes(node['feature']) + ');')
  345. end
  346. // service (non-DOM) statements follow
  347. else if s = 'append' then
  348. rslt.Add(indent + '_append(' + node['collection'] + ', ' + ReplaceQuotes(node['item']) + ');')
  349. else if s = 'assign' then
  350. begin
  351. cond := TypeOfVar(node['var']);
  352. if (cond = '_collection') or (cond = '_list') then
  353. rslt.Add(indent + '_assign(' + node['var'] + ', ' + node['value'] + ');')
  354. else // emit an assignment operator. Force type for the case where they assign Document to Element.
  355. rslt.Add(indent + node['var'] + ' := ' + TypeOfVar(node['var']) + '(' + ReplaceQuotes(node['value']) + ');');
  356. end
  357. else if s = 'increment' then
  358. rslt.Add(indent + 'Inc(' + node['var'] + ', ' + node['value'] + ');')
  359. else if s = 'decrement' then
  360. rslt.Add(indent + 'Dec(' + node['var'] + ', ' + node['value'] + ');')
  361. else if s = 'plus' then
  362. rslt.Add(indent + node['var'] + ' := ' + ReplaceQuotes(node['op1']) + ' + ' + ReplaceQuotes(node['op2']) + ';')
  363. else if s = 'fail' then
  364. rslt.Add(indent + s + '(''' + node['id'] + ''');')
  365. else if s = 'assertEquals' then
  366. begin
  367. cond := TypeOfVar(node['actual']);
  368. if cond = '_collection' then
  369. rslt.Add(indent + 'AssertEqualsCollection(''' + node['id'] + ''', ' + ReplaceQuotes(node['expected']) + ', ' + node['actual'] + ');')
  370. else if cond = '_list' then
  371. rslt.Add(indent + 'AssertEqualsList(''' + node['id'] + ''', ' + ReplaceQuotes(node['expected']) + ', ' + node['actual'] + ');')
  372. else
  373. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + ReplaceQuotes(node['expected']) + ', ' + node['actual'] + ');');
  374. end
  375. else if s = 'assertSame' then
  376. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + ReplaceQuotes(node['expected']) + ', ' + node['actual'] + ');')
  377. else if (s = 'assertNull') or (s = 'assertNotNull') {or (s='assertFalse')} then
  378. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + node['actual'] + ');')
  379. else if s = 'assertSize' then
  380. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + node['size'] + ', ' + node['collection']+');')
  381. else if s = 'assertInstanceOf' then
  382. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + node['obj'] + ', ''' + PascalType(node['type'])+''');')
  383. else if (s = 'assertTrue') or (s='assertFalse') then
  384. if node.HasChildNodes then
  385. begin
  386. child := FirstElement(node);
  387. CondToStr(TDOMElement(child), cond);
  388. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + cond + ');');
  389. end
  390. else
  391. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + node['actual'] + ');')
  392. else if s = 'assertURIEquals' then
  393. begin
  394. // TODO: maybe add 'flags' argument to specify which strings are non-NULL
  395. cond := '''' + node['id'] + ''', ';
  396. AppendParam(cond, node, 'scheme');
  397. AppendParam(cond, node, 'path');
  398. AppendParam(cond, node, 'host');
  399. AppendParam(cond, node, 'file');
  400. AppendParam(cond, node, 'name');
  401. AppendParam(cond, node, 'query');
  402. AppendParam(cond, node, 'fragment');
  403. if node.HasAttribute('isAbsolute') then
  404. cond := cond + node['isAbsolute']
  405. else
  406. cond := cond + 'False';
  407. cond := cond + ', ';
  408. cond := cond + node['actual'];
  409. rslt.Add(indent + s + '(' + cond + ');');
  410. end
  411. else if n = 'load' then
  412. rslt.Add(indent + 'Load('+node['var']+', '''+ node['href']+''');')
  413. else if s = 'implementationAttribute' then
  414. rslt.Add(indent + s + '[''' + node['name'] + '''] := ' + node['value'] + ';')
  415. else if s = 'createXPathEvaluator' then
  416. rslt.Add(indent + node['var'] + ' := CreateXPathEvaluator(' + node['document'] + ');')
  417. else if s = 'comment' then
  418. rslt.Add(indent + '{ Source comment: ' + node.TextContent + ' }')
  419. else
  420. begin
  421. if not FailFlag then
  422. rslt.Add(indent + 'Fail(''This test is not completely converted'');');
  423. FailFlag := True;
  424. DumpUnprocessed(node, rslt);
  425. end;
  426. end;
  427. procedure ConvertException(el: TDOMElement; const ExceptClass: string; indent: string);
  428. var
  429. excode: string;
  430. begin
  431. if not SuccessVarFlag then
  432. rslt.Insert(2, ' success: Boolean;');
  433. SuccessVarFlag := True;
  434. rslt.Add(indent+'success := False;');
  435. rslt.Add(indent+'try');
  436. child := el.FirstChild;
  437. while assigned(child) do
  438. begin
  439. if child.nodeType = ELEMENT_NODE then
  440. begin
  441. excode := child.nodeName;
  442. subchild := child.FirstChild;
  443. while Assigned(subchild) do
  444. begin
  445. if subchild.nodeType = ELEMENT_NODE then
  446. ConvertStatement(TDOMElement(subchild), indent + ' ');
  447. subchild := subchild.NextSibling;
  448. end;
  449. end;
  450. child := child.NextSibling;
  451. end;
  452. rslt.Add(indent+'except');
  453. rslt.Add(indent+' on E: Exception do');
  454. rslt.Add(indent+' success := (E is ' + ExceptClass +') and (' + ExceptClass + '(E).Code = ' + excode + ');');
  455. rslt.Add(indent+'end;');
  456. rslt.Add(indent+'AssertTrue('''+el['id']+''', success);');
  457. end;
  458. procedure ConvertBlock(el: TDOMNode; indent: string);
  459. var
  460. curr: TDOMNode;
  461. element: TDOMElement;
  462. List: TList;
  463. cond: string;
  464. Frag: TDOMDocumentFragment;
  465. I: Integer;
  466. ElseNode: TDOMNode;
  467. IsColl: Boolean;
  468. begin
  469. List := TList.Create;
  470. curr := el.FirstChild;
  471. indent := indent + ' ';
  472. while Assigned(curr) do
  473. begin
  474. if (curr.NodeType <> ELEMENT_NODE) or
  475. (curr.NodeName = 'var') or (curr.NodeName = 'metadata') then
  476. begin
  477. curr := curr.NextSibling;
  478. Continue;
  479. end;
  480. element := TDOMElement(curr);
  481. n := element.TagName;
  482. if n = 'assertDOMException' then
  483. ConvertException(element, 'EDOMError', indent)
  484. else if n = 'assertXPathException' then
  485. ConvertException(element, 'EXPathException', indent)
  486. else if n = 'try' then
  487. begin
  488. GetChildElements(curr, List);
  489. rslt.Add(indent+'try');
  490. I := 0;
  491. while I < List.Count do
  492. begin
  493. Child := TDOMNode(List[I]);
  494. if Child.NodeName = 'catch' then
  495. break;
  496. ConvertStatement(TDOMElement(child), indent + ' ');
  497. Inc(I);
  498. end;
  499. if (child.NodeName <> 'catch') or (Pointer(Child) <> List.Last) then
  500. rslt.Add('{ ERROR: misplaced "catch" tag }');
  501. GetChildElements(child, List);
  502. cond := '';
  503. for I := 0 to List.Count-1 do
  504. begin
  505. if TDOMElement(List[I]).TagName <> 'DOMException' then
  506. begin
  507. rslt.Add('{ ERROR: unhandled: ' + TDOMElement(List[I]).TagName +' }');
  508. Break;
  509. end;
  510. if cond <> '' then cond := cond + ', ';
  511. cond := cond + TDOMElement(List[I])['code'];
  512. end;
  513. rslt.Add(indent+'except');
  514. rslt.Add(indent+' on E: EDOMError do');
  515. rslt.Add(indent+' if not (E.code in ['+cond+']) then raise;');
  516. rslt.Add(indent+'end;');
  517. end
  518. else if n = 'if' then
  519. begin
  520. ElseNode := nil;
  521. GetChildElements(curr, List);
  522. if (List.Count > 1) and CondToStr(TDOMElement(List[0]), cond) then
  523. begin
  524. rslt.Add(indent+ 'if '+cond+' then');
  525. frag := curr.OwnerDocument.CreateDocumentFragment;
  526. try
  527. // first node is the condition; skip it
  528. for I := 1 to List.Count-1 do
  529. begin
  530. child := TDOMNode(List[I]);
  531. if child.NodeName = 'else' then
  532. begin
  533. ElseNode := child;
  534. Break;
  535. end;
  536. frag.AppendChild(child.CloneNode(True));
  537. end;
  538. rslt.add(indent+'begin');
  539. ConvertBlock(frag, indent);
  540. if Assigned(ElseNode) then
  541. begin
  542. rslt.add(indent+'end');
  543. rslt.Add(indent+'else');
  544. rslt.Add(indent+'begin');
  545. ConvertBlock(ElseNode, indent);
  546. end;
  547. rslt.add(indent+'end;');
  548. finally
  549. frag.Free;
  550. end;
  551. end
  552. else
  553. begin
  554. rslt.Add('{ ERROR: malformed "if" tag }');
  555. dumpunprocessed(element, rslt);
  556. end;
  557. end
  558. else if n = 'for-each' then
  559. begin
  560. // having loop var name globally unique isn't a must.
  561. cond := 'loop'+IntToStr(cntr);
  562. Inc(cntr);
  563. rslt.Insert(2, ' ' + cond + ': Integer;');
  564. IsColl := IsCollection(element);
  565. if IsColl then
  566. rslt.Add(indent+'for '+cond+' := 0 to ' + 'High(' + element['collection'] + ') do')
  567. else
  568. rslt.Add(indent+'for '+cond+' := 0 to ' + element['collection'] + '.Length-1 do');
  569. rslt.Add(indent+'begin');
  570. if IsColl then
  571. rslt.Add(indent+' ' + element['member'] + ' := '+element['collection']+'['+cond+'];')
  572. else
  573. rslt.Add(indent+' ' + 'TDOMNode('+element['member'] + ') := '+element['collection']+'['+cond+'];');
  574. ConvertBlock(element, indent);
  575. rslt.Add(indent+'end;');
  576. end
  577. else if n = 'while' then
  578. begin
  579. GetChildElements(curr, List);
  580. if (List.Count > 1) and CondToStr(TDOMElement(List[0]), cond) then
  581. begin
  582. rslt.Add(indent+ 'while '+cond+' do');
  583. frag := curr.OwnerDocument.CreateDocumentFragment;
  584. try
  585. for I := 1 to List.Count-1 do // skip first node which is the condition
  586. begin
  587. child := TDOMNode(List[I]);
  588. frag.AppendChild(child.CloneNode(True));
  589. end;
  590. rslt.add(indent+'begin');
  591. ConvertBlock(frag, indent);
  592. rslt.add(indent+'end;');
  593. finally
  594. frag.Free;
  595. end;
  596. end
  597. else
  598. begin
  599. rslt.Add('{ ERROR: malformed "while" tag }');
  600. DumpUnprocessed(element, rslt);
  601. end;
  602. end
  603. else
  604. ConvertStatement(element, indent);
  605. curr := curr.NextSibling;
  606. end;
  607. List.Free;
  608. end;
  609. procedure ConvertVars;
  610. var
  611. TypedConsts: TStrings;
  612. I, J: Integer;
  613. vars, subvars: TDOMNodeList;
  614. node: TDOMElement;
  615. hs: string;
  616. begin
  617. TypedConsts := TStringList.Create;
  618. vars := rootNode.GetElementsByTagName('var');
  619. if vars.Count > 0 then
  620. begin
  621. rslt.Add('var');
  622. for I := 0 to vars.Count-1 do
  623. begin
  624. node := TDOMElement(vars[I]);
  625. FixKeywords(node, 'name');
  626. if node.hasAttribute('isNull') or node.hasAttribute('value') then
  627. begin
  628. // TODO: isNull is identified by 'yes' value, not by mere attr presence?
  629. // TODO: consider putting isNull things to constants
  630. if node.hasAttribute('value') then
  631. hs := ReplaceQuotes(Node['value'])
  632. else
  633. begin
  634. if node['type'] = 'DOMString' then
  635. hs := ''''''
  636. else
  637. hs := 'nil';
  638. end;
  639. Inits.Add(' ' + node['name'] + ' := ' + hs + ';');
  640. end;
  641. if Node.HasChildNodes then
  642. begin
  643. subvars := Node.GetElementsByTagName('member');
  644. try
  645. if subvars.Count > 0 then
  646. begin
  647. if TDOMElement(subvars[0]).HasAttribute('type') then
  648. hs := PascalType(TDOMElement(subvars[0]).GetAttribute('type'))
  649. else
  650. hs := 'DOMString';
  651. TypedConsts.Add(' ' + Node['name'] + ': array[0..' + IntToStr(subvars.Count-1) + '] of ' + hs + ' = (');
  652. for J := 0 to subvars.Count-1 do
  653. begin
  654. hs := ' ' + ReplaceQuotes(subvars[J].TextContent);
  655. if J = subvars.Count-1 then
  656. TypedConsts.Add(hs + ');')
  657. else
  658. TypedConsts.Add(hs + ',');
  659. end;
  660. end
  661. else
  662. DumpUnprocessed(Node, rslt);
  663. finally
  664. subvars.Free;
  665. end;
  666. end
  667. else
  668. rslt.Add(' ' + Node['name'] +': '+ PascalType(Node['type'])+';');
  669. VarTypes.Add(Node['name'] + '=' + PascalType(Node['type']));
  670. end;
  671. if TypedConsts.Count > 0 then
  672. begin
  673. rslt.add('const');
  674. rslt.AddStrings(TypedConsts);
  675. end;
  676. end;
  677. vars.Free;
  678. TypedConsts.Free;
  679. end;
  680. // ConvertTest() itself
  681. begin
  682. SuccessVarFlag := False;
  683. FailFlag := False;
  684. VarTypes := TStringList.Create;
  685. Inits := TStringList.Create;
  686. ConvertVars;
  687. rslt.add('begin');
  688. rslt.AddStrings(Inits);
  689. Inits.Free;
  690. ConvertBlock(rootNode, '');
  691. VarTypes.Free;
  692. rslt.add('end;');
  693. rslt.Add('');
  694. if FailFlag then
  695. begin
  696. if not forced then
  697. rslt.Clear;
  698. Inc(FailCount);
  699. end;
  700. end;
  701. // Intercepting validation errors while loading API
  702. type
  703. TErrHandler = class(TObject)
  704. public
  705. procedure HandleError(E: EXMLReadError);
  706. end;
  707. procedure TErrHandler.HandleError(E: EXMLReadError);
  708. begin
  709. raise E;
  710. end;
  711. const
  712. UnitHeader =
  713. '{ AUTOGENERATED FILE - DO NOT EDIT'#10+
  714. ' This Pascal source file was generated by testgen program'#10 +
  715. ' and is a derived work from the source document.'#10 +
  716. ' The source document contained the following notice:'#10+
  717. '%s}'#10+
  718. 'unit %s;'#10 +
  719. '{$mode objfpc}{$h+}'#10 +
  720. '{$notes off}'#10 +
  721. '{$codepage utf8}'#10 +
  722. 'interface'#10 +
  723. #10 +
  724. 'uses'#10 +
  725. ' SysUtils, Classes, DOM, xmlread, fpcunit, contnrs, domunit, testregistry;'#10 +
  726. #10 +
  727. 'type'#10 +
  728. ' %s = class(TDOMTestBase)'#10 +
  729. ' protected'#10 +
  730. ' function GetTestFilesURI: string; override;'#10 +
  731. ' published'#10;
  732. procedure ConvertSuite(const BaseURI: DOMString; const UnitFileName: string);
  733. var
  734. suite, testdoc: TXMLDocument;
  735. testlist: TDOMNodeList;
  736. root: TDOMElement;
  737. href, testuri: DOMString;
  738. I: Integer;
  739. sl, all, impl: TStringList;
  740. Pars: TDOMParser;
  741. eh: TErrHandler;
  742. class_name, unit_name, notice: string;
  743. comment: TDOMNode;
  744. begin
  745. Pars := TDOMParser.Create;
  746. eh := TErrHandler.Create;
  747. Pars.Options.Validate := True;
  748. Pars.OnError := @eh.HandleError;
  749. // API database must be loaded in validating mode
  750. Pars.ParseURI('file:api.xml', api);
  751. sl := TStringList.Create;
  752. all := TStringList.Create;
  753. impl := TStringList.Create;
  754. Pars.OnError := nil;
  755. Pars.Options.ExpandEntities := True;
  756. Pars.ParseURI(BaseURI + 'alltests.xml', suite);
  757. // extract the copyright notice
  758. notice := '';
  759. comment := suite.FirstChild;
  760. while Assigned(comment) do
  761. begin
  762. if (comment.nodeType = COMMENT_NODE) and
  763. (Pos(DOMString('Copyright'), comment.nodeValue) > 0) then
  764. begin
  765. notice := comment.nodeValue;
  766. Break;
  767. end;
  768. comment := comment.nextSibling;
  769. end;
  770. unit_name := ChangeFileExt(ExtractFileName(UnitFileName), '');
  771. class_name := 'TTest' + UpperCase(unit_name[1]) + copy(unit_name, 2, MaxInt);
  772. // provide unit header
  773. all.Text := Format(UnitHeader, [notice, unit_name, class_name]);
  774. // emit the 'GetPathToModuleFiles' function body
  775. impl.Add('implementation');
  776. impl.Add('');
  777. impl.Add('function '+class_name+'.GetTestFilesURI: string;');
  778. impl.Add('begin');
  779. impl.Add(' result := ''' + BaseURI + ''';');
  780. impl.Add('end;');
  781. impl.Add('');
  782. testlist := suite.GetElementsByTagName('suite.member');
  783. testcount := testlist.Count;
  784. writeln;
  785. writeln(testcount, ' test cases found');
  786. for I := 0 to testcount-1 do
  787. begin
  788. href := TDOMElement(testlist[I])['href'];
  789. // simple concatenation should suffice, but be paranoid
  790. ResolveRelativeURI(BaseURI, href, testuri);
  791. Pars.ParseURI(testuri, testdoc);
  792. try
  793. sl.Clear;
  794. root := testdoc.DocumentElement;
  795. // fix clash with local vars having the same name
  796. if root['name'] = 'attrname' then
  797. root['name'] := 'attr_name';
  798. sl.Add('procedure ' + class_name + '.' + root['name'] + ';');
  799. try
  800. ConvertTest(root, sl);
  801. except
  802. Writeln('An exception occured while converting '+root['name']);
  803. raise;
  804. end;
  805. if sl.Count > 0 then
  806. begin
  807. all.add(' procedure '+root['name']+';');
  808. impl.AddStrings(sl)
  809. end;
  810. finally
  811. testdoc.Free;
  812. end;
  813. end;
  814. testlist.Free;
  815. suite.Free;
  816. // terminate class declaration
  817. all.Add(' end;');
  818. all.Add('');
  819. // append all procedure bodies
  820. all.AddStrings(impl);
  821. all.Add('initialization');
  822. all.Add(' RegisterTest('+class_name+');');
  823. all.Add('end.');
  824. all.SaveToFile(UnitFileName);
  825. impl.Free;
  826. all.Free;
  827. sl.Free;
  828. eh.Free;
  829. Pars.Free;
  830. end;
  831. var
  832. SuiteName: string;
  833. OutputUnit: string;
  834. s: string;
  835. I: Integer;
  836. begin
  837. writeln('testgen - w3.org DOM test suite to Pascal converter');
  838. writeln('Copyright (c) 2008 by Sergei Gorelkin');
  839. if ParamCount < 2 then
  840. begin
  841. writeln;
  842. writeln('Usage: ', ParamStr(0), ' <suite dir> <outputunit.pp> [-f]');
  843. writeln(' -f: force conversion of tests which contain unknown tags');
  844. Exit;
  845. end;
  846. SuiteName := ExpandFilename(ParamStr(1));
  847. OutputUnit := ExpandFilename(ParamStr(2));
  848. i := 3;
  849. while i <= ParamCount do
  850. begin
  851. s := Lowercase(ParamStr(i));
  852. if s = '-f' then
  853. forced := True;
  854. Inc(i);
  855. end;
  856. // strip filename if present, we're going to read all dir
  857. if not DirectoryExists(SuiteName) then
  858. SuiteName := ExtractFilePath(SuiteName)
  859. else
  860. SuiteName := IncludeTrailingPathDelimiter(SuiteName);
  861. ConvertSuite(FilenameToURI(SuiteName), OutputUnit);
  862. writeln(testcount - FailCount, ' tests converted successfully');
  863. if FailCount > 0 then
  864. begin
  865. writeln(FailCount, ' tests contain tags that are not supported yet');
  866. if forced then
  867. begin
  868. writeln('Conversion of these tests was forced,');
  869. writeln('the resulting file may not compile!');
  870. end
  871. else
  872. writeln('These tests were skipped');
  873. end;
  874. end.