testgen.pp 25 KB

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