testgen.pp 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  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 node['ignoreCase'] = 'true' then
  391. rslt.Add(indent + 'AssertEqualsNoCase(''' + node['id'] + ''', ' + ReplaceQuotes(node['expected']) + ', ' + node['actual'] + ');')
  392. else
  393. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + ReplaceQuotes(node['expected']) + ', ' + node['actual'] + ');');
  394. end
  395. else if s = 'assertSame' then
  396. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + ReplaceQuotes(node['expected']) + ', ' + node['actual'] + ');')
  397. else if (s = 'assertNull') or (s = 'assertNotNull') {or (s='assertFalse')} then
  398. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + node['actual'] + ');')
  399. else if s = 'assertSize' then
  400. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + node['size'] + ', ' + node['collection']+');')
  401. else if s = 'assertInstanceOf' then
  402. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + node['obj'] + ', ''' + PascalType(node['type'])+''');')
  403. else if (s = 'assertTrue') or (s='assertFalse') then
  404. if node.HasChildNodes then
  405. begin
  406. child := FirstElement(node);
  407. CondToStr(TDOMElement(child), cond);
  408. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + cond + ');');
  409. end
  410. else
  411. rslt.Add(indent + s + '(''' + node['id'] + ''', ' + node['actual'] + ');')
  412. else if s = 'assertURIEquals' then
  413. begin
  414. // TODO: maybe add 'flags' argument to specify which strings are non-NULL
  415. cond := '''' + node['id'] + ''', ';
  416. AppendParam(cond, node, 'scheme');
  417. AppendParam(cond, node, 'path');
  418. AppendParam(cond, node, 'host');
  419. AppendParam(cond, node, 'file');
  420. AppendParam(cond, node, 'name');
  421. AppendParam(cond, node, 'query');
  422. AppendParam(cond, node, 'fragment');
  423. if node.HasAttribute('isAbsolute') then
  424. cond := cond + node['isAbsolute']
  425. else
  426. cond := cond + 'False';
  427. cond := cond + ', ';
  428. cond := cond + node['actual'];
  429. rslt.Add(indent + s + '(' + cond + ');');
  430. end
  431. else if n = 'load' then
  432. rslt.Add(indent + 'Load('+node['var']+', '''+ node['href']+''');')
  433. else if s = 'implementationAttribute' then
  434. begin
  435. if (node['name']='signed') and (node['value']='true') then
  436. IgnoreFlag := True;
  437. rslt.Add(indent + s + '[''' + node['name'] + '''] := ' + node['value'] + ';')
  438. end
  439. else if s = 'createXPathEvaluator' then
  440. rslt.Add(indent + node['var'] + ' := CreateXPathEvaluator(' + node['document'] + ');')
  441. else if s = 'comment' then
  442. rslt.Add(indent + '{ Source comment: ' + node.TextContent + ' }')
  443. else
  444. begin
  445. if not FailFlag then
  446. rslt.Add(indent + 'Fail(''This test is not completely converted'');');
  447. FailFlag := True;
  448. DumpUnprocessed(node, rslt);
  449. end;
  450. end;
  451. procedure ConvertException(el: TDOMElement; const ExceptClass: string; indent: string);
  452. var
  453. excode: string;
  454. begin
  455. if not SuccessVarFlag then
  456. rslt.Insert(2, ' success: Boolean;');
  457. SuccessVarFlag := True;
  458. rslt.Add(indent+'success := False;');
  459. rslt.Add(indent+'try');
  460. child := el.FirstChild;
  461. while assigned(child) do
  462. begin
  463. if child.nodeType = ELEMENT_NODE then
  464. begin
  465. excode := child.nodeName;
  466. subchild := child.FirstChild;
  467. while Assigned(subchild) do
  468. begin
  469. if subchild.nodeType = ELEMENT_NODE then
  470. ConvertStatement(TDOMElement(subchild), indent + ' ');
  471. subchild := subchild.NextSibling;
  472. end;
  473. end;
  474. child := child.NextSibling;
  475. end;
  476. rslt.Add(indent+'except');
  477. rslt.Add(indent+' on E: Exception do');
  478. rslt.Add(indent+' success := (E is ' + ExceptClass +') and (' + ExceptClass + '(E).Code = ' + excode + ');');
  479. rslt.Add(indent+'end;');
  480. rslt.Add(indent+'AssertTrue('''+el['id']+''', success);');
  481. end;
  482. procedure ConvertBlock(el: TDOMNode; indent: string);
  483. var
  484. curr: TDOMNode;
  485. element: TDOMElement;
  486. List: TList;
  487. cond: string;
  488. Frag: TDOMDocumentFragment;
  489. I: Integer;
  490. ElseNode: TDOMNode;
  491. IsColl: Boolean;
  492. begin
  493. List := TList.Create;
  494. curr := el.FirstChild;
  495. indent := indent + ' ';
  496. while Assigned(curr) do
  497. begin
  498. if (curr.NodeType <> ELEMENT_NODE) or
  499. (curr.NodeName = 'var') or (curr.NodeName = 'metadata') then
  500. begin
  501. curr := curr.NextSibling;
  502. Continue;
  503. end;
  504. element := TDOMElement(curr);
  505. n := element.TagName;
  506. if n = 'assertDOMException' then
  507. ConvertException(element, 'EDOMError', indent)
  508. else if n = 'assertXPathException' then
  509. ConvertException(element, 'EXPathException', indent)
  510. else if n = 'try' then
  511. begin
  512. GetChildElements(curr, List);
  513. rslt.Add(indent+'try');
  514. I := 0;
  515. while I < List.Count do
  516. begin
  517. Child := TDOMNode(List[I]);
  518. if Child.NodeName = 'catch' then
  519. break;
  520. ConvertStatement(TDOMElement(child), indent + ' ');
  521. Inc(I);
  522. end;
  523. if (child.NodeName <> 'catch') or (Pointer(Child) <> List.Last) then
  524. rslt.Add('{ ERROR: misplaced "catch" tag }');
  525. GetChildElements(child, List);
  526. cond := '';
  527. for I := 0 to List.Count-1 do
  528. begin
  529. if TDOMElement(List[I]).TagName <> 'DOMException' then
  530. begin
  531. rslt.Add('{ ERROR: unhandled: ' + TDOMElement(List[I]).TagName +' }');
  532. Break;
  533. end;
  534. if cond <> '' then cond := cond + ', ';
  535. cond := cond + TDOMElement(List[I])['code'];
  536. end;
  537. rslt.Add(indent+'except');
  538. rslt.Add(indent+' on E: EDOMError do');
  539. rslt.Add(indent+' if not (E.code in ['+cond+']) then raise;');
  540. rslt.Add(indent+'end;');
  541. end
  542. else if n = 'if' then
  543. begin
  544. ElseNode := nil;
  545. GetChildElements(curr, List);
  546. if (List.Count > 1) and CondToStr(TDOMElement(List[0]), cond) then
  547. begin
  548. rslt.Add(indent+ 'if '+cond+' then');
  549. frag := curr.OwnerDocument.CreateDocumentFragment;
  550. try
  551. // first node is the condition; skip it
  552. for I := 1 to List.Count-1 do
  553. begin
  554. child := TDOMNode(List[I]);
  555. if child.NodeName = 'else' then
  556. begin
  557. ElseNode := child;
  558. Break;
  559. end;
  560. frag.AppendChild(child.CloneNode(True));
  561. end;
  562. rslt.add(indent+'begin');
  563. ConvertBlock(frag, indent);
  564. if Assigned(ElseNode) then
  565. begin
  566. rslt.add(indent+'end');
  567. rslt.Add(indent+'else');
  568. rslt.Add(indent+'begin');
  569. ConvertBlock(ElseNode, indent);
  570. end;
  571. rslt.add(indent+'end;');
  572. finally
  573. frag.Free;
  574. end;
  575. end
  576. else
  577. begin
  578. rslt.Add('{ ERROR: malformed "if" tag }');
  579. dumpunprocessed(element, rslt);
  580. end;
  581. end
  582. else if n = 'for-each' then
  583. begin
  584. // having loop var name globally unique isn't a must.
  585. cond := 'loop'+IntToStr(cntr);
  586. Inc(cntr);
  587. rslt.Insert(rslt.IndexOf('var')+1, ' ' + cond + ': Integer;');
  588. IsColl := IsCollection(element);
  589. if IsColl then
  590. rslt.Add(indent+'for '+cond+' := 0 to ' + 'High(' + element['collection'] + ') do')
  591. else
  592. rslt.Add(indent+'for '+cond+' := 0 to ' + element['collection'] + '.Length-1 do');
  593. rslt.Add(indent+'begin');
  594. if IsColl then
  595. rslt.Add(indent+' ' + element['member'] + ' := '+element['collection']+'['+cond+'];')
  596. else
  597. rslt.Add(indent+' ' + 'TDOMNode('+element['member'] + ') := '+element['collection']+'['+cond+'];');
  598. ConvertBlock(element, indent);
  599. rslt.Add(indent+'end;');
  600. end
  601. else if n = 'while' then
  602. begin
  603. GetChildElements(curr, List);
  604. if (List.Count > 1) and CondToStr(TDOMElement(List[0]), cond) then
  605. begin
  606. rslt.Add(indent+ 'while '+cond+' do');
  607. frag := curr.OwnerDocument.CreateDocumentFragment;
  608. try
  609. for I := 1 to List.Count-1 do // skip first node which is the condition
  610. begin
  611. child := TDOMNode(List[I]);
  612. frag.AppendChild(child.CloneNode(True));
  613. end;
  614. rslt.add(indent+'begin');
  615. ConvertBlock(frag, indent);
  616. rslt.add(indent+'end;');
  617. finally
  618. frag.Free;
  619. end;
  620. end
  621. else
  622. begin
  623. rslt.Add('{ ERROR: malformed "while" tag }');
  624. DumpUnprocessed(element, rslt);
  625. end;
  626. end
  627. else
  628. ConvertStatement(element, indent);
  629. curr := curr.NextSibling;
  630. end;
  631. List.Free;
  632. end;
  633. procedure ConvertVars;
  634. var
  635. TypedConsts: TStrings;
  636. I, J: Integer;
  637. vars, subvars: TDOMNodeList;
  638. node: TDOMElement;
  639. hs: string;
  640. begin
  641. TypedConsts := TStringList.Create;
  642. vars := rootNode.GetElementsByTagName('var');
  643. if vars.Count > 0 then
  644. begin
  645. rslt.Add('var');
  646. for I := 0 to vars.Count-1 do
  647. begin
  648. node := TDOMElement(vars[I]);
  649. FixKeywords(node, 'name');
  650. if node.hasAttribute('isNull') or node.hasAttribute('value') then
  651. begin
  652. // TODO: isNull is identified by 'yes' value, not by mere attr presence?
  653. // TODO: consider putting isNull things to constants
  654. if node.hasAttribute('value') then
  655. hs := ReplaceQuotes(Node['value'])
  656. else
  657. begin
  658. if node['type'] = 'DOMString' then
  659. hs := ''''''
  660. else
  661. hs := 'nil';
  662. end;
  663. Inits.Add(' ' + node['name'] + ' := ' + hs + ';');
  664. end;
  665. if Node.HasChildNodes then
  666. begin
  667. subvars := Node.GetElementsByTagName('member');
  668. try
  669. if subvars.Count > 0 then
  670. begin
  671. if TDOMElement(subvars[0]).HasAttribute('type') then
  672. hs := PascalType(TDOMElement(subvars[0]).GetAttribute('type'))
  673. else
  674. hs := 'DOMString';
  675. TypedConsts.Add(' ' + Node['name'] + ': array[0..' + IntToStr(subvars.Count-1) + '] of ' + hs + ' = (');
  676. for J := 0 to subvars.Count-1 do
  677. begin
  678. hs := ' ' + ReplaceQuotes(subvars[J].TextContent);
  679. if J = subvars.Count-1 then
  680. TypedConsts.Add(hs + ');')
  681. else
  682. TypedConsts.Add(hs + ',');
  683. end;
  684. end
  685. else
  686. DumpUnprocessed(Node, rslt);
  687. finally
  688. subvars.Free;
  689. end;
  690. end
  691. else
  692. rslt.Add(' ' + Node['name'] +': '+ PascalType(Node['type'])+';');
  693. VarTypes.Add(Node['name'] + '=' + PascalType(Node['type']));
  694. end;
  695. if TypedConsts.Count > 0 then
  696. begin
  697. rslt.add('const');
  698. rslt.AddStrings(TypedConsts);
  699. end;
  700. end;
  701. vars.Free;
  702. TypedConsts.Free;
  703. end;
  704. // ConvertTest() itself
  705. begin
  706. SuccessVarFlag := False;
  707. FailFlag := False;
  708. IgnoreFlag := False;
  709. VarTypes := TStringList.Create;
  710. Inits := TStringList.Create;
  711. ConvertVars;
  712. rslt.add('begin');
  713. rslt.AddStrings(Inits);
  714. Inits.Free;
  715. ConvertBlock(rootNode, '');
  716. VarTypes.Free;
  717. rslt.add('end;');
  718. rslt.Add('');
  719. if FailFlag then
  720. begin
  721. if not forced then
  722. rslt.Clear;
  723. Inc(FailCount);
  724. end;
  725. if IgnoreFlag then
  726. begin
  727. rslt.Clear;
  728. Inc(IgnoreCount);
  729. end;
  730. end;
  731. // Intercepting validation errors while loading API
  732. type
  733. TErrHandler = class(TObject)
  734. public
  735. procedure HandleError(E: EXMLReadError);
  736. end;
  737. procedure TErrHandler.HandleError(E: EXMLReadError);
  738. begin
  739. raise E;
  740. end;
  741. function IsBlacklisted(const s: string; const list: array of string): Boolean;
  742. var
  743. I: Integer;
  744. begin
  745. Result := True;
  746. for I := Low(list) to High(list) do
  747. begin
  748. if s = list[I] then
  749. Exit;
  750. end;
  751. Result := False;
  752. end;
  753. const
  754. UnitHeader =
  755. '{ AUTOGENERATED FILE - DO NOT EDIT'#10+
  756. ' This Pascal source file was generated by testgen program'#10 +
  757. ' and is a derived work from the source document.'#10 +
  758. ' The source document contained the following notice:'#10+
  759. '%0:s}'#10+
  760. 'unit %1:s;'#10 +
  761. '{$mode objfpc}{$h+}'#10 +
  762. '{$notes off}'#10 +
  763. '{$codepage utf8}'#10 +
  764. 'interface'#10 +
  765. #10 +
  766. 'uses'#10 +
  767. ' SysUtils, Classes, DOM, xmlread, fpcunit, contnrs, domunit, testregistry%3:s;'#10 +
  768. #10 +
  769. 'type'#10 +
  770. ' %2:s = class(TDOMTestBase)'#10 +
  771. ' protected'#10 +
  772. ' function GetTestFilesURI: string; override;'#10 +
  773. ' published'#10;
  774. procedure ConvertSuite(const BaseURI: DOMString; const UnitFileName: string);
  775. var
  776. suite, testdoc: TXMLDocument;
  777. testlist: TDOMNodeList;
  778. root: TDOMElement;
  779. href, testuri: DOMString;
  780. I: Integer;
  781. sl, all, impl: TStringList;
  782. Pars: TDOMParser;
  783. eh: TErrHandler;
  784. class_name, unit_name, notice, casename, add_units: string;
  785. comment: TDOMNode;
  786. blacklist: array of string;
  787. begin
  788. Pars := TDOMParser.Create;
  789. eh := TErrHandler.Create;
  790. Pars.Options.Validate := True;
  791. Pars.OnError := @eh.HandleError;
  792. // API database must be loaded in validating mode
  793. Pars.ParseURI('file:api.xml', api);
  794. // Prepare the array of blacklisted test names
  795. testlist := api.GetElementsByTagName('blacklist');
  796. try
  797. SetLength(blacklist, testlist.length);
  798. for I := 0 to testlist.length-1 do
  799. blacklist[I] := testlist[I].TextContent;
  800. finally
  801. testlist.Free;
  802. end;
  803. sl := TStringList.Create;
  804. all := TStringList.Create;
  805. impl := TStringList.Create;
  806. Pars.OnError := nil;
  807. Pars.Options.ExpandEntities := True;
  808. Pars.ParseURI(BaseURI + 'alltests.xml', suite);
  809. // extract the copyright notice
  810. notice := '';
  811. comment := suite.FirstChild;
  812. while Assigned(comment) do
  813. begin
  814. if (comment.nodeType = COMMENT_NODE) and
  815. (Pos(DOMString('Copyright'), comment.nodeValue) > 0) then
  816. begin
  817. notice := comment.nodeValue;
  818. Break;
  819. end;
  820. comment := comment.nextSibling;
  821. end;
  822. // Check if we need the additional units to use
  823. add_units := '';
  824. testlist := api.GetElementsByTagName('uses');
  825. try
  826. for I := 0 to testlist.Length-1 do
  827. begin
  828. root := TDOMElement(testlist[I]);
  829. if Pos(root['pattern'], BaseURI) <> 0 then
  830. add_units := add_units + ', ' + root['unit'];
  831. end;
  832. finally
  833. testlist.Free;
  834. end;
  835. unit_name := ChangeFileExt(ExtractFileName(UnitFileName), '');
  836. class_name := 'TTest' + UpperCase(unit_name[1]) + copy(unit_name, 2, MaxInt);
  837. // provide unit header
  838. all.Text := Format(UnitHeader, [notice, unit_name, class_name, add_units]);
  839. // emit the 'GetPathToModuleFiles' function body
  840. impl.Add('implementation');
  841. impl.Add('');
  842. impl.Add('function '+class_name+'.GetTestFilesURI: string;');
  843. impl.Add('begin');
  844. impl.Add(' result := ''' + BaseURI + ''';');
  845. impl.Add('end;');
  846. impl.Add('');
  847. testlist := suite.GetElementsByTagName('suite.member');
  848. testcount := testlist.Count;
  849. writeln;
  850. writeln(testcount, ' test cases found');
  851. for I := 0 to testcount-1 do
  852. begin
  853. href := TDOMElement(testlist[I])['href'];
  854. ResolveRelativeURI(BaseURI, href, testuri);
  855. Pars.ParseURI(testuri, testdoc);
  856. try
  857. sl.Clear;
  858. root := testdoc.DocumentElement;
  859. // fix clash with local vars having the same name
  860. casename := root['name'];
  861. if casename = 'attrname' then
  862. casename := 'attr_name';
  863. if IsBlacklisted(casename, blacklist) then
  864. begin
  865. writeln('Test case "', casename, '" is blacklisted, skipping');
  866. Continue;
  867. end;
  868. sl.Add('procedure ' + class_name + '.' + casename + ';');
  869. try
  870. ConvertTest(root, sl);
  871. except
  872. Writeln('An exception occured while converting ', casename);
  873. raise;
  874. end;
  875. if sl.Count > 0 then
  876. begin
  877. all.add(' procedure '+casename+';');
  878. impl.AddStrings(sl)
  879. end;
  880. finally
  881. testdoc.Free;
  882. end;
  883. end;
  884. testlist.Free;
  885. suite.Free;
  886. // terminate class declaration
  887. all.Add(' end;');
  888. all.Add('');
  889. // append all procedure bodies
  890. all.AddStrings(impl);
  891. all.Add('initialization');
  892. all.Add(' RegisterTest('+class_name+');');
  893. all.Add('end.');
  894. all.SaveToFile(UnitFileName);
  895. impl.Free;
  896. all.Free;
  897. sl.Free;
  898. eh.Free;
  899. Pars.Free;
  900. end;
  901. var
  902. SuiteName: string;
  903. OutputUnit: string;
  904. s: string;
  905. I: Integer;
  906. begin
  907. writeln('testgen - w3.org DOM test suite to Object Pascal converter');
  908. writeln('Copyright (c) 2008 by Sergei Gorelkin');
  909. if ParamCount < 2 then
  910. begin
  911. writeln;
  912. writeln('Usage: ', ParamStr(0), ' <suite dir> <outputunit.pp> [-f]');
  913. writeln(' -f: force conversion of tests which contain unknown tags');
  914. Exit;
  915. end;
  916. SuiteName := ExpandFilename(ParamStr(1));
  917. OutputUnit := ExpandFilename(ParamStr(2));
  918. i := 3;
  919. while i <= ParamCount do
  920. begin
  921. s := Lowercase(ParamStr(i));
  922. if s = '-f' then
  923. forced := True;
  924. Inc(i);
  925. end;
  926. // strip filename if present, we're going to read all dir
  927. if not DirectoryExists(SuiteName) then
  928. SuiteName := ExtractFilePath(SuiteName)
  929. else
  930. SuiteName := IncludeTrailingPathDelimiter(SuiteName);
  931. ConvertSuite(FilenameToURI(SuiteName), OutputUnit);
  932. writeln(testcount - FailCount - IgnoreCount, ' tests converted successfully');
  933. if FailCount > 0 then
  934. begin
  935. writeln(FailCount, ' tests contain tags that are not supported yet');
  936. if forced then
  937. begin
  938. writeln('Conversion of these tests was forced,');
  939. writeln('the resulting file may not compile!');
  940. end
  941. else
  942. writeln('These tests were skipped');
  943. end;
  944. if IgnoreCount > 0 then
  945. begin
  946. writeln(IgnoreCount, ' tests were skipped because they are not');
  947. writeln(' applicable to our DOM implementation.');
  948. end;
  949. end.