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;
131 source = new FileStream (sname, FileMode.Open, FileAccess.Read);
132 reader = GetReader (source, sname, useSourcePath);
134 dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
135 writer = GetWriter (dest, dname);
138 foreach (DictionaryEntry e in reader) {
140 object val = e.Value;
142 writer.AddResource ((string)e.Key, (string)e.Value);
144 writer.AddResource ((string)e.Key, e.Value);
146 Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
150 Console.WriteLine("Writing resource file... Done.");
152 } catch (Exception e) {
153 Console.WriteLine ("Error: {0}", e.Message);
154 Exception inner = e.InnerException;
155 if (inner is TargetInvocationException && inner.InnerException != null)
156 inner = inner.InnerException;
158 Console.WriteLine ("Inner exception: {0}", inner.Message);
169 // since we're not first reading all entries in source, we may get a
170 // read failure after we're started writing to the destination file
171 // and leave behind a broken resources file, so remove it here
181 static int Main (string[] args) {
182 bool compileMultiple = false;
183 bool useSourcePath = false;
184 ArrayList inputFiles = new ArrayList ();
186 for (int i = 0; i < args.Length; i++) {
187 switch (args [i].ToLower ()) {
196 if (inputFiles.Count > 0) {
197 // the /compile option should be specified before any files
201 compileMultiple = true;
204 case "/usesourcepath":
205 case "-usesourcepath":
206 if (compileMultiple) {
207 // the /usesourcepath option should not appear after the
208 // /compile switch on the command-line
209 Console.WriteLine ("ResGen : error RG0000: Invalid "
210 + "command line syntax. Switch: \"/compile\" Bad value: "
211 + args [i] + ". Use ResGen /? for usage information.");
214 useSourcePath = true;
218 if (!IsFileArgument (args [i])) {
223 ResourceInfo resInf = new ResourceInfo ();
224 if (compileMultiple) {
225 string [] pair = args [i].Split (',');
226 switch (pair.Length) {
228 resInf.InputFile = Path.GetFullPath (pair [0]);
229 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
233 if (pair [1].Length == 0) {
234 Console.WriteLine (@"error: You must specify an input & outfile file name like this:");
235 Console.WriteLine ("inFile.txt,outFile.resources.");
236 Console.WriteLine ("You passed in '{0}'.", args [i]);
239 resInf.InputFile = Path.GetFullPath (pair [0]);
240 resInf.OutputFile = Path.GetFullPath (pair [1]);
247 if ((i + 1) < args.Length) {
248 resInf.InputFile = Path.GetFullPath (args [i]);
249 // move to next arg, since we assume that one holds
250 // the name of the output file
252 resInf.OutputFile = Path.GetFullPath (args [i]);
254 resInf.InputFile = Path.GetFullPath (args [i]);
255 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
259 inputFiles.Add (resInf);
264 if (inputFiles.Count == 0) {
269 foreach (ResourceInfo res in inputFiles) {
270 int ret = CompileResourceFile (res.InputFile, res.OutputFile, useSourcePath);
277 private static bool RunningOnUnix {
279 // check for Unix platforms - see FAQ for more details
280 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
281 int platform = (int) Environment.OSVersion.Platform;
282 return ((platform == 4) || (platform == 128));
286 private static bool IsFileArgument (string arg)
288 if ((arg [0] != '-') && (arg [0] != '/'))
291 // cope with absolute filenames for resx files on unix, as
292 // they also match the option pattern
294 // `/home/test.resx' is considered as a resx file, however
295 // '/test.resx' is considered as error
296 return (RunningOnUnix && arg.Length > 2 && arg.IndexOf ('/', 2) != -1);
300 class TxtResourceWriter : IResourceWriter {
303 public TxtResourceWriter (Stream stream) {
304 s = new StreamWriter (stream);
307 public void AddResource (string name, byte[] value) {
308 throw new Exception ("Binary data not valid in a text resource file");
311 public void AddResource (string name, object value) {
312 if (value is string) {
313 AddResource (name, (string)value);
316 throw new Exception ("Objects not valid in a text resource file");
319 public void AddResource (string name, string value) {
320 s.WriteLine ("{0}={1}", name, Escape (value));
324 static string Escape (string value)
326 StringBuilder b = new StringBuilder ();
327 for (int i = 0; i < value.Length; i++) {
342 b.Append (value [i]);
346 return b.ToString ();
349 public void Close () {
353 public void Dispose () {}
355 public void Generate () {}
358 class TxtResourceReader : IResourceReader {
362 public TxtResourceReader (Stream stream) {
363 data = new Hashtable ();
368 public virtual void Close () {
371 public IDictionaryEnumerator GetEnumerator() {
372 return data.GetEnumerator ();
376 StreamReader reader = new StreamReader (s);
377 string line, key, val;
378 int epos, line_num = 0;
379 while ((line = reader.ReadLine ()) != null) {
382 if (line.Length == 0 || line [0] == '#' ||
385 epos = line.IndexOf ('=');
387 throw new Exception ("Invalid format at line " + line_num);
388 key = line.Substring (0, epos);
389 val = line.Substring (epos + 1);
393 throw new Exception ("Key is empty at line " + line_num);
395 val = Unescape (val);
397 throw new Exception (String.Format ("Unsupported escape character in value of key '{0}'.", key));
405 static string Unescape (string value)
407 StringBuilder b = new StringBuilder ();
409 for (int i = 0; i < value.Length; i++) {
410 if (value [i] == '\\') {
411 if (i == value.Length - 1)
427 int ch = int.Parse (value.Substring (++i, 4), NumberStyles.HexNumber);
428 b.Append (char.ConvertFromUtf32 (ch));
440 b.Append (value [i]);
444 return b.ToString ();
447 IEnumerator IEnumerable.GetEnumerator () {
448 return ((IResourceReader) this).GetEnumerator();
451 void IDisposable.Dispose () {}
454 class PoResourceReader : IResourceReader {
459 public PoResourceReader (Stream stream)
461 data = new Hashtable ();
466 public virtual void Close ()
471 public IDictionaryEnumerator GetEnumerator()
473 return data.GetEnumerator ();
476 string GetValue (string line)
478 int begin = line.IndexOf ('"');
480 throw new FormatException (String.Format ("No begin quote at line {0}: {1}", line_num, line));
482 int end = line.LastIndexOf ('"');
484 throw new FormatException (String.Format ("No closing quote at line {0}: {1}", line_num, line));
486 return line.Substring (begin + 1, end - begin - 1);
491 StreamReader reader = new StreamReader (s);
494 string msgstr = null;
495 bool ignoreNext = false;
497 while ((line = reader.ReadLine ()) != null) {
500 if (line.Length == 0)
503 if (line [0] == '#') {
504 if (line.Length == 1 || line [1] != ',')
507 if (line.IndexOf ("fuzzy") != -1) {
511 throw new FormatException ("Error. Line: " + line_num);
512 data.Add (msgid, msgstr);
520 if (line.StartsWith ("msgid ")) {
521 if (msgid == null && msgstr != null)
522 throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num);
524 if (msgstr != null) {
526 data.Add (msgid, msgstr);
533 msgid = GetValue (line);
537 if (line.StartsWith ("msgstr ")) {
539 throw new FormatException ("msgstr with no msgid. Line: " + line_num);
541 msgstr = GetValue (line);
545 if (line [0] == '"') {
546 if (msgid == null || msgstr == null)
547 throw new FormatException ("Invalid format. Line: " + line_num);
549 msgstr += GetValue (line);
553 throw new FormatException ("Unexpected data. Line: " + line_num);
558 throw new FormatException ("Expecting msgstr. Line: " + line_num);
561 data.Add (msgid, msgstr);
565 IEnumerator IEnumerable.GetEnumerator ()
567 return GetEnumerator();
570 void IDisposable.Dispose ()
582 class PoResourceWriter : IResourceWriter
587 public PoResourceWriter (Stream stream)
589 s = new StreamWriter (stream);
592 public void AddResource (string name, byte [] value)
594 throw new InvalidOperationException ("Binary data not valid in a po resource file");
597 public void AddResource (string name, object value)
599 if (value is string) {
600 AddResource (name, (string) value);
603 throw new InvalidOperationException ("Objects not valid in a po resource file");
606 StringBuilder ebuilder = new StringBuilder ();
608 public string Escape (string ns)
612 foreach (char c in ns){
616 ebuilder.Append ('\\');
620 ebuilder.Append ("\\a");
623 ebuilder.Append ("\\n");
626 ebuilder.Append ("\\r");
633 return ebuilder.ToString ();
636 public void AddResource (string name, string value)
638 if (!headerWritten) {
639 headerWritten = true;
643 s.WriteLine ("msgid \"{0}\"", Escape (name));
644 s.WriteLine ("msgstr \"{0}\"", Escape (value));
650 s.WriteLine ("msgid \"\"");
651 s.WriteLine ("msgstr \"\"");
652 s.WriteLine ("\"MIME-Version: 1.0\\n\"");
653 s.WriteLine ("\"Content-Type: text/plain; charset=UTF-8\\n\"");
654 s.WriteLine ("\"Content-Transfer-Encoding: 8bit\\n\"");
655 s.WriteLine ("\"X-Generator: Mono resgen 0.1\\n\"");
656 s.WriteLine ("#\"Project-Id-Version: FILLME\\n\"");
657 s.WriteLine ("#\"POT-Creation-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
658 s.WriteLine ("#\"PO-Revision-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
659 s.WriteLine ("#\"Last-Translator: FILLME\\n\"");
660 s.WriteLine ("#\"Language-Team: FILLME\\n\"");
661 s.WriteLine ("#\"Report-Msgid-Bugs-To: \\n\"");
670 public void Dispose () { }
672 public void Generate () {}
677 public string InputFile;
678 public string OutputFile;