ISHelpGen.dpr 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  1. program ISHelpGen;
  2. {$APPTYPE CONSOLE}
  3. uses
  4. Windows,
  5. SysUtils,
  6. Classes,
  7. ActiveX,
  8. ComObj,
  9. TypInfo,
  10. XMLParse in 'XMLParse.pas',
  11. UIsxclassesParser in 'UIsxclassesParser.pas';
  12. const
  13. Version = '1.13';
  14. XMLFileVersion = '1';
  15. SNewLine = #13#10;
  16. type
  17. TElement = (
  18. el_Text,
  19. elA,
  20. elAnchorLink,
  21. elB,
  22. elBody,
  23. elBR,
  24. elContents,
  25. elContentsHeading,
  26. elContentsTopic,
  27. elDD,
  28. elDL,
  29. elDT,
  30. elExample,
  31. elExamples,
  32. elExtLink,
  33. elFlag,
  34. elFlagList,
  35. elHeading,
  36. elI,
  37. elIndent,
  38. elKeyword,
  39. elLI,
  40. elLink,
  41. elOL,
  42. elP,
  43. elParam,
  44. elParamList,
  45. elPre,
  46. elPreCode,
  47. elSetupDefault,
  48. elSetupFormat,
  49. elSetupValid,
  50. elSetupTopic,
  51. elSmall,
  52. elTable,
  53. elTD,
  54. elTopic,
  55. elTR,
  56. elTT,
  57. elU,
  58. elUL);
  59. TElementSet = set of TElement;
  60. TKeywordInfo = class
  61. public
  62. Topic, Anchor: String;
  63. end;
  64. var
  65. SourceDir, OutputDir: String;
  66. ISPP: Boolean;
  67. Keywords, DefinedTopics, TargetTopics, SetupDirectives: TStringList;
  68. TopicsGenerated: Integer = 0;
  69. CurrentTopicName: String;
  70. CurrentListIsCompact: Boolean;
  71. CurrentTableColumnIndex: Integer;
  72. const
  73. { When IE 6 is rendering a frame in Standards mode (due to a !DOCTYPE tag)
  74. and a vertical scroll bar must be displayed, the scroll bar obscures the
  75. right edge of the content. This hack works around that by forcing the
  76. vertical scroll bar to always be shown. It is applied only to IE 6, since
  77. neither IE 5.x nor IE 7 exhibit this behavior.
  78. See http://groups.google.com/group/macromedia.dreamweaver/browse_thread/thread/fb755be2e0ee9267 }
  79. IE6FramesHack =
  80. '<!--[if IE 6]><style type="text/css">html{overflow-y:scroll}</style><![endif]-->';
  81. procedure UnexpectedElementError(const Node: IXMLNode);
  82. begin
  83. raise Exception.CreateFmt('Element "%s" is unexpected here', [Node.NodeName]);
  84. end;
  85. function ElementFromNode(const Node: IXMLNode): TElement;
  86. var
  87. I: Integer;
  88. begin
  89. case Node.NodeType of
  90. NODE_ELEMENT:
  91. begin
  92. I := GetEnumValue(TypeInfo(TElement), 'el' + Node.NodeName);
  93. if I < 0 then
  94. raise Exception.CreateFmt('Unknown element "%s"', [Node.NodeName]);
  95. Result := TElement(I);
  96. end;
  97. NODE_TEXT, NODE_ENTITY_REFERENCE: Result := el_Text;
  98. else
  99. raise Exception.CreateFmt('ElementFromNode: Unknown node type %d', [Node.NodeType]);
  100. end;
  101. end;
  102. function IsWhitespace(const Node: IXMLNode): Boolean;
  103. { Returns True if the node is text that consists only of whitespace }
  104. var
  105. S: String;
  106. I: Integer;
  107. begin
  108. Result := False;
  109. if Node.NodeType = NODE_TEXT then begin
  110. S := Node.Text;
  111. for I := 1 to Length(S) do
  112. if not CharInSet(S[I], [#9, #10, ' ']) then
  113. Exit;
  114. Result := True;
  115. end;
  116. end;
  117. function IsFirstNonWhitespaceNode(Node: IXMLNode): Boolean;
  118. { Returns True if there are no preceding non-whitespace sibling elements }
  119. begin
  120. repeat
  121. Node := Node.PreviousSibling;
  122. until (Node = nil) or not IsWhitespace(Node);
  123. Result := (Node = nil);
  124. end;
  125. function IsLastNonWhitespaceNode(Node: IXMLNode): Boolean;
  126. { Returns True if no non-whitespace sibling elements follow }
  127. begin
  128. repeat
  129. Node := Node.NextSibling;
  130. until (Node = nil) or not IsWhitespace(Node);
  131. Result := (Node = nil);
  132. end;
  133. function NodeHasChildren(Node: IXMLNode): Boolean;
  134. { Returns True if the node has non-whitespace children }
  135. begin
  136. Node := Node.GetFirstChild;
  137. while Assigned(Node) do begin
  138. if not IsWhitespace(Node) then begin
  139. Result := True;
  140. Exit;
  141. end;
  142. Node := Node.NextSibling;
  143. end;
  144. Result := False;
  145. end;
  146. function ListItemExists(const SL: TStrings; const S: String): Boolean;
  147. var
  148. I: Integer;
  149. begin
  150. for I := 0 to SL.Count-1 do
  151. if SL[I] = S then begin
  152. Result := True;
  153. Exit;
  154. end;
  155. Result := False;
  156. end;
  157. function StringChange(var S: String; const FromStr, ToStr: String): Integer;
  158. var
  159. FromStrLen, I, EndPos, J: Integer;
  160. IsMatch: Boolean;
  161. label 1;
  162. begin
  163. Result := 0;
  164. if FromStr = '' then Exit;
  165. FromStrLen := Length(FromStr);
  166. I := 1;
  167. 1:EndPos := Length(S) - FromStrLen + 1;
  168. while I <= EndPos do begin
  169. IsMatch := True;
  170. J := 0;
  171. while J < FromStrLen do begin
  172. if S[J+I] <> FromStr[J+1] then begin
  173. IsMatch := False;
  174. Break;
  175. end;
  176. Inc(J);
  177. end;
  178. if IsMatch then begin
  179. Inc(Result);
  180. Delete(S, I, FromStrLen);
  181. Insert(ToStr, S, I);
  182. Inc(I, Length(ToStr));
  183. goto 1;
  184. end;
  185. Inc(I);
  186. end;
  187. end;
  188. procedure SaveStringToFile(const S, Filename: String);
  189. var
  190. F: TFileStream;
  191. U: UTF8String;
  192. begin
  193. F := TFileStream.Create(Filename, fmCreate);
  194. try
  195. U := UTF8String(S);
  196. F.WriteBuffer(U[1], Length(U));
  197. finally
  198. F.Free;
  199. end;
  200. end;
  201. function EscapeHTML(const S: String; const EscapeDoubleQuotes: Boolean = True): String;
  202. begin
  203. Result := S;
  204. StringChange(Result, '&', '&amp;');
  205. StringChange(Result, '<', '&lt;');
  206. StringChange(Result, '>', '&gt;');
  207. if EscapeDoubleQuotes then
  208. StringChange(Result, '"', '&quot;');
  209. { Also convert the Unicode representation of a non-breaking space into &nbsp;
  210. so it's easily to tell them apart from normal spaces when viewing the
  211. generated HTML source }
  212. StringChange(Result, #$00A0, '&nbsp;');
  213. end;
  214. procedure CheckTopicNameValidity(const TopicName: String);
  215. var
  216. I: Integer;
  217. begin
  218. if TopicName = '' then
  219. raise Exception.Create('Topic name cannot be empty');
  220. { Security: Make sure topic names don't include slashes etc. }
  221. for I := 1 to Length(TopicName) do
  222. if not CharInSet(TopicName[I], ['A'..'Z', 'a'..'z', '0'..'9', '_', '-']) then
  223. raise Exception.CreateFmt('Topic name "%s" includes invalid characters', [TopicName]);
  224. end;
  225. procedure CheckAnchorNameValidity(const AnchorName: String);
  226. var
  227. I: Integer;
  228. begin
  229. if AnchorName = '' then
  230. raise Exception.Create('Anchor name cannot be empty');
  231. for I := 1 to Length(AnchorName) do
  232. if not CharInSet(AnchorName[I], ['A'..'Z', 'a'..'z', '0'..'9', '_', '-', '.']) then
  233. raise Exception.CreateFmt('Anchor name "%s" includes invalid characters', [AnchorName]);
  234. end;
  235. function GenerateTopicFilename(const TopicName: String): String;
  236. begin
  237. CheckTopicNameValidity(TopicName);
  238. Result := 'topic_' + Lowercase(TopicName) + '.htm';
  239. end;
  240. function GenerateTopicLink(const TopicName, AnchorName: String): String;
  241. begin
  242. if TopicName <> '' then
  243. Result := GenerateTopicFileName(TopicName)
  244. else begin
  245. Result := '';
  246. if AnchorName = '' then
  247. raise Exception.Create('Cannot create link with neither a target topic nor anchor');
  248. end;
  249. if AnchorName <> '' then begin
  250. CheckAnchorNameValidity(AnchorName);
  251. Result := Result + '#' + AnchorName;
  252. end;
  253. end;
  254. function GenerateAnchorHTML(const AnchorName, InnerContents: String): String;
  255. { Generates HTML for an anchor on the current topic, also updating
  256. DefinedTopics and checking for duplicates }
  257. var
  258. S: String;
  259. begin
  260. if CurrentTopicName = '' then
  261. raise Exception.Create('Cannot create anchor outside of topic');
  262. CheckAnchorNameValidity(AnchorName);
  263. S := CurrentTopicName + '#' + AnchorName;
  264. if ListItemExists(DefinedTopics, S) then
  265. raise Exception.CreateFmt('Anchor name "%s" in topic "%s" defined more than once',
  266. [AnchorName, CurrentTopicName]);
  267. DefinedTopics.Add(S);
  268. Result := Format('<a name="%s">%s</a>', [EscapeHTML(AnchorName), InnerContents]);
  269. end;
  270. function GenerateTopicLinkHTML(const TopicName, AnchorName, InnerContents: String): String;
  271. { Generates HTML for a link to a topic and/or anchor, also updating
  272. TargetTopics }
  273. var
  274. S: String;
  275. begin
  276. if TopicName <> '' then
  277. S := TopicName
  278. else begin
  279. S := CurrentTopicName;
  280. if S = '' then
  281. raise Exception.Create('Cannot create link outside of topic with empty target topic');
  282. if AnchorName = '' then
  283. raise Exception.Create('Cannot create link with neither a target topic nor anchor');
  284. end;
  285. CheckTopicNameValidity(S);
  286. if AnchorName <> '' then begin
  287. CheckAnchorNameValidity(AnchorName);
  288. S := S + '#' + AnchorName;
  289. end;
  290. if not ListItemExists(TargetTopics, S) then
  291. TargetTopics.Add(S);
  292. Result := Format('<a href="%s">%s</a>',
  293. [EscapeHTML(GenerateTopicLink(TopicName, AnchorName)), InnerContents]);
  294. end;
  295. procedure CreateKeyword(const AKeyword, ATopicName, AAnchorName: String);
  296. var
  297. KeywordInfo: TKeywordInfo;
  298. begin
  299. KeywordInfo := TKeywordInfo.Create;
  300. KeywordInfo.Topic := ATopicName;
  301. KeywordInfo.Anchor := AAnchorName;
  302. Keywords.AddObject(AKeyword, KeywordInfo);
  303. end;
  304. function ParseFormattedText(Node: IXMLNode): String;
  305. var
  306. S: String;
  307. I: Integer;
  308. B: Boolean;
  309. begin
  310. Result := '';
  311. Node := Node.FirstChild;
  312. while Assigned(Node) do begin
  313. case ElementFromNode(Node) of
  314. el_Text:
  315. Result := Result + EscapeHTML(Node.Text, False);
  316. elA:
  317. begin
  318. S := Node.Attributes['name'];
  319. Result := Result + GenerateAnchorHTML(S, ParseFormattedText(Node));
  320. end;
  321. elAnchorLink:
  322. begin
  323. S := Node.Attributes['name'];
  324. Result := Result + GenerateTopicLinkHTML('', S, ParseFormattedText(Node));
  325. end;
  326. elB:
  327. Result := Result + '<b>' + ParseFormattedText(Node) + '</b>';
  328. elBR:
  329. Result := Result + '<br/>';
  330. elDD:
  331. Result := Result + '<dd>' + ParseFormattedText(Node) + '</dd>';
  332. elDL:
  333. Result := Result + '<dl>' + ParseFormattedText(Node) + '</dl>';
  334. elDT:
  335. Result := Result + '<dt>' + ParseFormattedText(Node) + '</dt>';
  336. elExample:
  337. Result := Result + '<div class="examplebox">' + SNewLine +
  338. '<div class="exampleheader">Example:</div>' + ParseFormattedText(Node) + '</div>';
  339. elExamples:
  340. Result := Result + '<div class="examplebox">' + SNewLine +
  341. '<div class="exampleheader">Examples:</div>' + ParseFormattedText(Node) + '</div>';
  342. elFlag:
  343. begin
  344. S := Node.Attributes['name'];
  345. if CurrentTopicName = '' then
  346. raise Exception.Create('<flag> used outside of topic');
  347. CreateKeyword(S, CurrentTopicName, S);
  348. Result := Result + '<dt class="flaglist">' + GenerateAnchorHTML(S, EscapeHTML(S)) +
  349. '</dt>' + SNewLine + '<dd>' + ParseFormattedText(Node) +
  350. '</dd>';
  351. end;
  352. elFlagList:
  353. Result := Result + '<dl>' + ParseFormattedText(Node) + '</dl>';
  354. elI:
  355. Result := Result + '<i>' + ParseFormattedText(Node) + '</i>';
  356. elIndent:
  357. Result := Result + '<div class="indent">' + ParseFormattedText(Node) + '</div>';
  358. elLI:
  359. begin
  360. Result := Result + '<li';
  361. if CurrentListIsCompact then
  362. Result := Result + ' class="compact"';
  363. Result := Result + '>' + ParseFormattedText(Node) + '</li>';
  364. end;
  365. elLink:
  366. begin
  367. S := Node.Attributes['topic'];
  368. Result := Result + GenerateTopicLinkHTML(S, Node.OptionalAttributes['anchor'],
  369. ParseFormattedText(Node));
  370. end;
  371. elExtLink:
  372. begin
  373. S := EscapeHTML(Node.Attributes['href']);
  374. if Pos('ms-its:', S) = 1 then
  375. Result := Result + Format('<a href="%s">%s</a>', [S, ParseFormattedText(Node)])
  376. else
  377. Result := Result + Format('<a href="%s" target="_blank" title="%s">%s</a><img src="images/extlink.png" alt=" [external link]" />',
  378. [S, S, ParseFormattedText(Node)]);
  379. end;
  380. elHeading:
  381. begin
  382. if IsFirstNonWhitespaceNode(Node) then
  383. Result := Result + '<h2 class="heading notopmargin">'
  384. else
  385. Result := Result + '<h2 class="heading">';
  386. Result := Result + ParseFormattedText(Node) + '</h2>';
  387. end;
  388. elOL:
  389. Result := Result + '<ol>' + ParseFormattedText(Node) + '</ol>';
  390. elP:
  391. begin
  392. if Node.HasAttribute('margin') and (Node.Attributes['margin'] = 'no') then
  393. Result := Result + '<div>' + ParseFormattedText(Node) + '</div>'
  394. else
  395. Result := Result + '<p>' + ParseFormattedText(Node) + '</p>';
  396. end;
  397. elParam:
  398. begin
  399. { IE doesn't support immediate-child-only selectors in CSS (e.g.
  400. "DL.paramlist > DT") so we have to apply the class to each DT
  401. instead of just on the DL. }
  402. S := Node.Attributes['name'];
  403. if CurrentTopicName = '' then
  404. raise Exception.Create('<param> used outside of topic');
  405. CreateKeyword(S, CurrentTopicName, S);
  406. Result := Result + '<dt class="paramlist"><b>' + GenerateAnchorHTML(S, EscapeHTML(S)) + '</b>';
  407. if Node.Attributes['required'] = 'yes' then
  408. Result := Result + ' &nbsp;<i>(Required)</i>';
  409. Result := Result + '</dt><dd class="paramlist">' + ParseFormattedText(Node) + '</dd>';
  410. end;
  411. elParamList:
  412. Result := Result + '<dl>' + ParseFormattedText(Node) + '</dl>';
  413. elPre:
  414. begin
  415. Result := Result + '<pre';
  416. { Special handling for <pre> inside example boxes: Don't include a
  417. bottom margin if <pre> is the last element }
  418. if (ElementFromNode(Node.ParentNode) in [elExample, elExamples]) and
  419. IsLastNonWhitespaceNode(Node) then
  420. Result := Result + ' class="nomargin"';
  421. Result := Result + '>' + ParseFormattedText(Node) + '</pre>';
  422. end;
  423. elPreCode:
  424. Result := Result + '<pre class="indent examplebox">' + ParseFormattedText(Node) + '</pre>';
  425. elSmall:
  426. Result := Result + '<span class="small">' + ParseFormattedText(Node) + '</span>';
  427. elTable:
  428. Result := Result + '<table>' + ParseFormattedText(Node) + '</table>';
  429. elTD:
  430. begin
  431. Result := Result + '<td';
  432. if CurrentTableColumnIndex = 0 then
  433. Result := Result + ' class="cellleft"'
  434. else
  435. Result := Result + ' class="cellright"';
  436. Result := Result + '>' + ParseFormattedText(Node) + '</td>';
  437. Inc(CurrentTableColumnIndex);
  438. end;
  439. elTR:
  440. begin
  441. I := CurrentTableColumnIndex;
  442. CurrentTableColumnIndex := 0;
  443. Result := Result + '<tr>' + ParseFormattedText(Node) + '</tr>';
  444. CurrentTableColumnIndex := I;
  445. end;
  446. elTT:
  447. Result := Result + '<tt>' + ParseFormattedText(Node) + '</tt>';
  448. elU:
  449. Result := Result + '<u>' + ParseFormattedText(Node) + '</u>';
  450. elUL:
  451. begin
  452. B := CurrentListIsCompact;
  453. CurrentListIsCompact := (Node.HasAttribute('appearance') and (Node.Attributes['appearance'] = 'compact'));
  454. Result := Result + '<ul>' + ParseFormattedText(Node) + '</ul>';
  455. CurrentListIsCompact := B;
  456. end;
  457. else
  458. UnexpectedElementError(Node);
  459. end;
  460. Node := Node.NextSibling;
  461. end;
  462. end;
  463. function GenerateSetupDirectiveTopicName(const Directive: String): String;
  464. begin
  465. Result := 'setup_' + Lowercase(Directive);
  466. end;
  467. procedure ParseTopic(const TopicNode: IXMLNode; const SetupTopic: Boolean);
  468. var
  469. TopicDirective, TopicName, TopicTitle: String;
  470. BodyText, SetupFormatText, SetupValidText, SetupDefaultText, S: String;
  471. Node: IXMLNode;
  472. begin
  473. if not SetupTopic then begin
  474. TopicName := TopicNode.Attributes['name'];
  475. TopicTitle := TopicNode.Attributes['title'];
  476. end
  477. else begin
  478. TopicDirective := TopicNode.Attributes['directive'];
  479. TopicName := GenerateSetupDirectiveTopicName(TopicDirective);
  480. CreateKeyword(TopicDirective, TopicName, '');
  481. if TopicNode.HasAttribute('title') then
  482. TopicTitle := '[Setup]: ' + TopicNode.Attributes['title']
  483. else
  484. TopicTitle := '[Setup]: ' + TopicDirective;
  485. end;
  486. CheckTopicNameValidity(TopicName);
  487. if ListItemExists(DefinedTopics, TopicName) then
  488. raise Exception.CreateFmt('Topic "%s" defined more than once', [TopicName]);
  489. DefinedTopics.Add(TopicName);
  490. CurrentTopicName := TopicName;
  491. Node := TopicNode.FirstChild;
  492. while Assigned(Node) do begin
  493. if not IsWhitespace(Node) then begin
  494. case ElementFromNode(Node) of
  495. elBody:
  496. BodyText := ParseFormattedText(Node);
  497. elKeyword:
  498. CreateKeyword(Node.Attributes['value'], TopicName, Node.OptionalAttributes['anchor']);
  499. elSetupDefault:
  500. begin
  501. if not SetupTopic then
  502. raise Exception.Create('<setupdefault> is only valid inside <setuptopic>');
  503. { <div class="margined"> is used instead of <p> since the data could
  504. contain <p>'s of its own, which can't be nested.
  505. NOTE: The space before </div> is intentional -- as noted in
  506. styles.css, "vertical-align: baseline" doesn't work right on IE6,
  507. but putting a space before </div> works around the problem, at
  508. least when it comes to lining up normal text with a single line
  509. of monospaced text. }
  510. SetupDefaultText := '<tr><td class="setuphdrl"><p>Default value:</p></td>' +
  511. '<td class="setuphdrr"><div class="margined">' + ParseFormattedText(Node) +
  512. ' </div></td></tr>' + SNewLine;
  513. end;
  514. elSetupFormat:
  515. begin
  516. if not SetupTopic then
  517. raise Exception.Create('<setupformat> is only valid inside <setuptopic>');
  518. { See comments above! }
  519. SetupFormatText := '<tr><td class="setuphdrl"><p>Format:</p></td>' +
  520. '<td class="setuphdrr"><div class="margined">' + ParseFormattedText(Node) +
  521. ' </div></td></tr>' + SNewLine;
  522. end;
  523. elSetupValid:
  524. begin
  525. if not SetupTopic then
  526. raise Exception.Create('<setupvalid> is only valid inside <setuptopic>');
  527. { See comments above! }
  528. SetupValidText := '<tr><td class="setuphdrl"><p>Valid values:</p></td>' +
  529. '<td class="setuphdrr"><div class="margined">' + ParseFormattedText(Node) +
  530. ' </div></td></tr>' + SNewLine;
  531. end;
  532. else
  533. UnexpectedElementError(Node);
  534. end;
  535. end;
  536. Node := Node.NextSibling;
  537. end;
  538. CurrentTopicName := '';
  539. S :=
  540. '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' + SNewLine +
  541. '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">' + SNewLine +
  542. '<head>' + SNewLine +
  543. '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' + SNewLine +
  544. '<meta http-equiv="X-UA-Compatible" content="IE=8" />' + SNewLine +
  545. '<title>' + EscapeHTML(TopicTitle, False) + '</title>' + SNewLine +
  546. IE6FramesHack + SNewLine +
  547. '<link rel="stylesheet" type="text/css" href="styles.css" />' + SNewLine +
  548. '<script type="text/javascript" src="topic.js"></script>' + SNewLine +
  549. '</head>' + SNewLine +
  550. '<body>' + SNewLine +
  551. '<h1 class="topicheading">' + EscapeHTML(TopicTitle, False) + '</h1>' + SNewLine +
  552. '<div class="topicbody">';
  553. if TopicName = 'whatisinnosetup' then begin
  554. S := S + SNewLine + SNewLine +
  555. '<!--[if lt IE 6]>' + SNewLine +
  556. '<p style="background: #ffa0a0; color: black; padding: 6px; border: 1px solid black">' + SNewLine +
  557. 'You are running an old version of Internet Explorer. Consequently, ' +
  558. 'you may encounter problems viewing the documentation. It is ' +
  559. 'recommended that you upgrade to Internet Explorer 6.0 or later.' + SNewLine +
  560. '</p>' + SNewLine +
  561. '<![endif]-->';
  562. end;
  563. if SetupTopic then begin
  564. if (SetupFormatText <> '') or
  565. (SetupValidText <> '') or
  566. (SetupDefaultText <> '') then
  567. S := S + SNewLine + '<table class="setuphdr">' + SNewLine +
  568. SetupFormatText + SetupValidText + SetupDefaultText + '</table>';
  569. S := S + SNewLine + '<div><b>Description:</b></div>';
  570. end;
  571. S := S +
  572. BodyText +
  573. '</div>' + SNewLine +
  574. '</body>' + SNewLine +
  575. '</html>' + SNewLine;
  576. { Normalize the line breaks (MSXML converts CRLF -> LF) }
  577. StringChange(S, #13#10, #10);
  578. StringChange(S, #10, #13#10);
  579. SaveStringToFile(S, OutputDir + GenerateTopicFilename(TopicName));
  580. Inc(TopicsGenerated);
  581. end;
  582. procedure GenerateHTMLHelpContents(const ContentsNode: IXMLNode);
  583. var
  584. SL: TStringList;
  585. procedure AddLeaf(const Title, TopicName: String);
  586. begin
  587. SL.Add(Format('<li><object type="text/sitemap">' +
  588. '<param name="Name" value="%s">' +
  589. '<param name="Local" value="%s"></object>',
  590. [EscapeHTML(Title), EscapeHTML(GenerateTopicLink(TopicName, ''))]));
  591. end;
  592. procedure HandleSetupDirectivesNode;
  593. var
  594. I: Integer;
  595. begin
  596. SL.Add('<ul>');
  597. for I := 0 to SetupDirectives.Count-1 do
  598. AddLeaf(SetupDirectives[I], GenerateSetupDirectiveTopicName(SetupDirectives[I]));
  599. SL.Add('</ul>');
  600. end;
  601. procedure HandleNode(const ParentNode: IXMLNode);
  602. var
  603. Node: IXMLNode;
  604. begin
  605. SL.Add('<ul>');
  606. Node := ParentNode.FirstChild;
  607. while Assigned(Node) do begin
  608. if not IsWhitespace(Node) then begin
  609. case ElementFromNode(Node) of
  610. elContentsHeading:
  611. begin
  612. SL.Add(Format('<li><object type="text/sitemap">' +
  613. '<param name="Name" value="%s"></object>',
  614. [EscapeHTML(Node.Attributes['title'])]));
  615. if Node.Attributes['title'] = '[Setup] section directives' then
  616. HandleSetupDirectivesNode
  617. else
  618. HandleNode(Node);
  619. end;
  620. elContentsTopic:
  621. AddLeaf(Node.Attributes['title'], Node.Attributes['topic']);
  622. else
  623. UnexpectedElementError(Node);
  624. end;
  625. end;
  626. Node := Node.NextSibling;
  627. end;
  628. SL.Add('</ul>');
  629. if not ISPP and (ParentNode = ContentsNode) then begin
  630. { Don't put next 2 lines on 1 line or hhc will hang... }
  631. SL.Add('<object type="text/sitemap">');
  632. SL.Add('<param name="Merge" value="ispp.chm::\hh_generated_contents.hhc"></object>');
  633. end;
  634. end;
  635. begin
  636. SL := TStringList.Create;
  637. try
  638. SL.Add('<html><head></head><body>');
  639. HandleNode(ContentsNode);
  640. SL.Add('</body></html>');
  641. SL.WriteBOM := False;
  642. SL.SaveToFile(OutputDir + 'hh_generated_contents.hhc', TEncoding.UTF8);
  643. finally
  644. SL.Free;
  645. end;
  646. end;
  647. procedure GenerateStaticContents(const ContentsNode: IXMLNode);
  648. var
  649. SL: TStringList;
  650. CurHeadingID: Integer;
  651. procedure AddLeaf(const Title, TopicName: String);
  652. begin
  653. SL.Add(Format('<tr><td><img src="images/contentstopic.png" alt="" /></td>' +
  654. '<td><a href="%s" target="bodyframe">%s</a></td></tr>',
  655. [EscapeHTML(GenerateTopicLink(TopicName, '')), EscapeHTML(Title)]));
  656. end;
  657. procedure HandleSetupDirectivesNode;
  658. var
  659. I: Integer;
  660. begin
  661. SL.Add('<table>');
  662. for I := 0 to SetupDirectives.Count-1 do
  663. AddLeaf(SetupDirectives[I], GenerateSetupDirectiveTopicName(SetupDirectives[I]));
  664. SL.Add('</table>');
  665. end;
  666. procedure HandleNode(const ParentNode: IXMLNode);
  667. var
  668. Node: IXMLNode;
  669. begin
  670. SL.Add('<table>');
  671. Node := ParentNode.FirstChild;
  672. while Assigned(Node) do begin
  673. if not IsWhitespace(Node) then begin
  674. case ElementFromNode(Node) of
  675. elContentsHeading:
  676. begin
  677. Inc(CurHeadingID);
  678. SL.Add(Format('<tr id="nodecaption_%d"><td><img id="nodeimg_%d" src="images/contentsheadopen.png" alt="&gt;&nbsp;" onclick="toggle_node(%d);" /></td>' +
  679. '<td><a href="javascript:toggle_node(%d);">%s</a></td></tr>',
  680. [CurHeadingID, CurHeadingID, CurHeadingID, CurHeadingID, EscapeHTML(Node.Attributes['title'])]));
  681. SL.Add(Format('<tr id="nodecontent_%d"><td></td><td>', [CurHeadingID]));
  682. if Node.Attributes['title'] = '[Setup] section directives' then
  683. HandleSetupDirectivesNode
  684. else
  685. HandleNode(Node);
  686. SL.Add('</td></tr>');
  687. end;
  688. elContentsTopic:
  689. AddLeaf(Node.Attributes['title'], Node.Attributes['topic']);
  690. else
  691. UnexpectedElementError(Node);
  692. end;
  693. end;
  694. Node := Node.NextSibling;
  695. end;
  696. SL.Add('</table>');
  697. end;
  698. var
  699. TemplateSL: TStringList;
  700. S: String;
  701. begin
  702. SL := TStringList.Create;
  703. try
  704. CurHeadingID := 0;
  705. HandleNode(ContentsNode);
  706. TemplateSL := TStringList.Create;
  707. try
  708. TemplateSL.LoadFromFile(OutputDir + 'contents-template.htm');
  709. S := TemplateSL.Text;
  710. if StringChange(S, '%CONTENTSTABLES%' + SNewLine, SL.Text) <> 1 then
  711. raise Exception.Create('GenerateStaticContents: Unexpected result from StringChange');
  712. TemplateSL.Text := S;
  713. TemplateSL.WriteBOM := False;
  714. TemplateSL.SaveToFile(OutputDir + 'contents.htm', TEncoding.UTF8);
  715. finally
  716. TemplateSL.Free;
  717. end;
  718. finally
  719. SL.Free;
  720. end;
  721. end;
  722. procedure GenerateHTMLHelpIndex;
  723. function MultiKeyword(const Keyword: String): Boolean;
  724. var
  725. I, N: Integer;
  726. begin
  727. N := 0;
  728. for I := 0 to Keywords.Count-1 do begin
  729. if Keywords[I] = Keyword then begin
  730. Inc(N);
  731. if N > 1 then
  732. Break;
  733. end;
  734. end;
  735. Result := N > 1;
  736. end;
  737. var
  738. SL: TStringList;
  739. I: Integer;
  740. Anchor: String;
  741. begin
  742. SL := TStringList.Create;
  743. try
  744. SL.Add('<html><head></head><body><ul>');
  745. for I := 0 to Keywords.Count-1 do begin
  746. { If a keyword is used more then once, don't use anchors: the 'Topics Found'
  747. dialog displayed when clicking on such a keyword doesn't display the correct
  748. topic titles anymore for each item with an anchor. Some HTML Help bug, see
  749. http://social.msdn.microsoft.com/Forums/en-US/devdocs/thread/a2ee989e-4488-4edd-b034-745ed91c19e2 }
  750. if not MultiKeyword(Keywords[I]) then
  751. Anchor := TKeywordInfo(Keywords.Objects[I]).Anchor
  752. else
  753. Anchor := '';
  754. SL.Add(Format('<li><object type="text/sitemap">' +
  755. '<param name="Name" value="%s">' +
  756. '<param name="Local" value="%s">' +
  757. '</object>',
  758. [EscapeHTML(Keywords[I]),
  759. EscapeHTML(GenerateTopicLink(TKeywordInfo(Keywords.Objects[I]).Topic,
  760. Anchor))]));
  761. end;
  762. SL.Add('</ul></body></html>');
  763. SL.WriteBOM := False;
  764. SL.SaveToFile(OutputDir + 'hh_generated_index.hhk', TEncoding.UTF8);
  765. finally
  766. SL.Free;
  767. end;
  768. end;
  769. procedure GenerateStaticIndex;
  770. function EscapeForJSStringLiteral(const S: String): String;
  771. begin
  772. Result := S;
  773. StringChange(Result, '\', '\\');
  774. StringChange(Result, '"', '\"');
  775. { Note: Escaping " isn't really necessary here since EscapeHTML will
  776. replace all " with &quot; }
  777. end;
  778. var
  779. S, T: String;
  780. I: Integer;
  781. begin
  782. S := 'var contentsIndexData=[';
  783. for I := 0 to Keywords.Count-1 do begin
  784. T := Lowercase(TKeywordInfo(Keywords.Objects[I]).Topic);
  785. if TKeywordInfo(Keywords.Objects[I]).Anchor <> '' then
  786. T := T + '#' + TKeywordInfo(Keywords.Objects[I]).Anchor;
  787. if Pos(':', T) <> 0 then
  788. raise Exception.CreateFmt('GenerateStaticIndex: Invalid character in topic name/anchor "%s"', [T]);
  789. if I <> 0 then
  790. S := S + ',';
  791. S := S + Format('"%s:%s"', [EscapeForJSStringLiteral(EscapeHTML(T)),
  792. EscapeForJSStringLiteral(EscapeHTML(Keywords[I]))]);
  793. end;
  794. S := S + ('];' + SNewLine + 'init_index_tab_elements();');
  795. SaveStringToFile(S, OutputDir + 'contentsindex.js');
  796. end;
  797. procedure CheckForNonexistentTargetTopics;
  798. var
  799. I: Integer;
  800. begin
  801. for I := 0 to TargetTopics.Count-1 do
  802. if not ListItemExists(DefinedTopics, TargetTopics[I]) then
  803. raise Exception.CreateFmt('Link target topic "%s" does not exist',
  804. [TargetTopics[I]]);
  805. //Writeln(Format('Warning: Link target topic "%s" does not exist',
  806. // [TargetTopics[I]]));
  807. end;
  808. procedure Go;
  809. procedure TransformFile(const FromXml, FromXsl, ToXml: String);
  810. var
  811. Doc, StyleDoc: TXMLDocument;
  812. begin
  813. Writeln('- Generating ' + ToXml);
  814. Doc := TXMLDocument.Create;
  815. try
  816. StyleDoc := TXMLDocument.Create;
  817. try
  818. Writeln(' - Loading ' + FromXml);
  819. Doc.LoadFromFile(SourceDir + FromXml);
  820. Writeln(' - Loading ' + FromXsl);
  821. StyleDoc.LoadFromFile(SourceDir + FromXsl);
  822. Writeln(' - Transforming');
  823. SaveStringToFile(Doc.Root.TransformNode(StyleDoc.Root),
  824. SourceDir + ToXml);
  825. finally
  826. StyleDoc.Free;
  827. end;
  828. finally
  829. Doc.Free;
  830. end;
  831. end;
  832. procedure GenerateIsxClassesFile;
  833. var
  834. IsxclassesParser: TIsxclassesParser;
  835. begin
  836. Writeln('- Generating isxclasses_generated.xml');
  837. IsxclassesParser := TIsxclassesParser.Create;
  838. try
  839. IsxclassesParser.Parse(SourceDir + 'isxclasses.pas');
  840. IsxclassesParser.SaveXML(SourceDir + 'isxclasses.header',
  841. SourceDir + 'isxclasses.header2',
  842. SourceDir + 'isxclasses.footer',
  843. SourceDir + 'isxclasses_generated.xml');
  844. finally
  845. IsxclassesParser.Free;
  846. end;
  847. end;
  848. procedure ReadSetupDirectiveNames(Node: IXMLNode);
  849. begin
  850. while Assigned(Node) do begin
  851. if ElementFromNode(Node) = elSetupTopic then
  852. SetupDirectives.Add(Node.Attributes['directive']);
  853. Node := Node.NextSibling;
  854. end;
  855. end;
  856. procedure DoDoc(Filename: String);
  857. var
  858. Doc: TXMLDocument;
  859. Node: IXMLNode;
  860. begin
  861. Writeln('- Parsing ', Filename);
  862. Doc := TXMLDocument.Create;
  863. try
  864. Doc.LoadFromFile(SourceDir + Filename);
  865. Doc.StripComments;
  866. Node := Doc.Root;
  867. if Node.HasAttribute('version') and (Node.Attributes['version'] <> XMLFileVersion) then
  868. raise Exception.CreateFmt('Unrecognized file version "%s" (expected "%s")',
  869. [Node.Attributes['version'], XMLFileVersion]);
  870. Node := Node.FirstChild;
  871. ReadSetupDirectiveNames(Node);
  872. while Assigned(Node) do begin
  873. if not IsWhitespace(Node) then begin
  874. case ElementFromNode(Node) of
  875. elContents:
  876. begin
  877. Writeln(' - Generating hh_generated_contents.hhc');
  878. GenerateHTMLHelpContents(Node);
  879. Writeln(' - Generating contents.htm');
  880. GenerateStaticContents(Node);
  881. end;
  882. elSetupTopic: ParseTopic(Node, True);
  883. elTopic: ParseTopic(Node, False);
  884. else
  885. UnexpectedElementError(Node);
  886. end;
  887. end;
  888. Node := Node.NextSibling;
  889. end;
  890. finally
  891. Doc.Free;
  892. end;
  893. end;
  894. var
  895. I: Integer;
  896. begin
  897. if not ISPP then begin
  898. TransformFile('isxfunc.xml', 'isxfunc.xsl', 'isxfunc_generated.xml');
  899. GenerateIsxClassesFile;
  900. end else
  901. TransformFile('ispp.xml', 'ispp.xsl', 'ispp_generated.xml');
  902. Keywords := TStringList.Create;
  903. Keywords.Duplicates := dupAccept;
  904. Keywords.Sorted := True;
  905. DefinedTopics := TStringList.Create;
  906. DefinedTopics.Sorted := True;
  907. TargetTopics := TStringList.Create;
  908. TargetTopics.Sorted := True;
  909. SetupDirectives := TStringList.Create;
  910. SetupDirectives.Duplicates := dupError;
  911. SetupDirectives.Sorted := True;
  912. try
  913. if not ISPP then begin
  914. DoDoc('isetup.xml');
  915. DoDoc('isx.xml');
  916. DoDoc('isxfunc_generated.xml');
  917. DoDoc('isxclasses_generated.xml');
  918. end else
  919. DoDoc('ispp_generated.xml');
  920. CheckForNonexistentTargetTopics;
  921. Writeln('- Generating hh_generated_index.hhk');
  922. GenerateHTMLHelpIndex;
  923. Writeln('- Generating contentsindex.js');
  924. GenerateStaticIndex;
  925. finally
  926. SetupDirectives.Free;
  927. TargetTopics.Free;
  928. DefinedTopics.Free;
  929. if Assigned(Keywords) then begin
  930. for I := Keywords.Count-1 downto 0 do
  931. TKeywordInfo(Keywords.Objects[I]).Free;
  932. Keywords.Free;
  933. end;
  934. end;
  935. end;
  936. var
  937. StartTime, EndTime: DWORD;
  938. begin
  939. try
  940. Writeln('ISHelpGen v' + Version + ' by Jordan Russell & Martijn Laan');
  941. if ParamCount <> 1 then begin
  942. Writeln('usage: ISHelpGen [source-dir]');
  943. Halt(2);
  944. end;
  945. SourceDir := ParamStr(1) + '\';
  946. OutputDir := SourceDir + 'Staging\';
  947. ISPP := FileExists(SourceDir + 'ispp.xml');
  948. if ISPP then
  949. Writeln('Running in ISPP mode');
  950. OleCheck(CoInitialize(nil)); { for MSXML }
  951. StartTime := GetTickCount;
  952. Go;
  953. EndTime := GetTickCount;
  954. Writeln('Success - ', TopicsGenerated, ' topics generated (',
  955. EndTime - StartTime, ' ms elapsed)');
  956. except
  957. on E: Exception do begin
  958. Writeln('Error: ', TrimRight(E.Message));
  959. Halt(1);
  960. end;
  961. end;
  962. end.