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 () {
42 string Usage = @"Mono Resource Generator version 0.1
44 resgen source.ext [dest.ext]";
47 resgen [options] /compile source.ext[,dest.resources] [...]";
50 resgen /compile source.ext[,dest.resources] [...]";
54 Convert a resource file from one format to another.
55 The currently supported formats are: '.txt' '.resources' '.resx' '.po'.
56 If the destination file is not specified, source.resources will be used.";
61 takes a list of .resX or .txt files to convert to .resources files
62 in one bulk operation, replacing .ext with .resources for the
63 output file name (if not set).
65 to resolve relative file paths, use the directory of the resource
66 file as current directory.";
69 The /compile option takes a list of .resX or .txt files to convert to
70 .resources files in one bulk operation, replacing .ext with .resources for
71 the output file name (if not set).";
75 Console.WriteLine( Usage );
78 static IResourceReader GetReader (Stream stream, string name, bool useSourcePath) {
79 string format = Path.GetExtension (name);
80 switch (format.ToLower (System.Globalization.CultureInfo.InvariantCulture)) {
82 return new PoResourceReader (stream);
85 return new TxtResourceReader (stream);
87 return new ResourceReader (stream);
90 IResourceReader reader = (IResourceReader) Activator.CreateInstance (
91 resxr, new object[] {stream});
92 if (useSourcePath) { // only possible on 2.0 profile, or higher
93 PropertyInfo p = reader.GetType ().GetProperty ("BasePath",
94 BindingFlags.Public | BindingFlags.Instance);
95 if (p != null && p.CanWrite) {
96 p.SetValue (reader, Path.GetDirectoryName (name), null);
101 throw new Exception ("Unknown format in file " + name);
105 static IResourceWriter GetWriter (Stream stream, string name) {
106 string format = Path.GetExtension (name);
107 switch (format.ToLower ()) {
109 return new PoResourceWriter (stream);
112 return new TxtResourceWriter (stream);
114 return new ResourceWriter (stream);
117 return (IResourceWriter)Activator.CreateInstance (resxw, new object[] {stream});
119 throw new Exception ("Unknown format in file " + name);
123 static int CompileResourceFile (string sname, string dname, bool useSourcePath) {
124 FileStream source = null;
125 FileStream dest = null;
126 IResourceReader reader = null;
127 IResourceWriter writer = null;
130 source = new FileStream (sname, FileMode.Open, FileAccess.Read);
131 reader = GetReader (source, sname, useSourcePath);
133 dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
134 writer = GetWriter (dest, dname);
137 foreach (DictionaryEntry e in reader) {
139 object val = e.Value;
141 writer.AddResource ((string)e.Key, (string)e.Value);
143 writer.AddResource ((string)e.Key, e.Value);
145 Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
149 Console.WriteLine("Writing resource file... Done.");
150 } catch (Exception e) {
151 Console.WriteLine ("Error: {0}", e.Message);
152 Exception inner = e.InnerException;
153 if (inner is TargetInvocationException && inner.InnerException != null)
154 inner = inner.InnerException;
156 Console.WriteLine ("Inner exception: {0}", inner.Message);
167 // since we're not first reading all entries in source, we may get a
168 // read failure after we're started writing to the destination file
169 // and leave behind a broken resources file, so remove it here
179 static int Main (string[] args) {
180 bool compileMultiple = false;
181 bool useSourcePath = false;
182 ArrayList inputFiles = new ArrayList ();
184 for (int i = 0; i < args.Length; i++) {
185 switch (args [i].ToLower ()) {
194 if (inputFiles.Count > 0) {
195 // the /compile option should be specified before any files
199 compileMultiple = true;
202 case "/usesourcepath":
203 case "-usesourcepath":
204 if (compileMultiple) {
205 // the /usesourcepath option should not appear after the
206 // /compile switch on the command-line
207 Console.WriteLine ("ResGen : error RG0000: Invalid "
208 + "command line syntax. Switch: \"/compile\" Bad value: "
209 + args [i] + ". Use ResGen /? for usage information.");
212 useSourcePath = true;
216 if (!IsFileArgument (args [i])) {
221 ResourceInfo resInf = new ResourceInfo ();
222 if (compileMultiple) {
223 string [] pair = args [i].Split (',');
224 switch (pair.Length) {
226 resInf.InputFile = Path.GetFullPath (pair [0]);
227 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
231 if (pair [1].Length == 0) {
232 Console.WriteLine (@"error: You must specify an input & outfile file name like this:");
233 Console.WriteLine ("inFile.txt,outFile.resources.");
234 Console.WriteLine ("You passed in '{0}'.", args [i]);
237 resInf.InputFile = Path.GetFullPath (pair [0]);
238 resInf.OutputFile = Path.GetFullPath (pair [1]);
245 if ((i + 1) < args.Length) {
246 resInf.InputFile = Path.GetFullPath (args [i]);
247 // move to next arg, since we assume that one holds
248 // the name of the output file
250 resInf.OutputFile = Path.GetFullPath (args [i]);
252 resInf.InputFile = Path.GetFullPath (args [i]);
253 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
257 inputFiles.Add (resInf);
262 if (inputFiles.Count == 0) {
267 foreach (ResourceInfo res in inputFiles) {
268 int ret = CompileResourceFile (res.InputFile, res.OutputFile, useSourcePath);
275 private static bool RunningOnUnix {
277 // check for Unix platforms - see FAQ for more details
278 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
279 int platform = (int) Environment.OSVersion.Platform;
280 return ((platform == 4) || (platform == 128));
284 private static bool IsFileArgument (string arg)
286 if ((arg [0] != '-') && (arg [0] != '/'))
289 // cope with absolute filenames for resx files on unix, as
290 // they also match the option pattern
292 // `/home/test.resx' is considered as a resx file, however
293 // '/test.resx' is considered as error
294 return (RunningOnUnix && arg.Length > 2 && arg.IndexOf ('/', 2) != -1);
298 class TxtResourceWriter : IResourceWriter {
301 public TxtResourceWriter (Stream stream) {
302 s = new StreamWriter (stream);
305 public void AddResource (string name, byte[] value) {
306 throw new Exception ("Binary data not valid in a text resource file");
309 public void AddResource (string name, object value) {
310 if (value is string) {
311 AddResource (name, (string)value);
314 throw new Exception ("Objects not valid in a text resource file");
317 public void AddResource (string name, string value) {
318 s.WriteLine ("{0}={1}", name, Escape (value));
322 static string Escape (string value)
324 StringBuilder b = new StringBuilder ();
325 for (int i = 0; i < value.Length; i++) {
340 b.Append (value [i]);
344 return b.ToString ();
347 public void Close () {
351 public void Dispose () {}
353 public void Generate () {}
356 class TxtResourceReader : IResourceReader {
360 public TxtResourceReader (Stream stream) {
361 data = new Hashtable ();
366 public virtual void Close () {
369 public IDictionaryEnumerator GetEnumerator() {
370 return data.GetEnumerator ();
374 StreamReader reader = new StreamReader (s);
375 string line, key, val;
376 int epos, line_num = 0;
377 while ((line = reader.ReadLine ()) != null) {
380 if (line.Length == 0 || line [0] == '#' ||
383 epos = line.IndexOf ('=');
385 throw new Exception ("Invalid format at line " + line_num);
386 key = line.Substring (0, epos);
387 val = line.Substring (epos + 1);
391 throw new Exception ("Key is empty at line " + line_num);
393 val = Unescape (val);
395 throw new Exception (String.Format ("Unsupported escape character in value of key '{0}'.", key));
403 static string Unescape (string value)
405 StringBuilder b = new StringBuilder ();
407 for (int i = 0; i < value.Length; i++) {
408 if (value [i] == '\\') {
409 if (i == value.Length - 1)
425 int ch = int.Parse (value.Substring (++i, 4), NumberStyles.HexNumber);
426 b.Append (char.ConvertFromUtf32 (ch));
438 b.Append (value [i]);
442 return b.ToString ();
445 IEnumerator IEnumerable.GetEnumerator () {
446 return ((IResourceReader) this).GetEnumerator();
449 void IDisposable.Dispose () {}
452 class PoResourceReader : IResourceReader {
457 public PoResourceReader (Stream stream)
459 data = new Hashtable ();
464 public virtual void Close ()
469 public IDictionaryEnumerator GetEnumerator()
471 return data.GetEnumerator ();
474 string GetValue (string line)
476 int begin = line.IndexOf ('"');
478 throw new FormatException (String.Format ("No begin quote at line {0}: {1}", line_num, line));
480 int end = line.LastIndexOf ('"');
482 throw new FormatException (String.Format ("No closing quote at line {0}: {1}", line_num, line));
484 return line.Substring (begin + 1, end - begin - 1);
489 StreamReader reader = new StreamReader (s);
492 string msgstr = null;
493 bool ignoreNext = false;
495 while ((line = reader.ReadLine ()) != null) {
498 if (line.Length == 0)
501 if (line [0] == '#') {
502 if (line.Length == 1 || line [1] != ',')
505 if (line.IndexOf ("fuzzy") != -1) {
509 throw new FormatException ("Error. Line: " + line_num);
510 data.Add (msgid, msgstr);
518 if (line.StartsWith ("msgid ")) {
519 if (msgid == null && msgstr != null)
520 throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num);
522 if (msgstr != null) {
524 data.Add (msgid, msgstr);
531 msgid = GetValue (line);
535 if (line.StartsWith ("msgstr ")) {
537 throw new FormatException ("msgstr with no msgid. Line: " + line_num);
539 msgstr = GetValue (line);
543 if (line [0] == '"') {
544 if (msgid == null || msgstr == null)
545 throw new FormatException ("Invalid format. Line: " + line_num);
547 msgstr += GetValue (line);
551 throw new FormatException ("Unexpected data. Line: " + line_num);
556 throw new FormatException ("Expecting msgstr. Line: " + line_num);
559 data.Add (msgid, msgstr);
563 IEnumerator IEnumerable.GetEnumerator ()
565 return GetEnumerator();
568 void IDisposable.Dispose ()
580 class PoResourceWriter : IResourceWriter
585 public PoResourceWriter (Stream stream)
587 s = new StreamWriter (stream);
590 public void AddResource (string name, byte [] value)
592 throw new InvalidOperationException ("Binary data not valid in a po resource file");
595 public void AddResource (string name, object value)
597 if (value is string) {
598 AddResource (name, (string) value);
601 throw new InvalidOperationException ("Objects not valid in a po resource file");
604 StringBuilder ebuilder = new StringBuilder ();
606 public string Escape (string ns)
610 foreach (char c in ns){
614 ebuilder.Append ('\\');
618 ebuilder.Append ("\\a");
621 ebuilder.Append ("\\n");
624 ebuilder.Append ("\\r");
631 return ebuilder.ToString ();
634 public void AddResource (string name, string value)
636 if (!headerWritten) {
637 headerWritten = true;
641 s.WriteLine ("msgid \"{0}\"", Escape (name));
642 s.WriteLine ("msgstr \"{0}\"", Escape (value));
648 s.WriteLine ("msgid \"\"");
649 s.WriteLine ("msgstr \"\"");
650 s.WriteLine ("\"MIME-Version: 1.0\\n\"");
651 s.WriteLine ("\"Content-Type: text/plain; charset=UTF-8\\n\"");
652 s.WriteLine ("\"Content-Transfer-Encoding: 8bit\\n\"");
653 s.WriteLine ("\"X-Generator: Mono resgen 0.1\\n\"");
654 s.WriteLine ("#\"Project-Id-Version: FILLME\\n\"");
655 s.WriteLine ("#\"POT-Creation-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
656 s.WriteLine ("#\"PO-Revision-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
657 s.WriteLine ("#\"Last-Translator: FILLME\\n\"");
658 s.WriteLine ("#\"Language-Team: FILLME\\n\"");
659 s.WriteLine ("#\"Report-Msgid-Bugs-To: \\n\"");
668 public void Dispose () { }
670 public void Generate () {}
675 public string InputFile;
676 public string OutputFile;