rstconv.pp 14 KB

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