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
;
29 * We load the ResX format stuff on demand, since the classes are in
30 * System.Windows.Forms (!!!) and we can't depend on that assembly in mono, yet.
32 static void LoadResX () {
36 swf
= Assembly
.Load (Consts
.AssemblySystem_Windows_Forms
);
37 resxr
= swf
.GetType ("System.Resources.ResXResourceReader");
38 resxw
= swf
.GetType ("System.Resources.ResXResourceWriter");
39 } catch (Exception e
) {
40 throw new Exception ("Cannot load support for ResX format: " + e
.Message
);
44 static void Usage () {
46 string Usage
= @"Mono Resource Generator version " + Consts
.MonoVersion
+
47 @" for the 2.0 profile
49 resgen2 source.ext [dest.ext]
50 resgen2 [options] /compile source.ext[,dest.resources] [...]";
52 string Usage
= @"Mono Resource Generator version " + Consts
.MonoVersion
+
53 @" for the 1.0 profile
55 resgen source.ext [dest.ext]
56 resgen /compile source.ext[,dest.resources] [...]";
60 Convert a resource file from one format to another.
61 The currently supported formats are: '.txt' '.resources' '.resx' '.po'.
62 If the destination file is not specified, source.resources will be used.";
68 takes a list of .resX or .txt files to convert to .resources files
69 in one bulk operation, replacing .ext with .resources for the
70 output file name (if not set).
71 -usesourcepath, /useSourcePath
72 to resolve relative file paths, use the directory of the resource
73 file as current directory.";
76 The /compile option takes a list of .resX or .txt files to convert to
77 .resources files in one bulk operation, replacing .ext with .resources for
78 the output file name (if not set).";
82 Console
.WriteLine( Usage
);
85 static IResourceReader
GetReader (Stream stream
, string name
, bool useSourcePath
) {
86 string format
= Path
.GetExtension (name
);
87 switch (format
.ToLower (System
.Globalization
.CultureInfo
.InvariantCulture
)) {
89 return new PoResourceReader (stream
);
92 return new TxtResourceReader (stream
);
94 return new ResourceReader (stream
);
97 IResourceReader reader
= (IResourceReader
) Activator
.CreateInstance (
98 resxr
, new object[] {stream}
);
99 if (useSourcePath
) { // only possible on 2.0 profile, or higher
100 PropertyInfo p
= reader
.GetType ().GetProperty ("BasePath",
101 BindingFlags
.Public
| BindingFlags
.Instance
);
102 if (p
!= null && p
.CanWrite
) {
103 p
.SetValue (reader
, Path
.GetDirectoryName (name
), null);
108 throw new Exception ("Unknown format in file " + name
);
112 static IResourceWriter
GetWriter (Stream stream
, string name
) {
113 string format
= Path
.GetExtension (name
);
114 switch (format
.ToLower ()) {
116 return new PoResourceWriter (stream
);
119 return new TxtResourceWriter (stream
);
121 return new ResourceWriter (stream
);
124 return (IResourceWriter
)Activator
.CreateInstance (resxw
, new object[] {stream}
);
126 throw new Exception ("Unknown format in file " + name
);
130 static int CompileResourceFile (string sname
, string dname
, bool useSourcePath
) {
131 FileStream source
= null;
132 FileStream dest
= null;
133 IResourceReader reader
= null;
134 IResourceWriter writer
= null;
137 source
= new FileStream (sname
, FileMode
.Open
, FileAccess
.Read
);
138 reader
= GetReader (source
, sname
, useSourcePath
);
140 dest
= new FileStream (dname
, FileMode
.Create
, FileAccess
.Write
);
141 writer
= GetWriter (dest
, dname
);
144 foreach (DictionaryEntry e
in reader
) {
146 object val
= e
.Value
;
148 writer
.AddResource ((string)e
.Key
, (string)e
.Value
);
150 writer
.AddResource ((string)e
.Key
, e
.Value
);
152 Console
.WriteLine( "Read in {0} resources from '{1}'", rescount
, sname
);
156 Console
.WriteLine("Writing resource file... Done.");
157 } catch (Exception e
) {
158 Console
.WriteLine ("Error: {0}", e
.Message
);
159 Exception inner
= e
.InnerException
;
161 // under 2.0 ResXResourceReader can wrap an exception into an XmlException
162 // and this hides some helpful message from the original exception
163 XmlException xex
= (inner
as XmlException
);
165 // message is identical to the inner exception (from MWF ResXResourceReader)
166 Console
.WriteLine ("Position: Line {0}, Column {1}.", xex
.LineNumber
, xex
.LinePosition
);
167 inner
= inner
.InnerException
;
170 if (inner
is TargetInvocationException
&& inner
.InnerException
!= null)
171 inner
= inner
.InnerException
;
173 Console
.WriteLine ("Inner exception: {0}", inner
.Message
);
184 // since we're not first reading all entries in source, we may get a
185 // read failure after we're started writing to the destination file
186 // and leave behind a broken resources file, so remove it here
196 static int Main (string[] args
) {
197 bool compileMultiple
= false;
198 bool useSourcePath
= false;
199 ArrayList inputFiles
= new ArrayList ();
201 for (int i
= 0; i
< args
.Length
; i
++) {
202 switch (args
[i
].ToLower ()) {
211 if (inputFiles
.Count
> 0) {
212 // the /compile option should be specified before any files
216 compileMultiple
= true;
219 case "/usesourcepath":
220 case "-usesourcepath":
221 if (compileMultiple
) {
222 // the /usesourcepath option should not appear after the
223 // /compile switch on the command-line
224 Console
.WriteLine ("ResGen : error RG0000: Invalid "
225 + "command line syntax. Switch: \"/compile\" Bad value: "
226 + args
[i
] + ". Use ResGen /? for usage information.");
229 useSourcePath
= true;
233 if (!IsFileArgument (args
[i
])) {
238 ResourceInfo resInf
= new ResourceInfo ();
239 if (compileMultiple
) {
240 string [] pair
= args
[i
].Split (',');
241 switch (pair
.Length
) {
243 resInf
.InputFile
= Path
.GetFullPath (pair
[0]);
244 resInf
.OutputFile
= Path
.ChangeExtension (resInf
.InputFile
,
248 if (pair
[1].Length
== 0) {
249 Console
.WriteLine (@"error: You must specify an input & outfile file name like this:");
250 Console
.WriteLine ("inFile.txt,outFile.resources.");
251 Console
.WriteLine ("You passed in '{0}'.", args
[i
]);
254 resInf
.InputFile
= Path
.GetFullPath (pair
[0]);
255 resInf
.OutputFile
= Path
.GetFullPath (pair
[1]);
262 if ((i
+ 1) < args
.Length
) {
263 resInf
.InputFile
= Path
.GetFullPath (args
[i
]);
264 // move to next arg, since we assume that one holds
265 // the name of the output file
267 resInf
.OutputFile
= Path
.GetFullPath (args
[i
]);
269 resInf
.InputFile
= Path
.GetFullPath (args
[i
]);
270 resInf
.OutputFile
= Path
.ChangeExtension (resInf
.InputFile
,
274 inputFiles
.Add (resInf
);
279 if (inputFiles
.Count
== 0) {
284 foreach (ResourceInfo res
in inputFiles
) {
285 int ret
= CompileResourceFile (res
.InputFile
, res
.OutputFile
, useSourcePath
);
292 private static bool RunningOnUnix
{
294 // check for Unix platforms - see FAQ for more details
295 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
296 int platform
= (int) Environment
.OSVersion
.Platform
;
297 return ((platform
== 4) || (platform
== 128) || (platform
== 6));
301 private static bool IsFileArgument (string arg
)
303 if ((arg
[0] != '-') && (arg
[0] != '/'))
306 // cope with absolute filenames for resx files on unix, as
307 // they also match the option pattern
309 // `/home/test.resx' is considered as a resx file, however
310 // '/test.resx' is considered as error
311 return (RunningOnUnix
&& arg
.Length
> 2 && arg
.IndexOf ('/', 2) != -1);
315 class TxtResourceWriter
: IResourceWriter
{
318 public TxtResourceWriter (Stream stream
) {
319 s
= new StreamWriter (stream
);
322 public void AddResource (string name
, byte[] value) {
323 throw new Exception ("Binary data not valid in a text resource file");
326 public void AddResource (string name
, object value) {
327 if (value is string) {
328 AddResource (name
, (string)value);
331 throw new Exception ("Objects not valid in a text resource file");
334 public void AddResource (string name
, string value) {
335 s
.WriteLine ("{0}={1}", name
, Escape (value));
339 static string Escape (string value)
341 StringBuilder b
= new StringBuilder ();
342 for (int i
= 0; i
< value.Length
; i
++) {
357 b
.Append (value [i
]);
361 return b
.ToString ();
364 public void Close () {
368 public void Dispose () {}
370 public void Generate () {}
373 class TxtResourceReader
: IResourceReader
{
377 public TxtResourceReader (Stream stream
) {
378 data
= new Hashtable ();
383 public virtual void Close () {
386 public IDictionaryEnumerator
GetEnumerator() {
387 return data
.GetEnumerator ();
391 StreamReader reader
= new StreamReader (s
);
392 string line
, key
, val
;
393 int epos
, line_num
= 0;
394 while ((line
= reader
.ReadLine ()) != null) {
397 if (line
.Length
== 0 || line
[0] == '#' ||
400 epos
= line
.IndexOf ('=');
402 throw new Exception ("Invalid format at line " + line_num
);
403 key
= line
.Substring (0, epos
);
404 val
= line
.Substring (epos
+ 1);
408 throw new Exception ("Key is empty at line " + line_num
);
410 val
= Unescape (val
);
412 throw new Exception (String
.Format ("Unsupported escape character in value of key '{0}'.", key
));
420 static string Unescape (string value)
422 StringBuilder b
= new StringBuilder ();
424 for (int i
= 0; i
< value.Length
; i
++) {
425 if (value [i
] == '\\') {
426 if (i
== value.Length
- 1)
442 int ch
= int.Parse (value.Substring (++i
, 4), NumberStyles
.HexNumber
);
443 b
.Append (char.ConvertFromUtf32 (ch
));
455 b
.Append (value [i
]);
459 return b
.ToString ();
462 IEnumerator IEnumerable
.GetEnumerator () {
463 return ((IResourceReader
) this).GetEnumerator();
466 void IDisposable
.Dispose () {}
469 class PoResourceReader
: IResourceReader
{
474 public PoResourceReader (Stream stream
)
476 data
= new Hashtable ();
481 public virtual void Close ()
486 public IDictionaryEnumerator
GetEnumerator()
488 return data
.GetEnumerator ();
491 string GetValue (string line
)
493 int begin
= line
.IndexOf ('"');
495 throw new FormatException (String
.Format ("No begin quote at line {0}: {1}", line_num
, line
));
497 int end
= line
.LastIndexOf ('"');
499 throw new FormatException (String
.Format ("No closing quote at line {0}: {1}", line_num
, line
));
501 return line
.Substring (begin
+ 1, end
- begin
- 1);
506 StreamReader reader
= new StreamReader (s
);
509 string msgstr
= null;
510 bool ignoreNext
= false;
512 while ((line
= reader
.ReadLine ()) != null) {
515 if (line
.Length
== 0)
518 if (line
[0] == '#') {
519 if (line
.Length
== 1 || line
[1] != ',')
522 if (line
.IndexOf ("fuzzy") != -1) {
526 throw new FormatException ("Error. Line: " + line_num
);
527 data
.Add (msgid
, msgstr
);
535 if (line
.StartsWith ("msgid ")) {
536 if (msgid
== null && msgstr
!= null)
537 throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num
);
539 if (msgstr
!= null) {
541 data
.Add (msgid
, msgstr
);
548 msgid
= GetValue (line
);
552 if (line
.StartsWith ("msgstr ")) {
554 throw new FormatException ("msgstr with no msgid. Line: " + line_num
);
556 msgstr
= GetValue (line
);
560 if (line
[0] == '"') {
561 if (msgid
== null || msgstr
== null)
562 throw new FormatException ("Invalid format. Line: " + line_num
);
564 msgstr
+= GetValue (line
);
568 throw new FormatException ("Unexpected data. Line: " + line_num
);
573 throw new FormatException ("Expecting msgstr. Line: " + line_num
);
576 data
.Add (msgid
, msgstr
);
580 IEnumerator IEnumerable
.GetEnumerator ()
582 return GetEnumerator();
585 void IDisposable
.Dispose ()
597 class PoResourceWriter
: IResourceWriter
602 public PoResourceWriter (Stream stream
)
604 s
= new StreamWriter (stream
);
607 public void AddResource (string name
, byte [] value)
609 throw new InvalidOperationException ("Binary data not valid in a po resource file");
612 public void AddResource (string name
, object value)
614 if (value is string) {
615 AddResource (name
, (string) value);
618 throw new InvalidOperationException ("Objects not valid in a po resource file");
621 StringBuilder ebuilder
= new StringBuilder ();
623 public string Escape (string ns
)
627 foreach (char c
in ns
){
631 ebuilder
.Append ('\\');
635 ebuilder
.Append ("\\a");
638 ebuilder
.Append ("\\n");
641 ebuilder
.Append ("\\r");
648 return ebuilder
.ToString ();
651 public void AddResource (string name
, string value)
653 if (!headerWritten
) {
654 headerWritten
= true;
658 s
.WriteLine ("msgid \"{0}\"", Escape (name
));
659 s
.WriteLine ("msgstr \"{0}\"", Escape (value));
665 s
.WriteLine ("msgid \"\"");
666 s
.WriteLine ("msgstr \"\"");
667 s
.WriteLine ("\"MIME-Version: 1.0\\n\"");
668 s
.WriteLine ("\"Content-Type: text/plain; charset=UTF-8\\n\"");
669 s
.WriteLine ("\"Content-Transfer-Encoding: 8bit\\n\"");
670 s
.WriteLine ("\"X-Generator: Mono resgen 0.1\\n\"");
671 s
.WriteLine ("#\"Project-Id-Version: FILLME\\n\"");
672 s
.WriteLine ("#\"POT-Creation-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
673 s
.WriteLine ("#\"PO-Revision-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
674 s
.WriteLine ("#\"Last-Translator: FILLME\\n\"");
675 s
.WriteLine ("#\"Language-Team: FILLME\\n\"");
676 s
.WriteLine ("#\"Report-Msgid-Bugs-To: \\n\"");
685 public void Dispose () { }
687 public void Generate () {}
692 public string InputFile
;
693 public string OutputFile
;