Really remove unused variable.
[mono.git] / mcs / tools / resgen / monoresgen.cs
1 /*
2  * resgen: convert between the resource formats (.txt, .resources, .resx).
3  *
4  * Copyright (c) 2002 Ximian, Inc
5  *
6  * Authors:
7  *      Paolo Molaro (lupus@ximian.com)
8  *      Gonzalo Paniagua Javier (gonzalo@ximian.com)
9  */
10
11 using System;
12 using System.Globalization;
13 using System.Text;
14 using System.IO;
15 using System.Collections;
16 using System.Resources;
17 using System.Reflection;
18
19 class ResGen {
20
21         static Assembly swf;
22         static Type resxr;
23         static Type resxw;
24
25         /*
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.
28          */
29         static void LoadResX () {
30                 if (swf != null)
31                         return;
32                 try {
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);
38                 }
39         }
40
41         static void Usage () {
42                 string Usage = @"Mono Resource Generator version 0.1
43 Usage:
44                 resgen source.ext [dest.ext]";
45 #if NET_2_0
46                 Usage += @"
47                 resgen [options] /compile source.ext[,dest.resources] [...]";
48 #else
49                 Usage += @"
50                 resgen /compile source.ext[,dest.resources] [...]";
51 #endif
52                 Usage += @"
53
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.";
57 #if NET_2_0
58                 Usage += @"
59 Options:
60 /compile
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).
64 /useSourcePath
65         to resolve relative file paths, use the directory of the resource 
66         file as current directory.";
67 #else
68                 Usage += @"
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).";
72 #endif
73                 Usage += @"
74 ";
75                 Console.WriteLine( Usage );
76         }
77         
78         static IResourceReader GetReader (Stream stream, string name, bool useSourcePath) {
79                 string format = Path.GetExtension (name);
80                 switch (format.ToLower (System.Globalization.CultureInfo.InvariantCulture)) {
81                 case ".po":
82                         return new PoResourceReader (stream);
83                 case ".txt":
84                 case ".text":
85                         return new TxtResourceReader (stream);
86                 case ".resources":
87                         return new ResourceReader (stream);
88                 case ".resx":
89                         LoadResX ();
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);
97                                 }
98                         }
99                         return reader;
100                 default:
101                         throw new Exception ("Unknown format in file " + name);
102                 }
103         }
104         
105         static IResourceWriter GetWriter (Stream stream, string name) {
106                 string format = Path.GetExtension (name);
107                 switch (format.ToLower ()) {
108                 case ".po":
109                         return new PoResourceWriter (stream);
110                 case ".txt":
111                 case ".text":
112                         return new TxtResourceWriter (stream);
113                 case ".resources":
114                         return new ResourceWriter (stream);
115                 case ".resx":
116                         LoadResX ();
117                         return (IResourceWriter)Activator.CreateInstance (resxw, new object[] {stream});
118                 default:
119                         throw new Exception ("Unknown format in file " + name);
120                 }
121         }
122         
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;
128
129                 try {
130                         source = new FileStream (sname, FileMode.Open, FileAccess.Read);
131                         reader = GetReader (source, sname, useSourcePath);
132
133                         dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
134                         writer = GetWriter (dest, dname);
135
136                         int rescount = 0;
137                         foreach (DictionaryEntry e in reader) {
138                                 rescount++;
139                                 object val = e.Value;
140                                 if (val is string)
141                                         writer.AddResource ((string)e.Key, (string)e.Value);
142                                 else
143                                         writer.AddResource ((string)e.Key, e.Value);
144                         }
145                         Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
146
147                         reader.Close ();
148                         writer.Close ();
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;
155                         if (inner != null)
156                                 Console.WriteLine ("Inner exception: {0}", inner.Message);
157
158                         if (reader != null)
159                                 reader.Dispose ();
160                         if (source != null)
161                                 source.Close ();
162                         if (writer != null)
163                                 writer.Dispose ();
164                         if (dest != null)
165                                 dest.Close ();
166
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
170                         try {
171                                 File.Delete (dname);
172                         } catch {
173                         }
174                         return 1;
175                 }
176                 return 0;
177         }
178         
179         static int Main (string[] args) {
180                 bool compileMultiple = false;
181                 bool useSourcePath = false;
182                 ArrayList inputFiles = new ArrayList ();
183
184                 for (int i = 0; i < args.Length; i++) {
185                         switch (args [i].ToLower ()) {
186                         case "-h":
187                         case "/h":
188                         case "-?":
189                         case "/?":
190                                 Usage ();
191                                 return 1;
192                         case "/compile":
193                         case "-compile":
194                                 if (inputFiles.Count > 0) {
195                                         // the /compile option should be specified before any files
196                                         Usage ();
197                                         return 1;
198                                 }
199                                 compileMultiple = true;
200                                 break;
201 #if NET_2_0
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.");
210                                         return 1;
211                                 }
212                                 useSourcePath = true;
213                                 break;
214 #endif
215                         default:
216                                 if (!IsFileArgument (args [i])) {
217                                         Usage ();
218                                         return 1;
219                                 }
220
221                                 ResourceInfo resInf = new ResourceInfo ();
222                                 if (compileMultiple) {
223                                         string [] pair = args [i].Split (',');
224                                         switch (pair.Length) {
225                                         case 1:
226                                                 resInf.InputFile = Path.GetFullPath (pair [0]);
227                                                 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
228                                                         "resources");
229                                                 break;
230                                         case 2:
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]);
235                                                         return 1;
236                                                 }
237                                                 resInf.InputFile = Path.GetFullPath (pair [0]);
238                                                 resInf.OutputFile = Path.GetFullPath (pair [1]);
239                                                 break;
240                                         default:
241                                                 Usage ();
242                                                 return 1;
243                                         }
244                                 } else {
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
249                                                 i++;
250                                                 resInf.OutputFile = Path.GetFullPath (args [i]);
251                                         } else {
252                                                 resInf.InputFile = Path.GetFullPath (args [i]);
253                                                 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
254                                                         "resources");
255                                         }
256                                 }
257                                 inputFiles.Add (resInf);
258                                 break;
259                         }
260                 }
261
262                 if (inputFiles.Count == 0) {
263                         Usage ();
264                         return 1;
265                 }
266
267                 foreach (ResourceInfo res in inputFiles) {
268                         int ret = CompileResourceFile (res.InputFile, res.OutputFile, useSourcePath);
269                         if (ret != 0 )
270                                 return ret;
271                 }
272                 return 0;
273         }
274
275         private static bool RunningOnUnix {
276                 get {
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));
281                 }
282         }
283
284         private static bool IsFileArgument (string arg)
285         {
286                 if ((arg [0] != '-') && (arg [0] != '/'))
287                         return true;
288
289                 // cope with absolute filenames for resx files on unix, as
290                 // they also match the option pattern
291                 //
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);
295         }
296 }
297
298 class TxtResourceWriter : IResourceWriter {
299         StreamWriter s;
300         
301         public TxtResourceWriter (Stream stream) {
302                 s = new StreamWriter (stream);
303         }
304         
305         public void AddResource (string name, byte[] value) {
306                 throw new Exception ("Binary data not valid in a text resource file");
307         }
308         
309         public void AddResource (string name, object value) {
310                 if (value is string) {
311                         AddResource (name, (string)value);
312                         return;
313                 }
314                 throw new Exception ("Objects not valid in a text resource file");
315         }
316         
317         public void AddResource (string name, string value) {
318                 s.WriteLine ("{0}={1}", name, Escape (value));
319         }
320
321         // \n -> \\n ...
322         static string Escape (string value)
323         {
324                 StringBuilder b = new StringBuilder ();
325                 for (int i = 0; i < value.Length; i++) {
326                         switch (value [i]) {
327                         case '\n':
328                                 b.Append ("\\n");
329                                 break;
330                         case '\r':
331                                 b.Append ("\\r");
332                                 break;
333                         case '\t':
334                                 b.Append ("\\t");
335                                 break;
336                         case '\\':
337                                 b.Append ("\\\\");
338                                 break;
339                         default:
340                                 b.Append (value [i]);
341                                 break;
342                         }
343                 }
344                 return b.ToString ();
345         }
346         
347         public void Close () {
348                 s.Close ();
349         }
350         
351         public void Dispose () {}
352         
353         public void Generate () {}
354 }
355
356 class TxtResourceReader : IResourceReader {
357         Hashtable data;
358         Stream s;
359         
360         public TxtResourceReader (Stream stream) {
361                 data = new Hashtable ();
362                 s = stream;
363                 Load ();
364         }
365         
366         public virtual void Close () {
367         }
368         
369         public IDictionaryEnumerator GetEnumerator() {
370                 return data.GetEnumerator ();
371         }
372         
373         void Load () {
374                 StreamReader reader = new StreamReader (s);
375                 string line, key, val;
376                 int epos, line_num = 0;
377                 while ((line = reader.ReadLine ()) != null) {
378                         line_num++;
379                         line = line.Trim ();
380                         if (line.Length == 0 || line [0] == '#' ||
381                             line [0] == ';')
382                                 continue;
383                         epos = line.IndexOf ('=');
384                         if (epos < 0) 
385                                 throw new Exception ("Invalid format at line " + line_num);
386                         key = line.Substring (0, epos);
387                         val = line.Substring (epos + 1);
388                         key = key.Trim ();
389                         val = val.Trim ();
390                         if (key.Length == 0) 
391                                 throw new Exception ("Key is empty at line " + line_num);
392
393                         val = Unescape (val);
394                         if (val == null)
395                                 throw new Exception (String.Format ("Unsupported escape character in value of key '{0}'.", key));
396
397
398                         data.Add (key, val);
399                 }
400         }
401
402         // \\n -> \n ...
403         static string Unescape (string value)
404         {
405                 StringBuilder b = new StringBuilder ();
406
407                 for (int i = 0; i < value.Length; i++) {
408                         if (value [i] == '\\') {
409                                 if (i == value.Length - 1)
410                                         return null;
411
412                                 i++;
413                                 switch (value [i]) {
414                                 case 'n':
415                                         b.Append ('\n');
416                                         break;
417                                 case 'r':
418                                         b.Append ('\r');
419                                         break;
420                                 case 't':
421                                         b.Append ('\t');
422                                         break;
423 #if NET_2_0
424                                 case 'u':
425                                         int ch = int.Parse (value.Substring (++i, 4), NumberStyles.HexNumber);
426                                         b.Append (char.ConvertFromUtf32 (ch));
427                                         i += 3;
428                                         break;
429 #endif
430                                 case '\\':
431                                         b.Append ('\\');
432                                         break;
433                                 default:
434                                         return null;
435                                 }
436
437                         } else {
438                                 b.Append (value [i]);
439                         }
440                 }
441
442                 return b.ToString ();
443         }
444         
445         IEnumerator IEnumerable.GetEnumerator () {
446                 return ((IResourceReader) this).GetEnumerator();
447         }
448
449         void IDisposable.Dispose () {}
450 }
451
452 class PoResourceReader : IResourceReader {
453         Hashtable data;
454         Stream s;
455         int line_num;
456         
457         public PoResourceReader (Stream stream)
458         {
459                 data = new Hashtable ();
460                 s = stream;
461                 Load ();
462         }
463         
464         public virtual void Close ()
465         {
466                 s.Close ();
467         }
468         
469         public IDictionaryEnumerator GetEnumerator()
470         {
471                 return data.GetEnumerator ();
472         }
473         
474         string GetValue (string line)
475         {
476                 int begin = line.IndexOf ('"');
477                 if (begin == -1)
478                         throw new FormatException (String.Format ("No begin quote at line {0}: {1}", line_num, line));
479
480                 int end = line.LastIndexOf ('"');
481                 if (end == -1)
482                         throw new FormatException (String.Format ("No closing quote at line {0}: {1}", line_num, line));
483
484                 return line.Substring (begin + 1, end - begin - 1);
485         }
486         
487         void Load ()
488         {
489                 StreamReader reader = new StreamReader (s);
490                 string line;
491                 string msgid = null;
492                 string msgstr = null;
493                 bool ignoreNext = false;
494
495                 while ((line = reader.ReadLine ()) != null) {
496                         line_num++;
497                         line = line.Trim ();
498                         if (line.Length == 0)
499                                 continue;
500                                 
501                         if (line [0] == '#') {
502                                 if (line.Length == 1 || line [1] != ',')
503                                         continue;
504
505                                 if (line.IndexOf ("fuzzy") != -1) {
506                                         ignoreNext = true;
507                                         if (msgid != null) {
508                                                 if (msgstr == null)
509                                                         throw new FormatException ("Error. Line: " + line_num);
510                                                 data.Add (msgid, msgstr);
511                                                 msgid = null;
512                                                 msgstr = null;
513                                         }
514                                 }
515                                 continue;
516                         }
517                         
518                         if (line.StartsWith ("msgid ")) {
519                                 if (msgid == null && msgstr != null)
520                                         throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num);
521
522                                 if (msgstr != null) {
523                                         if (!ignoreNext)
524                                                 data.Add (msgid, msgstr);
525
526                                         ignoreNext = false;
527                                         msgid = null;
528                                         msgstr = null;
529                                 }
530
531                                 msgid = GetValue (line);
532                                 continue;
533                         }
534
535                         if (line.StartsWith ("msgstr ")) {
536                                 if (msgid == null)
537                                         throw new FormatException ("msgstr with no msgid. Line: " + line_num);
538
539                                 msgstr = GetValue (line);
540                                 continue;
541                         }
542
543                         if (line [0] == '"') {
544                                 if (msgid == null || msgstr == null)
545                                         throw new FormatException ("Invalid format. Line: " + line_num);
546
547                                 msgstr += GetValue (line);
548                                 continue;
549                         }
550
551                         throw new FormatException ("Unexpected data. Line: " + line_num);
552                 }
553
554                 if (msgid != null) {
555                         if (msgstr == null)
556                                 throw new FormatException ("Expecting msgstr. Line: " + line_num);
557
558                         if (!ignoreNext)
559                                 data.Add (msgid, msgstr);
560                 }
561         }
562         
563         IEnumerator IEnumerable.GetEnumerator ()
564         {
565                 return GetEnumerator();
566         }
567
568         void IDisposable.Dispose ()
569         {
570                 if (data != null)
571                         data = null;
572
573                 if (s != null) {
574                         s.Close ();
575                         s = null;
576                 }
577         }
578 }
579
580 class PoResourceWriter : IResourceWriter
581 {
582         TextWriter s;
583         bool headerWritten;
584         
585         public PoResourceWriter (Stream stream)
586         {
587                 s = new StreamWriter (stream);
588         }
589         
590         public void AddResource (string name, byte [] value)
591         {
592                 throw new InvalidOperationException ("Binary data not valid in a po resource file");
593         }
594         
595         public void AddResource (string name, object value)
596         {
597                 if (value is string) {
598                         AddResource (name, (string) value);
599                         return;
600                 }
601                 throw new InvalidOperationException ("Objects not valid in a po resource file");
602         }
603
604         StringBuilder ebuilder = new StringBuilder ();
605         
606         public string Escape (string ns)
607         {
608                 ebuilder.Length = 0;
609
610                 foreach (char c in ns){
611                         switch (c){
612                         case '"':
613                         case '\\':
614                                 ebuilder.Append ('\\');
615                                 ebuilder.Append (c);
616                                 break;
617                         case '\a':
618                                 ebuilder.Append ("\\a");
619                                 break;
620                         case '\n':
621                                 ebuilder.Append ("\\n");
622                                 break;
623                         case '\r':
624                                 ebuilder.Append ("\\r");
625                                 break;
626                         default:
627                                 ebuilder.Append (c);
628                                 break;
629                         }
630                 }
631                 return ebuilder.ToString ();
632         }
633         
634         public void AddResource (string name, string value)
635         {
636                 if (!headerWritten) {
637                         headerWritten = true;
638                         WriteHeader ();
639                 }
640                 
641                 s.WriteLine ("msgid \"{0}\"", Escape (name));
642                 s.WriteLine ("msgstr \"{0}\"", Escape (value));
643                 s.WriteLine ("");
644         }
645         
646         void WriteHeader ()
647         {
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\"");
660                 s.WriteLine ();
661         }
662
663         public void Close ()
664         {
665                 s.Close ();
666         }
667         
668         public void Dispose () { }
669         
670         public void Generate () {}
671 }
672
673 class ResourceInfo
674 {
675         public string InputFile;
676         public string OutputFile;
677 }