rstconv.pp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 1999-2000 by Sebastian Guenther
  4. Added .rc and OS/2 MSG support in 2002 by Yuri Prokushev
  5. .rst resource string table file converter.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. {$MODE objfpc}
  13. {$H+}
  14. program rstconv;
  15. uses sysutils, classes, jsonparser, fpjson;
  16. resourcestring
  17. help =
  18. 'rstconv [-h|--help] Displays this help'+LineEnding+
  19. 'rstconv options Convert rst file'+LineEnding+LineEnding+
  20. 'Options are:'+LineEnding+
  21. ' -i file Use specified file instead of stdin as input .rst (OPTIONAL)'+LineEnding+
  22. ' -o file Write output to specified file (REQUIRED)'+LineEnding+
  23. ' -f format Specifies the output format:'+LineEnding+
  24. ' po GNU gettext .po (portable) format (DEFAULT)'+LineEnding+
  25. ' msg IBM OS/2 MSG file format'+LineEnding+
  26. ' rc Resource compiler .rc format'+LineEnding+LineEnding+
  27. '.po format only options are:'+LineEnding+
  28. ' -c char set Adds a header specifying the given character set (OPTIONAL).'+LineEnding+LineEnding+
  29. 'OS/2 MSG file only options are:'+LineEnding+
  30. ' -c identifier Specifies the component identifier (REQUIRED).'+LineEnding+
  31. ' Identifier is any three chars in upper case.'+LineEnding+
  32. ' -n number Specifies the first message number [1-9999] (OPTIONAL).'+LineEnding+LineEnding+
  33. 'Resource compiler script only options are:'+LineEnding+
  34. ' -s Use STRINGTABLE instead of MESSAGETABLE'+LineEnding+
  35. ' -c identifier Use identifier as ID base (ID+n) (OPTIONAL)'+LineEnding+
  36. ' -n number Specifies the first ID number (OPTIONAL)'+LineEnding;
  37. InvalidOption = 'Invalid option - ';
  38. RequiredOption = 'Required option is absent - ';
  39. OptionAlreadySpecified = 'Option has already been specified - ';
  40. NoOutFilename = 'No output filename specified';
  41. InvalidOutputFormat = 'Invalid output format -';
  42. MessageNumberTooBig = 'Message number too big';
  43. InvalidRange = 'Invalid range of the first message number';
  44. type
  45. TConstItem = class(TCollectionItem)
  46. public
  47. ModuleName, ConstName, Value: String;
  48. end;
  49. var
  50. InFilename, OutFilename: String;
  51. ConstItems: TCollection;
  52. CharSet: String;
  53. Identifier: String;
  54. FirstMessage: Word;
  55. MessageTable: Boolean;
  56. procedure ReadRSTFile;
  57. var
  58. f: Text;
  59. s: String;
  60. item: TConstItem;
  61. DotPos, EqPos, i, j: Integer;
  62. begin
  63. Assign(f, InFilename);
  64. Reset(f);
  65. while not eof(f) do begin
  66. ReadLn(f, s);
  67. If (Length(S)=0) or (S[1]='#') then
  68. continue;
  69. item := TConstItem(ConstItems.Add);
  70. DotPos := Pos('.', s);
  71. EqPos := Pos('=', s);
  72. if DotPos > EqPos then // paranoia checking.
  73. DotPos := 0;
  74. item.ModuleName := Copy(s, 1, DotPos - 1);
  75. item.ConstName := Copy(s, DotPos + 1, EqPos - DotPos - 1);
  76. item.Value := '';
  77. i := EqPos + 1;
  78. while i <= Length(s) do begin
  79. if s[i] = '''' then begin
  80. Inc(i);
  81. j := i;
  82. while (i <= Length(s)) and (s[i] <> '''') do
  83. Inc(i);
  84. item.Value := item.Value + Copy(s, j, i - j);
  85. Inc(i);
  86. end else if s[i] = '#' then begin
  87. Inc(i);
  88. j := i;
  89. while (i <= Length(s)) and (s[i] in ['0'..'9']) do
  90. Inc(i);
  91. item.Value := item.Value + Chr(StrToInt(Copy(s, j, i - j)));
  92. end else if s[i] = '+' then begin
  93. ReadLn(f, s);
  94. i := 1;
  95. end else
  96. Inc(i);
  97. end;
  98. end;
  99. Close(f);
  100. end;
  101. procedure ReadRSJFile;
  102. var
  103. Stream: TFileStream;
  104. Parser: TJSONParser;
  105. JsonItems: TJSONArray;
  106. JsonData, JsonItem: TJSONObject;
  107. S: String;
  108. item: TConstItem;
  109. DotPos, I: Integer;
  110. begin
  111. Stream := TFileStream.Create(InFilename, fmOpenRead or fmShareDenyNone);
  112. Parser := TJSONParser.Create(Stream);
  113. try
  114. JsonData := Parser.Parse as TJSONObject;
  115. try
  116. JsonItems := JsonData.Arrays['strings'];
  117. for I := 0 to JsonItems.Count - 1 do
  118. begin
  119. item := TConstItem(ConstItems.Add);
  120. JsonItem := JsonItems.Items[I] as TJSONObject;
  121. S := JsonItem.Get('name');
  122. DotPos := Pos('.', s);
  123. item.ModuleName := Copy(s, 1, DotPos - 1);
  124. item.ConstName := Copy(s, DotPos + 1, Length(S) - DotPos);
  125. item.Value := JsonItem.Get('value');
  126. end;
  127. finally
  128. JsonData.Free;
  129. end;
  130. finally
  131. Parser.Free;
  132. Stream.Free;
  133. end;
  134. end;
  135. procedure ConvertToGettextPO;
  136. var
  137. i, j: Integer;
  138. f: Text;
  139. item: TConstItem;
  140. s: String;
  141. c: Char;
  142. begin
  143. Assign(f, OutFilename);
  144. Rewrite(f);
  145. if CharSet<>'' then begin
  146. // Write file header with
  147. WriteLn(f, 'msgid ""');
  148. WriteLn(f, 'msgstr ""');
  149. WriteLn(f, '"MIME-Version: 1.0\n"');
  150. WriteLn(f, '"Content-Type: text/plain; charset=', CharSet, '\n"');
  151. WriteLn(f, '"Content-Transfer-Encoding: 8bit\n"');
  152. WriteLn(f);
  153. end;
  154. for i := 0 to ConstItems.Count - 1 do begin
  155. item := TConstItem(ConstItems.items[i]);
  156. // Convert string to C-style syntax
  157. s := '';
  158. for j := 1 to Length(item.Value) do begin
  159. c := item.Value[j];
  160. case c of
  161. #9: s := s + '\t';
  162. #10: s := s + '\n';
  163. {$IFNDEF UNIX}
  164. #13: ;
  165. #1..#8, #11..#12, #14..#31, #128..#255:
  166. {$ELSE}
  167. #1..#8, #11..#31, #128..#255:
  168. {$ENDIF}
  169. s := s + '\' +
  170. Chr(Ord(c) shr 6 + 48) +
  171. Chr((Ord(c) shr 3) and 7 + 48) +
  172. Chr(Ord(c) and 7 + 48);
  173. '\': s := s + '\\';
  174. '"': s := s + '\"';
  175. else s := s + c;
  176. end;
  177. end;
  178. // Write msg entry
  179. WriteLn(f, '#: ', item.ModuleName, ':', item.ConstName);
  180. j := Pos('\n', s);
  181. if j > 0 then begin
  182. WriteLn(f, 'msgid ""');
  183. while j > 0 do begin
  184. Writeln(f, '"',copy(s, 1, j+1),'"');
  185. Delete(s, 1, j+1);
  186. j := Pos('\n', s);
  187. end;
  188. if s <> '' then
  189. Writeln(f, '"',s,'"');
  190. end
  191. else
  192. WriteLn(f, 'msgid "', s, '"');
  193. WriteLn(f, 'msgstr ""');
  194. WriteLn(f);
  195. end;
  196. Close(f);
  197. end;
  198. // This routine stores rst file in rc format. Can be written as MESSAGETABLE
  199. // as STRINGTABLE. Beware! OS/2 RC doesn't support lines longer whan 255 chars.
  200. procedure ConvertToRC;
  201. var
  202. i, j: Integer;
  203. f: Text;
  204. item: TConstItem;
  205. s: String;
  206. c: Char;
  207. begin
  208. Assign(f, OutFilename);
  209. Rewrite(f);
  210. If MessageTable then
  211. WriteLn(F, 'MESSAGETABLE')
  212. else
  213. WriteLn(F, 'STRINGTABLE');
  214. WriteLn(F, 'BEGIN');
  215. If Identifier<>'' then WriteLn(F, '#define ', Identifier);
  216. for i := 0 to ConstItems.Count - 1 do begin
  217. item := TConstItem(ConstItems.items[i]);
  218. // Convert string to C-style syntax
  219. s := '';
  220. for j := 1 to Length(item.Value) do begin
  221. c := item.Value[j];
  222. case c of
  223. #9: s := s + '\t';
  224. #10: s := s + '\n"'#13#10'"';
  225. {$IFNDEF UNIX}
  226. #13: ;
  227. #1..#8, #11..#12, #14..#31, #128..#255:
  228. {$ELSE}
  229. #1..#8, #11..#31, #128..#255:
  230. {$ENDIF}
  231. s := s + '\' +
  232. Chr(Ord(c) shr 6 + 48) +
  233. Chr((Ord(c) shr 3) and 7 + 48) +
  234. Chr(Ord(c) and 7 + 48);
  235. '\': s := s + '\\';
  236. '"': s := s + '\"';
  237. else s := s + c;
  238. end;
  239. end;
  240. // Write msg entry
  241. WriteLn(f, '/* ', item.ModuleName, ':', item.ConstName, '*/');
  242. WriteLn(f, '/* ', s, ' */');
  243. If Identifier<>'' then Write(F, Identifier, '+');
  244. WriteLn(f, I+FirstMessage,' "', s, '"');
  245. WriteLn(f);
  246. end;
  247. WriteLn(F, 'END');
  248. Close(f);
  249. end;
  250. // This routine stores rst file in OS/2 msg format. This format is preffered
  251. // for help screens, messages, etc.
  252. procedure ConvertToOS2MSG;
  253. var
  254. i, j: Integer;
  255. f: Text;
  256. item: TConstItem;
  257. s: String;
  258. begin
  259. If (ConstItems.Count+FirstMessage-1)>9999 then
  260. begin
  261. WriteLn(MessageNumberTooBig);
  262. Halt(1);
  263. end;
  264. Identifier:=Copy(UpperCase(Identifier), 1, 3);
  265. Assign(f, OutFilename);
  266. Rewrite(f);
  267. WriteLn(f, Identifier);
  268. // Fake entry, because MKMSGF limitation
  269. WriteLn(f, Format('%s%.4d?: ',[Identifier, FirstMessage-1]));
  270. for i := 0 to ConstItems.Count - 1 do begin
  271. item := TConstItem(ConstItems.items[i]);
  272. // Prepare comment string
  273. // Convert string to C-style syntax
  274. s := '';
  275. j:=1;
  276. while j<=Length(item.Value) do
  277. begin
  278. if copy(item.Value, j, 2)=#13#10 then
  279. begin
  280. s:=s+#13#10';';
  281. Inc(j, 2);
  282. end else begin
  283. s := s + item.Value[j];
  284. Inc(j);
  285. end;
  286. end;
  287. // Write msg entry
  288. WriteLn(f, ';', item.ModuleName, '.', item.ConstName);
  289. WriteLn(f, Format(';%s%.4dP: %s %%0',[Identifier, i+FirstMessage, s]));
  290. WriteLn(f, Format('%s%.4dP: %s %%0',[Identifier, i+FirstMessage, Item.Value]));
  291. end;
  292. Close(f);
  293. end;
  294. type
  295. TConversionProc = procedure;
  296. var
  297. i: Integer;
  298. ConversionProc: TConversionProc;
  299. OutputFormat: String;
  300. begin
  301. if (ParamStr(1) = '-h') or (ParamStr(1) = '--help') then begin
  302. WriteLn(help);
  303. exit;
  304. end;
  305. ConversionProc := @ConvertToGettextPO;
  306. OutputFormat:='';
  307. CharSet:='';
  308. Identifier:='';
  309. FirstMessage:=0;
  310. MessageTable:=True;
  311. i := 1;
  312. while i <= ParamCount do begin
  313. if ParamStr(i) = '-i' then begin
  314. if InFilename <> '' then begin
  315. WriteLn(StdErr, OptionAlreadySpecified, '-i');
  316. Halt(1);
  317. end;
  318. InFilename := ParamStr(i + 1);
  319. Inc(i, 2);
  320. end else if ParamStr(i) = '-o' then begin
  321. if OutFilename <> '' then begin
  322. WriteLn(StdErr, OptionAlreadySpecified, '-o');
  323. Halt(1);
  324. end;
  325. OutFilename := ParamStr(i + 1);
  326. Inc(i, 2);
  327. end else if ParamStr(i) = '-f' then begin
  328. if OutputFormat <> '' then begin
  329. WriteLn(StdErr, OptionAlreadySpecified, '-f');
  330. Halt(1);
  331. end;
  332. if ParamStr(i + 1) = 'po' then
  333. OutputFormat:='po'
  334. else if ParamStr(i + 1) = 'msg' then begin
  335. OutputFormat:='msg';
  336. ConversionProc := @ConvertToOS2MSG;
  337. end else if ParamStr(i + 1) = 'rc' then begin
  338. OutputFormat:='rc';
  339. ConversionProc := @ConvertToRC;
  340. end else begin
  341. WriteLn(StdErr, InvalidOutputFormat, ParamStr(i + 1));
  342. Halt(1);
  343. end;
  344. Inc(i, 2);
  345. end else if ParamStr(i) = '-c' then begin
  346. if (OutputFormat='') or (OutputFormat='po') then begin
  347. if CharSet <> '' then begin
  348. WriteLn(StdErr, OptionAlreadySpecified, '-c');
  349. Halt(1);
  350. end;
  351. CharSet:=ParamStr(i+1);
  352. end else
  353. begin
  354. if Identifier <> '' then begin
  355. WriteLn(StdErr, OptionAlreadySpecified, '-c');
  356. Halt(1);
  357. end;
  358. Identifier:=ParamStr(i+1);
  359. end;
  360. Inc(i, 2);
  361. end else if ParamStr(i) = '-s' then begin
  362. if not MessageTable then begin
  363. WriteLn(StdErr, OptionAlreadySpecified, '-s');
  364. Halt(1);
  365. end;
  366. MessageTable:=False;
  367. Inc(i);
  368. end else if ParamStr(i) = '-n' then begin
  369. if FirstMessage <> 0 then begin
  370. WriteLn(StdErr, OptionAlreadySpecified, '-n');
  371. Halt(1);
  372. end;
  373. try
  374. FirstMessage := StrToInt(ParamStr(i + 1));
  375. If (FirstMessage<1) then raise EConvertError.Create(InvalidRange+' '+ParamStr(i+1));
  376. except
  377. on EConvertError do
  378. begin
  379. WriteLn(StdErr, InvalidOption, ParamStr(i));
  380. Halt(1);
  381. end;
  382. end;
  383. Inc(i, 2);
  384. end else begin
  385. WriteLn(StdErr, InvalidOption, ParamStr(i));
  386. Halt(1);
  387. end;
  388. end;
  389. If ((OutputFormat<>'') and (OutputFormat<>'po')) and (CharSet<>'') then begin
  390. WriteLn(StdErr, InvalidOption, '');
  391. Halt(1);
  392. end;
  393. If ((OutputFormat<>'msg') and (OutputFormat<>'rc')) and ((Identifier<>'') or (FirstMessage<>0)) then begin
  394. WriteLn(StdErr, InvalidOption, '');
  395. Halt(1);
  396. end;
  397. If (OutputFormat='msg') and (Identifier='') then begin
  398. WriteLn(StdErr, RequiredOption, '-c');
  399. Halt(1);
  400. end;
  401. if OutFilename = '' then begin
  402. WriteLn(StdErr, NoOutFilename);
  403. Halt(1);
  404. end;
  405. ConstItems := TCollection.Create(TConstItem);
  406. if ExtractFileExt(InFilename) = '.rsj' then
  407. ReadRSJFile
  408. else
  409. ReadRSTFile;
  410. ConversionProc;
  411. end.