testgen.pp 30 KB

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