mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-06 16:27:57 +02:00
521 lines
14 KiB
ObjectPascal
521 lines
14 KiB
ObjectPascal
{
|
|
This file is part of the Free Pascal run time library.
|
|
Copyright (c) 1999-2000 by Sebastian Guenther
|
|
Added .rc and OS/2 MSG support in 2002 by Yuri Prokushev
|
|
|
|
.rst resource string table file converter.
|
|
|
|
See the file COPYING.FPC, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
**********************************************************************}
|
|
{$MODE objfpc}
|
|
{$H+}
|
|
|
|
program rstconv;
|
|
|
|
uses
|
|
{$ifdef unix}
|
|
cwstring,
|
|
{$endif}
|
|
sysutils, classes, jsonparser, fpjson, charset, cpall;
|
|
|
|
resourcestring
|
|
help =
|
|
'rstconv [-h|--help] Displays this help'+LineEnding+
|
|
'rstconv options Convert .rsj/.rst file'+LineEnding+LineEnding+
|
|
'Options are:'+LineEnding+
|
|
' -i file Use specified file instead of stdin as input .rsj/.rst (OPTIONAL)'+LineEnding+
|
|
' -o file Write output to specified file (REQUIRED)'+LineEnding+
|
|
' -f format Specifies the output format:'+LineEnding+
|
|
' po GNU gettext .po (portable) format (DEFAULT)'+LineEnding+
|
|
' msg IBM OS/2 MSG file format'+LineEnding+
|
|
' rc Resource compiler .rc format'+LineEnding+LineEnding+
|
|
'.po format only options are:'+LineEnding+
|
|
' -c char set Adds a header specifying the given character set (OPTIONAL).'+LineEnding+LineEnding+
|
|
'OS/2 MSG file only options are:'+LineEnding+
|
|
' -c identifier Specifies the component identifier (REQUIRED).'+LineEnding+
|
|
' Identifier is any three chars in upper case.'+LineEnding+
|
|
' -n number Specifies the first message number [1-9999] (OPTIONAL).'+LineEnding+LineEnding+
|
|
'Resource compiler script only options are:'+LineEnding+
|
|
' -s Use STRINGTABLE instead of MESSAGETABLE'+LineEnding+
|
|
' -c identifier Use identifier as ID base (ID+n) (OPTIONAL)'+LineEnding+
|
|
' -n number Specifies the first ID number (OPTIONAL)'+LineEnding+
|
|
'.rsj-input format-only options are:'+LineEnding+
|
|
' -p codepage Convert the string data to the specified code page before'+LineEnding+
|
|
' writing it to the output file. Possible values:';
|
|
|
|
|
|
InvalidOption = 'Invalid option - ';
|
|
RequiredOption = 'Required option is absent - ';
|
|
OptionAlreadySpecified = 'Option has already been specified - ';
|
|
NoOutFilename = 'No output filename specified';
|
|
InvalidOutputFormat = 'Invalid output format -';
|
|
MessageNumberTooBig = 'Message number too big';
|
|
InvalidRange = 'Invalid range of the first message number';
|
|
MissingOption = 'Missing option after parameter ';
|
|
UnsupportedOutputCodePage = 'Unsupported output code page specified: ';
|
|
RstNoOutputCodePage = 'It is not possible to specify an output code page when using a .rst file';
|
|
|
|
type
|
|
|
|
TConstItem = class(TCollectionItem)
|
|
public
|
|
ModuleName, ConstName, Value: String;
|
|
end;
|
|
|
|
var
|
|
InFilename, OutFilename: String;
|
|
ConstItems: TCollection;
|
|
HeaderCharSet: String;
|
|
Identifier: String;
|
|
OutputCodePage: Longint;
|
|
FirstMessage: Word;
|
|
MessageTable: Boolean;
|
|
|
|
procedure ReadRSTFile;
|
|
var
|
|
f: Text;
|
|
s: String;
|
|
item: TConstItem;
|
|
DotPos, EqPos, i, j: Integer;
|
|
begin
|
|
Assign(f, InFilename);
|
|
Reset(f);
|
|
|
|
while not eof(f) do begin
|
|
ReadLn(f, s);
|
|
If (Length(S)=0) or (S[1]='#') then
|
|
continue;
|
|
item := TConstItem(ConstItems.Add);
|
|
|
|
DotPos := Pos('.', s);
|
|
EqPos := Pos('=', s);
|
|
if DotPos > EqPos then // paranoia checking.
|
|
DotPos := 0;
|
|
item.ModuleName := Copy(s, 1, DotPos - 1);
|
|
item.ConstName := Copy(s, DotPos + 1, EqPos - DotPos - 1);
|
|
|
|
item.Value := '';
|
|
i := EqPos + 1;
|
|
while i <= Length(s) do begin
|
|
if s[i] = '''' then begin
|
|
Inc(i);
|
|
j := i;
|
|
while (i <= Length(s)) and (s[i] <> '''') do
|
|
Inc(i);
|
|
item.Value := item.Value + Copy(s, j, i - j);
|
|
Inc(i);
|
|
end else if s[i] = '#' then begin
|
|
Inc(i);
|
|
j := i;
|
|
while (i <= Length(s)) and (s[i] in ['0'..'9']) do
|
|
Inc(i);
|
|
item.Value := item.Value + Chr(StrToInt(Copy(s, j, i - j)));
|
|
end else if s[i] = '+' then begin
|
|
ReadLn(f, s);
|
|
i := 1;
|
|
end else
|
|
Inc(i);
|
|
end;
|
|
end;
|
|
|
|
Close(f);
|
|
end;
|
|
|
|
procedure ReadRSJFile;
|
|
var
|
|
Stream: TFileStream;
|
|
Parser: TJSONParser;
|
|
JsonItems,
|
|
RawStringData: TJSONArray;
|
|
JsonData, JsonItem: TJSONObject;
|
|
S: String;
|
|
item: TConstItem;
|
|
DotPos, I, J: Integer;
|
|
begin
|
|
if OutputCodePage<>-1 then
|
|
DefaultSystemCodePage:=OutputCodePage;
|
|
Stream := TFileStream.Create(InFilename, fmOpenRead or fmShareDenyNone);
|
|
Parser := TJSONParser.Create(Stream);
|
|
try
|
|
JsonData := Parser.Parse as TJSONObject;
|
|
try
|
|
JsonItems := JsonData.Arrays['strings'];
|
|
for I := 0 to JsonItems.Count - 1 do
|
|
begin
|
|
item := TConstItem(ConstItems.Add);
|
|
JsonItem := JsonItems.Items[I] as TJSONObject;
|
|
S := JsonItem.Get('name');
|
|
DotPos := Pos('.', s);
|
|
item.ModuleName := Copy(s, 1, DotPos - 1);
|
|
item.ConstName := Copy(s, DotPos + 1, Length(S) - DotPos);
|
|
if OutputCodePage=-1 then
|
|
begin
|
|
RawStringData:=JsonItem.Get('sourcebytes',TJSONArray(nil));
|
|
SetLength(item.Value, RawStringData.Count);
|
|
for J := 1 to Length(item.Value) do
|
|
item.Value[J]:=char(RawStringData.Integers[J-1]);
|
|
end
|
|
else
|
|
{ automatically converts from UTF-16 to the correct code page due
|
|
to the change of DefaultSystemCodePage to OutputCodePage above }
|
|
item.Value := JsonItem.Get('value');
|
|
end;
|
|
finally
|
|
JsonData.Free;
|
|
end;
|
|
finally
|
|
Parser.Free;
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure ConvertToGettextPO;
|
|
var
|
|
i, j: Integer;
|
|
f: Text;
|
|
item: TConstItem;
|
|
s: String;
|
|
c: Char;
|
|
|
|
begin
|
|
Assign(f, OutFilename);
|
|
Rewrite(f);
|
|
|
|
if HeaderCharSet<>'' then begin
|
|
// Write file header with
|
|
WriteLn(f, 'msgid ""');
|
|
WriteLn(f, 'msgstr ""');
|
|
WriteLn(f, '"MIME-Version: 1.0\n"');
|
|
WriteLn(f, '"Content-Type: text/plain; charset=', HeaderCharSet, '\n"');
|
|
WriteLn(f, '"Content-Transfer-Encoding: 8bit\n"');
|
|
WriteLn(f);
|
|
end;
|
|
|
|
for i := 0 to ConstItems.Count - 1 do begin
|
|
item := TConstItem(ConstItems.items[i]);
|
|
|
|
// Convert string to C-style syntax
|
|
s := '';
|
|
for j := 1 to Length(item.Value) do begin
|
|
c := item.Value[j];
|
|
case c of
|
|
#9: s := s + '\t';
|
|
#10: s := s + '\n';
|
|
{$IFNDEF UNIX}
|
|
#13: ;
|
|
#1..#8, #11..#12, #14..#31, #128..#255:
|
|
{$ELSE}
|
|
#1..#8, #11..#31, #128..#255:
|
|
{$ENDIF}
|
|
s := s + '\' +
|
|
Chr(Ord(c) shr 6 + 48) +
|
|
Chr((Ord(c) shr 3) and 7 + 48) +
|
|
Chr(Ord(c) and 7 + 48);
|
|
'\': s := s + '\\';
|
|
'"': s := s + '\"';
|
|
else s := s + c;
|
|
end;
|
|
end;
|
|
|
|
// Write msg entry
|
|
WriteLn(f, '#: ', item.ModuleName, ':', item.ConstName);
|
|
j := Pos('\n', s);
|
|
if j > 0 then begin
|
|
WriteLn(f, 'msgid ""');
|
|
while j > 0 do begin
|
|
Writeln(f, '"',copy(s, 1, j+1),'"');
|
|
Delete(s, 1, j+1);
|
|
j := Pos('\n', s);
|
|
end;
|
|
if s <> '' then
|
|
Writeln(f, '"',s,'"');
|
|
end
|
|
else
|
|
WriteLn(f, 'msgid "', s, '"');
|
|
WriteLn(f, 'msgstr ""');
|
|
WriteLn(f);
|
|
end;
|
|
|
|
Close(f);
|
|
end;
|
|
|
|
// This routine stores rst file in rc format. Can be written as MESSAGETABLE
|
|
// as STRINGTABLE. Beware! OS/2 RC doesn't support lines longer whan 255 chars.
|
|
procedure ConvertToRC;
|
|
var
|
|
i, j: Integer;
|
|
f: Text;
|
|
item: TConstItem;
|
|
s: String;
|
|
c: Char;
|
|
|
|
begin
|
|
Assign(f, OutFilename);
|
|
Rewrite(f);
|
|
|
|
If MessageTable then
|
|
WriteLn(F, 'MESSAGETABLE')
|
|
else
|
|
WriteLn(F, 'STRINGTABLE');
|
|
|
|
WriteLn(F, 'BEGIN');
|
|
If Identifier<>'' then WriteLn(F, '#define ', Identifier);
|
|
|
|
for i := 0 to ConstItems.Count - 1 do begin
|
|
item := TConstItem(ConstItems.items[i]);
|
|
|
|
// Convert string to C-style syntax
|
|
s := '';
|
|
for j := 1 to Length(item.Value) do begin
|
|
c := item.Value[j];
|
|
case c of
|
|
#9: s := s + '\t';
|
|
#10: s := s + '\n"'#13#10'"';
|
|
{$IFNDEF UNIX}
|
|
#13: ;
|
|
#1..#8, #11..#12, #14..#31, #128..#255:
|
|
{$ELSE}
|
|
#1..#8, #11..#31, #128..#255:
|
|
{$ENDIF}
|
|
s := s + '\' +
|
|
Chr(Ord(c) shr 6 + 48) +
|
|
Chr((Ord(c) shr 3) and 7 + 48) +
|
|
Chr(Ord(c) and 7 + 48);
|
|
'\': s := s + '\\';
|
|
'"': s := s + '\"';
|
|
else s := s + c;
|
|
end;
|
|
end;
|
|
|
|
// Write msg entry
|
|
WriteLn(f, '/* ', item.ModuleName, ':', item.ConstName, '*/');
|
|
WriteLn(f, '/* ', s, ' */');
|
|
If Identifier<>'' then Write(F, Identifier, '+');
|
|
WriteLn(f, I+FirstMessage,' "', s, '"');
|
|
WriteLn(f);
|
|
end;
|
|
|
|
WriteLn(F, 'END');
|
|
Close(f);
|
|
end;
|
|
|
|
// This routine stores rst file in OS/2 msg format. This format is preffered
|
|
// for help screens, messages, etc.
|
|
procedure ConvertToOS2MSG;
|
|
var
|
|
i, j: Integer;
|
|
f: Text;
|
|
item: TConstItem;
|
|
s: String;
|
|
begin
|
|
If (ConstItems.Count+FirstMessage-1)>9999 then
|
|
begin
|
|
WriteLn(MessageNumberTooBig);
|
|
Halt(1);
|
|
end;
|
|
|
|
Identifier:=Copy(UpperCase(Identifier), 1, 3);
|
|
|
|
Assign(f, OutFilename);
|
|
Rewrite(f);
|
|
|
|
WriteLn(f, Identifier);
|
|
|
|
// Fake entry, because MKMSGF limitation
|
|
WriteLn(f, Format('%s%.4d?: ',[Identifier, FirstMessage-1]));
|
|
|
|
for i := 0 to ConstItems.Count - 1 do begin
|
|
item := TConstItem(ConstItems.items[i]);
|
|
|
|
// Prepare comment string
|
|
// Convert string to C-style syntax
|
|
s := '';
|
|
j:=1;
|
|
while j<=Length(item.Value) do
|
|
begin
|
|
if copy(item.Value, j, 2)=#13#10 then
|
|
begin
|
|
s:=s+#13#10';';
|
|
Inc(j, 2);
|
|
end else begin
|
|
s := s + item.Value[j];
|
|
Inc(j);
|
|
end;
|
|
end;
|
|
|
|
// Write msg entry
|
|
WriteLn(f, ';', item.ModuleName, '.', item.ConstName);
|
|
WriteLn(f, Format(';%s%.4dP: %s %%0',[Identifier, i+FirstMessage, s]));
|
|
WriteLn(f, Format('%s%.4dP: %s %%0',[Identifier, i+FirstMessage, Item.Value]));
|
|
end;
|
|
|
|
Close(f);
|
|
end;
|
|
|
|
type
|
|
TConversionProc = procedure;
|
|
var
|
|
i: Integer;
|
|
ConversionProc: TConversionProc;
|
|
OutputFormat: String;
|
|
begin
|
|
|
|
if (ParamStr(1) = '-h') or (ParamStr(1) = '--help') then begin
|
|
WriteLn(help);
|
|
for i:=low(word) to high(word) do
|
|
if mappingavailable(i) then
|
|
writeln(' ',getmap(i)^.cpname);
|
|
{ UTF-8 is not supported via the CharSet unit }
|
|
writeln(' UTF-8');
|
|
exit;
|
|
end;
|
|
|
|
ConversionProc := @ConvertToGettextPO;
|
|
OutputFormat:='';
|
|
HeaderCharSet:='';
|
|
Identifier:='';
|
|
FirstMessage:=0;
|
|
MessageTable:=True;
|
|
OutputCodePage:=-1;
|
|
|
|
i := 1;
|
|
while i <= ParamCount do begin
|
|
if ParamStr(i) = '-i' then begin
|
|
if InFilename <> '' then begin
|
|
WriteLn(StdErr, OptionAlreadySpecified, '-i');
|
|
Halt(1);
|
|
end;
|
|
InFilename := ParamStr(i + 1);
|
|
Inc(i, 2);
|
|
end else if ParamStr(i) = '-o' then begin
|
|
if OutFilename <> '' then begin
|
|
WriteLn(StdErr, OptionAlreadySpecified, '-o');
|
|
Halt(1);
|
|
end;
|
|
OutFilename := ParamStr(i + 1);
|
|
Inc(i, 2);
|
|
end else if ParamStr(i) = '-f' then begin
|
|
if OutputFormat <> '' then begin
|
|
WriteLn(StdErr, OptionAlreadySpecified, '-f');
|
|
Halt(1);
|
|
end;
|
|
if ParamStr(i + 1) = 'po' then
|
|
OutputFormat:='po'
|
|
else if ParamStr(i + 1) = 'msg' then begin
|
|
OutputFormat:='msg';
|
|
ConversionProc := @ConvertToOS2MSG;
|
|
end else if ParamStr(i + 1) = 'rc' then begin
|
|
OutputFormat:='rc';
|
|
ConversionProc := @ConvertToRC;
|
|
end else begin
|
|
WriteLn(StdErr, InvalidOutputFormat, ParamStr(i + 1));
|
|
Halt(1);
|
|
end;
|
|
Inc(i, 2);
|
|
end else if ParamStr(i) = '-c' then begin
|
|
if (OutputFormat='') or (OutputFormat='po') then begin
|
|
if HeaderCharSet <> '' then begin
|
|
WriteLn(StdErr, OptionAlreadySpecified, '-c');
|
|
Halt(1);
|
|
end;
|
|
HeaderCharSet:=ParamStr(i+1);
|
|
end else
|
|
begin
|
|
if Identifier <> '' then begin
|
|
WriteLn(StdErr, OptionAlreadySpecified, '-c');
|
|
Halt(1);
|
|
end;
|
|
Identifier:=ParamStr(i+1);
|
|
end;
|
|
Inc(i, 2);
|
|
end else if ParamStr(i) = '-s' then begin
|
|
if not MessageTable then begin
|
|
WriteLn(StdErr, OptionAlreadySpecified, '-s');
|
|
Halt(1);
|
|
end;
|
|
MessageTable:=False;
|
|
Inc(i);
|
|
end else if ParamStr(i) = '-n' then begin
|
|
if FirstMessage <> 0 then begin
|
|
WriteLn(StdErr, OptionAlreadySpecified, '-n');
|
|
Halt(1);
|
|
end;
|
|
try
|
|
FirstMessage := StrToInt(ParamStr(i + 1));
|
|
If (FirstMessage<1) then raise EConvertError.Create(InvalidRange+' '+ParamStr(i+1));
|
|
except
|
|
on EConvertError do
|
|
begin
|
|
WriteLn(StdErr, InvalidOption, ParamStr(i));
|
|
Halt(1);
|
|
end;
|
|
end;
|
|
Inc(i, 2);
|
|
end else if ParamStr(i) = '-p' then
|
|
begin
|
|
if paramcount=i then
|
|
begin
|
|
WriteLn(StdErr, MissingOption,'-p');
|
|
Halt(1)
|
|
end;
|
|
if UpperCase(paramstr(i+1))<>'UTF-8' then
|
|
if not mappingavailable(ParamStr(i+1)) then
|
|
begin
|
|
WriteLn(StdErr, UnsupportedOutputCodePage, ParamStr(i+1));
|
|
Halt(1);
|
|
end
|
|
else
|
|
OutputCodePage:=getmap(ParamStr(i+1))^.cp
|
|
else
|
|
OutputCodePage:=CP_UTF8;
|
|
Inc(i, 2);
|
|
end
|
|
else begin
|
|
WriteLn(StdErr, InvalidOption, ParamStr(i));
|
|
Halt(1);
|
|
end;
|
|
end;
|
|
|
|
If ((OutputFormat<>'') and (OutputFormat<>'po')) and (HeaderCharSet<>'') then begin
|
|
WriteLn(StdErr, InvalidOption, '');
|
|
Halt(1);
|
|
end;
|
|
|
|
If ((OutputFormat<>'msg') and (OutputFormat<>'rc')) and ((Identifier<>'') or (FirstMessage<>0)) then begin
|
|
WriteLn(StdErr, InvalidOption, '');
|
|
Halt(1);
|
|
end;
|
|
|
|
If (OutputFormat='msg') and (Identifier='') then begin
|
|
WriteLn(StdErr, RequiredOption, '-c');
|
|
Halt(1);
|
|
end;
|
|
|
|
if OutFilename = '' then begin
|
|
WriteLn(StdErr, NoOutFilename);
|
|
Halt(1);
|
|
end;
|
|
|
|
ConstItems := TCollection.Create(TConstItem);
|
|
|
|
if ExtractFileExt(InFilename) = '.rsj' then
|
|
ReadRSJFile
|
|
else
|
|
begin
|
|
if OutputCodePage<>-1 then
|
|
begin
|
|
WriteLn(StdErr, RstNoOutputCodePage);
|
|
Halt(1);
|
|
end;
|
|
ReadRSTFile;
|
|
end;
|
|
|
|
ConversionProc;
|
|
end.
|