2 * resgen: convert between the resource formats (.txt, .resources, .resx).
4 * Copyright (c) 2002 Ximian, Inc
7 * Paolo Molaro (lupus@ximian.com)
8 * Gonzalo Paniagua Javier (gonzalo@ximian.com)
12 using System
.Globalization
;
15 using System
.Collections
;
16 using System
.Resources
;
17 using System
.Reflection
;
26 * We load the ResX format stuff on demand, since the classes are in
27 * System.Windows.Forms (!!!) and we can't depend on that assembly in mono, yet.
29 static void LoadResX () {
33 swf
= Assembly
.Load (Consts
.AssemblySystem_Windows_Forms
);
34 resxr
= swf
.GetType ("System.Resources.ResXResourceReader");
35 resxw
= swf
.GetType ("System.Resources.ResXResourceWriter");
36 } catch (Exception e
) {
37 throw new Exception ("Cannot load support for ResX format: " + e
.Message
);
41 static void Usage () {
43 string Usage
= @"Mono Resource Generator version " + Consts
.MonoVersion
+
44 @" for the 2.0 profile
46 resgen2 source.ext [dest.ext]
47 resgen2 [options] /compile source.ext[,dest.resources] [...]";
49 string Usage
= @"Mono Resource Generator version " + Consts
.MonoVersion
+
50 @" for the 1.0 profile
52 resgen source.ext [dest.ext]
53 resgen /compile source.ext[,dest.resources] [...]";
57 Convert a resource file from one format to another.
58 The currently supported formats are: '.txt' '.resources' '.resx' '.po'.
59 If the destination file is not specified, source.resources will be used.";
65 takes a list of .resX or .txt files to convert to .resources files
66 in one bulk operation, replacing .ext with .resources for the
67 output file name (if not set).
68 -usesourcepath, /useSourcePath
69 to resolve relative file paths, use the directory of the resource
70 file as current directory.";
73 The /compile option takes a list of .resX or .txt files to convert to
74 .resources files in one bulk operation, replacing .ext with .resources for
75 the output file name (if not set).";
79 Console
.WriteLine( Usage
);
82 static IResourceReader
GetReader (Stream stream
, string name
, bool useSourcePath
) {
83 string format
= Path
.GetExtension (name
);
84 switch (format
.ToLower (System
.Globalization
.CultureInfo
.InvariantCulture
)) {
86 return new PoResourceReader (stream
);
89 return new TxtResourceReader (stream
);
91 return new ResourceReader (stream
);
94 IResourceReader reader
= (IResourceReader
) Activator
.CreateInstance (
95 resxr
, new object[] {stream}
);
96 if (useSourcePath
) { // only possible on 2.0 profile, or higher
97 PropertyInfo p
= reader
.GetType ().GetProperty ("BasePath",
98 BindingFlags
.Public
| BindingFlags
.Instance
);
99 if (p
!= null && p
.CanWrite
) {
100 p
.SetValue (reader
, Path
.GetDirectoryName (name
), null);
105 throw new Exception ("Unknown format in file " + name
);
109 static IResourceWriter
GetWriter (Stream stream
, string name
) {
110 string format
= Path
.GetExtension (name
);
111 switch (format
.ToLower ()) {
113 return new PoResourceWriter (stream
);
116 return new TxtResourceWriter (stream
);
118 return new ResourceWriter (stream
);
121 return (IResourceWriter
)Activator
.CreateInstance (resxw
, new object[] {stream}
);
123 throw new Exception ("Unknown format in file " + name
);
127 static int CompileResourceFile (string sname
, string dname
, bool useSourcePath
) {
128 FileStream source
= null;
129 FileStream dest
= null;
130 IResourceReader reader
= null;
131 IResourceWriter writer
= null;
134 source
= new FileStream (sname
, FileMode
.Open
, FileAccess
.Read
);
135 reader
= GetReader (source
, sname
, useSourcePath
);
137 dest
= new FileStream (dname
, FileMode
.Create
, FileAccess
.Write
);
138 writer
= GetWriter (dest
, dname
);
141 foreach (DictionaryEntry e
in reader
) {
143 object val
= e
.Value
;
145 writer
.AddResource ((string)e
.Key
, (string)e
.Value
);
147 writer
.AddResource ((string)e
.Key
, e
.Value
);
149 Console
.WriteLine( "Read in {0} resources from '{1}'", rescount
, sname
);
153 Console
.WriteLine("Writing resource file... Done.");
154 } catch (Exception e
) {
155 Console
.WriteLine ("Error: {0}", e
.Message
);
156 Exception inner
= e
.InnerException
;
157 if (inner
is TargetInvocationException
&& inner
.InnerException
!= null)
158 inner
= inner
.InnerException
;
160 Console
.WriteLine ("Inner exception: {0}", inner
.Message
);
171 // since we're not first reading all entries in source, we may get a
172 // read failure after we're started writing to the destination file
173 // and leave behind a broken resources file, so remove it here
183 static int Main (string[] args
) {
184 bool compileMultiple
= false;
185 bool useSourcePath
= false;
186 ArrayList inputFiles
= new ArrayList ();
188 for (int i
= 0; i
< args
.Length
; i
++) {
189 switch (args
[i
].ToLower ()) {
198 if (inputFiles
.Count
> 0) {
199 // the /compile option should be specified before any files
203 compileMultiple
= true;
206 case "/usesourcepath":
207 case "-usesourcepath":
208 if (compileMultiple
) {
209 // the /usesourcepath option should not appear after the
210 // /compile switch on the command-line
211 Console
.WriteLine ("ResGen : error RG0000: Invalid "
212 + "command line syntax. Switch: \"/compile\" Bad value: "
213 + args
[i
] + ". Use ResGen /? for usage information.");
216 useSourcePath
= true;
220 if (!IsFileArgument (args
[i
])) {
225 ResourceInfo resInf
= new ResourceInfo ();
226 if (compileMultiple
) {
227 string [] pair
= args
[i
].Split (',');
228 switch (pair
.Length
) {
230 resInf
.InputFile
= Path
.GetFullPath (pair
[0]);
231 resInf
.OutputFile
= Path
.ChangeExtension (resInf
.InputFile
,
235 if (pair
[1].Length
== 0) {
236 Console
.WriteLine (@"error: You must specify an input & outfile file name like this:");
237 Console
.WriteLine ("inFile.txt,outFile.resources.");
238 Console
.WriteLine ("You passed in '{0}'.", args
[i
]);
241 resInf
.InputFile
= Path
.GetFullPath (pair
[0]);
242 resInf
.OutputFile
= Path
.GetFullPath (pair
[1]);
249 if ((i
+ 1) < args
.Length
) {
250 resInf
.InputFile
= Path
.GetFullPath (args
[i
]);
251 // move to next arg, since we assume that one holds
252 // the name of the output file
254 resInf
.OutputFile
= Path
.GetFullPath (args
[i
]);
256 resInf
.InputFile
= Path
.GetFullPath (args
[i
]);
257 resInf
.OutputFile
= Path
.ChangeExtension (resInf
.InputFile
,
261 inputFiles
.Add (resInf
);
266 if (inputFiles
.Count
== 0) {
271 foreach (ResourceInfo res
in inputFiles
) {
272 int ret
= CompileResourceFile (res
.InputFile
, res
.OutputFile
, useSourcePath
);
279 private static bool RunningOnUnix
{
281 // check for Unix platforms - see FAQ for more details
282 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
283 int platform
= (int) Environment
.OSVersion
.Platform
;
284 return ((platform
== 4) || (platform
== 128));
288 private static bool IsFileArgument (string arg
)
290 if ((arg
[0] != '-') && (arg
[0] != '/'))
293 // cope with absolute filenames for resx files on unix, as
294 // they also match the option pattern
296 // `/home/test.resx' is considered as a resx file, however
297 // '/test.resx' is considered as error
298 return (RunningOnUnix
&& arg
.Length
> 2 && arg
.IndexOf ('/', 2) != -1);
302 class TxtResourceWriter
: IResourceWriter
{
305 public TxtResourceWriter (Stream stream
) {
306 s
= new StreamWriter (stream
);
309 public void AddResource (string name
, byte[] value) {
310 throw new Exception ("Binary data not valid in a text resource file");
313 public void AddResource (string name
, object value) {
314 if (value is string) {
315 AddResource (name
, (string)value);
318 throw new Exception ("Objects not valid in a text resource file");
321 public void AddResource (string name
, string value) {
322 s
.WriteLine ("{0}={1}", name
, Escape (value));
326 static string Escape (string value)
328 StringBuilder b
= new StringBuilder ();
329 for (int i
= 0; i
< value.Length
; i
++) {
344 b
.Append (value [i
]);
348 return b
.ToString ();
351 public void Close () {
355 public void Dispose () {}
357 public void Generate () {}
360 class TxtResourceReader
: IResourceReader
{
364 public TxtResourceReader (Stream stream
) {
365 data
= new Hashtable ();
370 public virtual void Close () {
373 public IDictionaryEnumerator
GetEnumerator() {
374 return data
.GetEnumerator ();
378 StreamReader reader
= new StreamReader (s
);
379 string line
, key
, val
;
380 int epos
, line_num
= 0;
381 while ((line
= reader
.ReadLine ()) != null) {
384 if (line
.Length
== 0 || line
[0] == '#' ||
387 epos
= line
.IndexOf ('=');
389 throw new Exception ("Invalid format at line " + line_num
);
390 key
= line
.Substring (0, epos
);
391 val
= line
.Substring (epos
+ 1);
395 throw new Exception ("Key is empty at line " + line_num
);
397 val
= Unescape (val
);
399 throw new Exception (String
.Format ("Unsupported escape character in value of key '{0}'.", key
));
407 static string Unescape (string value)
409 StringBuilder b
= new StringBuilder ();
411 for (int i
= 0; i
< value.Length
; i
++) {
412 if (value [i
] == '\\') {
413 if (i
== value.Length
- 1)
429 int ch
= int.Parse (value.Substring (++i
, 4), NumberStyles
.HexNumber
);
430 b
.Append (char.ConvertFromUtf32 (ch
));
442 b
.Append (value [i
]);
446 return b
.ToString ();
449 IEnumerator IEnumerable
.GetEnumerator () {
450 return ((IResourceReader
) this).GetEnumerator();
453 void IDisposable
.Dispose () {}
456 class PoResourceReader
: IResourceReader
{
461 public PoResourceReader (Stream stream
)
463 data
= new Hashtable ();
468 public virtual void Close ()
473 public IDictionaryEnumerator
GetEnumerator()
475 return data
.GetEnumerator ();
478 string GetValue (string line
)
480 int begin
= line
.IndexOf ('"');
482 throw new FormatException (String
.Format ("No begin quote at line {0}: {1}", line_num
, line
));
484 int end
= line
.LastIndexOf ('"');
486 throw new FormatException (String
.Format ("No closing quote at line {0}: {1}", line_num
, line
));
488 return line
.Substring (begin
+ 1, end
- begin
- 1);
493 StreamReader reader
= new StreamReader (s
);
496 string msgstr
= null;
497 bool ignoreNext
= false;
499 while ((line
= reader
.ReadLine ()) != null) {
502 if (line
.Length
== 0)
505 if (line
[0] == '#') {
506 if (line
.Length
== 1 || line
[1] != ',')
509 if (line
.IndexOf ("fuzzy") != -1) {
513 throw new FormatException ("Error. Line: " + line_num
);
514 data
.Add (msgid
, msgstr
);
522 if (line
.StartsWith ("msgid ")) {
523 if (msgid
== null && msgstr
!= null)
524 throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num
);
526 if (msgstr
!= null) {
528 data
.Add (msgid
, msgstr
);
535 msgid
= GetValue (line
);
539 if (line
.StartsWith ("msgstr ")) {
541 throw new FormatException ("msgstr with no msgid. Line: " + line_num
);
543 msgstr
= GetValue (line
);
547 if (line
[0] == '"') {
548 if (msgid
== null || msgstr
== null)
549 throw new FormatException ("Invalid format. Line: " + line_num
);
551 msgstr
+= GetValue (line
);
555 throw new FormatException ("Unexpected data. Line: " + line_num
);
560 throw new FormatException ("Expecting msgstr. Line: " + line_num
);
563 data
.Add (msgid
, msgstr
);
567 IEnumerator IEnumerable
.GetEnumerator ()
569 return GetEnumerator();
572 void IDisposable
.Dispose ()
584 class PoResourceWriter
: IResourceWriter
589 public PoResourceWriter (Stream stream
)
591 s
= new StreamWriter (stream
);
594 public void AddResource (string name
, byte [] value)
596 throw new InvalidOperationException ("Binary data not valid in a po resource file");
599 public void AddResource (string name
, object value)
601 if (value is string) {
602 AddResource (name
, (string) value);
605 throw new InvalidOperationException ("Objects not valid in a po resource file");
608 StringBuilder ebuilder
= new StringBuilder ();
610 public string Escape (string ns
)
614 foreach (char c
in ns
){
618 ebuilder
.Append ('\\');
622 ebuilder
.Append ("\\a");
625 ebuilder
.Append ("\\n");
628 ebuilder
.Append ("\\r");
635 return ebuilder
.ToString ();
638 public void AddResource (string name
, string value)
640 if (!headerWritten
) {
641 headerWritten
= true;
645 s
.WriteLine ("msgid \"{0}\"", Escape (name
));
646 s
.WriteLine ("msgstr \"{0}\"", Escape (value));
652 s
.WriteLine ("msgid \"\"");
653 s
.WriteLine ("msgstr \"\"");
654 s
.WriteLine ("\"MIME-Version: 1.0\\n\"");
655 s
.WriteLine ("\"Content-Type: text/plain; charset=UTF-8\\n\"");
656 s
.WriteLine ("\"Content-Transfer-Encoding: 8bit\\n\"");
657 s
.WriteLine ("\"X-Generator: Mono resgen 0.1\\n\"");
658 s
.WriteLine ("#\"Project-Id-Version: FILLME\\n\"");
659 s
.WriteLine ("#\"POT-Creation-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
660 s
.WriteLine ("#\"PO-Revision-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
661 s
.WriteLine ("#\"Last-Translator: FILLME\\n\"");
662 s
.WriteLine ("#\"Language-Team: FILLME\\n\"");
663 s
.WriteLine ("#\"Report-Msgid-Bugs-To: \\n\"");
672 public void Dispose () { }
674 public void Generate () {}
679 public string InputFile
;
680 public string OutputFile
;