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;
27 * We load the ResX format stuff on demand, since the classes are in
28 * System.Windows.Forms (!!!) and we can't depend on that assembly in mono, yet.
30 static void LoadResX () {
34 swf = Assembly.Load (Consts.AssemblySystem_Windows_Forms);
35 resxr = swf.GetType ("System.Resources.ResXResourceReader");
36 resxw = swf.GetType ("System.Resources.ResXResourceWriter");
37 } catch (Exception e) {
38 throw new Exception ("Cannot load support for ResX format: " + e.Message);
42 static void Usage () {
44 string Usage = @"Mono Resource Generator version " + Consts.MonoVersion +
47 resgen source.ext [dest.ext]
48 resgen [options] /compile source.ext[,dest.resources] [...]";
51 Convert a resource file from one format to another.
52 The currently supported formats are: '.txt' '.resources' '.resx' '.po'.
53 If the destination file is not specified, source.resources will be used.";
59 takes a list of .resX or .txt files to convert to .resources files
60 in one bulk operation, replacing .ext with .resources for the
61 output file name (if not set).
62 -usesourcepath, /useSourcePath
63 to resolve relative file paths, use the directory of the resource
64 file as current directory.";
67 Console.WriteLine( Usage );
70 static IResourceReader GetReader (Stream stream, string name, bool useSourcePath) {
71 string format = Path.GetExtension (name);
72 switch (format.ToLower (System.Globalization.CultureInfo.InvariantCulture)) {
74 return new PoResourceReader (stream);
77 return new TxtResourceReader (stream);
79 return new ResourceReader (stream);
82 IResourceReader reader = (IResourceReader) Activator.CreateInstance (
83 resxr, new object[] {stream});
84 if (useSourcePath) { // only possible on 2.0 profile, or higher
85 PropertyInfo p = reader.GetType ().GetProperty ("BasePath",
86 BindingFlags.Public | BindingFlags.Instance);
87 if (p != null && p.CanWrite) {
88 p.SetValue (reader, Path.GetDirectoryName (name), null);
93 throw new Exception ("Unknown format in file " + name);
97 static IResourceWriter GetWriter (Stream stream, string name) {
98 string format = Path.GetExtension (name);
99 switch (format.ToLower ()) {
101 return new PoResourceWriter (stream);
104 return new TxtResourceWriter (stream);
106 return new ResourceWriter (stream);
109 return (IResourceWriter)Activator.CreateInstance (resxw, new object[] {stream});
111 throw new Exception ("Unknown format in file " + name);
115 static int CompileResourceFile (string sname, string dname, bool useSourcePath) {
116 FileStream source = null;
117 FileStream dest = null;
118 IResourceReader reader = null;
119 IResourceWriter writer = null;
122 source = new FileStream (sname, FileMode.Open, FileAccess.Read);
123 reader = GetReader (source, sname, useSourcePath);
125 dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
126 writer = GetWriter (dest, dname);
129 foreach (DictionaryEntry e in reader) {
131 object val = e.Value;
133 writer.AddResource ((string)e.Key, (string)e.Value);
135 writer.AddResource ((string)e.Key, e.Value);
137 Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
141 Console.WriteLine("Writing resource file... Done.");
142 } catch (Exception e) {
143 Console.WriteLine ("Error: {0}", e.Message);
144 Exception inner = e.InnerException;
146 // under 2.0 ResXResourceReader can wrap an exception into an XmlException
147 // and this hides some helpful message from the original exception
148 XmlException xex = (inner as XmlException);
150 // message is identical to the inner exception (from MWF ResXResourceReader)
151 Console.WriteLine ("Position: Line {0}, Column {1}.", xex.LineNumber, xex.LinePosition);
152 inner = inner.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) || (platform == 6));
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)
426 int ch = int.Parse (value.Substring (++i, 4), NumberStyles.HexNumber);
427 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;