* certmgr.cs: Fixed reflection magic to look for ServerCertificates in
[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 #if NET_2_0
43                 string Usage = @"Mono Resource Generator version " + Consts.MonoVersion +
44                     @" for the 2.0 profile
45 Usage:
46                 resgen2 source.ext [dest.ext]
47                 resgen2 [options] /compile source.ext[,dest.resources] [...]";
48 #else
49                 string Usage = @"Mono Resource Generator version " + Consts.MonoVersion +
50                     @" for the 1.0 profile
51 Usage:
52                 resgen source.ext [dest.ext]
53                 resgen /compile source.ext[,dest.resources] [...]";
54 #endif
55                 Usage += @"
56
57 Convert a resource file from one format to another.
58 The currently supported formats are: '.txt' '.resources' '.resx' '.po'.
59 If the destination file is not specified, source.resources will be used.";
60 #if NET_2_0
61                 Usage += @"
62
63 Options:
64 -compile, /compile
65         takes a list of .resX or .txt files to convert to .resources files
66         in one bulk operation, replacing .ext with .resources for the 
67         output file name (if not set).
68 -usesourcepath, /useSourcePath
69         to resolve relative file paths, use the directory of the resource 
70         file as current directory.";
71 #else
72                 Usage += @"
73 The /compile option takes a list of .resX or .txt files to convert to
74 .resources files in one bulk operation, replacing .ext with .resources for
75 the output file name (if not set).";
76 #endif
77                 Usage += @"
78 ";
79                 Console.WriteLine( Usage );
80         }
81         
82         static IResourceReader GetReader (Stream stream, string name, bool useSourcePath) {
83                 string format = Path.GetExtension (name);
84                 switch (format.ToLower (System.Globalization.CultureInfo.InvariantCulture)) {
85                 case ".po":
86                         return new PoResourceReader (stream);
87                 case ".txt":
88                 case ".text":
89                         return new TxtResourceReader (stream);
90                 case ".resources":
91                         return new ResourceReader (stream);
92                 case ".resx":
93                         LoadResX ();
94                         IResourceReader reader = (IResourceReader) Activator.CreateInstance (
95                                 resxr, new object[] {stream});
96                         if (useSourcePath) { // only possible on 2.0 profile, or higher
97                                 PropertyInfo p = reader.GetType ().GetProperty ("BasePath",
98                                         BindingFlags.Public | BindingFlags.Instance);
99                                 if (p != null && p.CanWrite) {
100                                         p.SetValue (reader, Path.GetDirectoryName (name), null);
101                                 }
102                         }
103                         return reader;
104                 default:
105                         throw new Exception ("Unknown format in file " + name);
106                 }
107         }
108         
109         static IResourceWriter GetWriter (Stream stream, string name) {
110                 string format = Path.GetExtension (name);
111                 switch (format.ToLower ()) {
112                 case ".po":
113                         return new PoResourceWriter (stream);
114                 case ".txt":
115                 case ".text":
116                         return new TxtResourceWriter (stream);
117                 case ".resources":
118                         return new ResourceWriter (stream);
119                 case ".resx":
120                         LoadResX ();
121                         return (IResourceWriter)Activator.CreateInstance (resxw, new object[] {stream});
122                 default:
123                         throw new Exception ("Unknown format in file " + name);
124                 }
125         }
126         
127         static int CompileResourceFile (string sname, string dname, bool useSourcePath) {
128                 FileStream source = null;
129                 FileStream dest = null;
130                 IResourceReader reader = null;
131                 IResourceWriter writer = null;
132
133                 try {
134                         source = new FileStream (sname, FileMode.Open, FileAccess.Read);
135                         reader = GetReader (source, sname, useSourcePath);
136
137                         dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
138                         writer = GetWriter (dest, dname);
139
140                         int rescount = 0;
141                         foreach (DictionaryEntry e in reader) {
142                                 rescount++;
143                                 object val = e.Value;
144                                 if (val is string)
145                                         writer.AddResource ((string)e.Key, (string)e.Value);
146                                 else
147                                         writer.AddResource ((string)e.Key, e.Value);
148                         }
149                         Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
150
151                         reader.Close ();
152                         writer.Close ();
153                         Console.WriteLine("Writing resource file...  Done.");
154                 } catch (Exception e) {
155                         Console.WriteLine ("Error: {0}", e.Message);
156                         Exception inner = e.InnerException;
157                         if (inner is TargetInvocationException && inner.InnerException != null)
158                                 inner = inner.InnerException;
159                         if (inner != null)
160                                 Console.WriteLine ("Inner exception: {0}", inner.Message);
161
162                         if (reader != null)
163                                 reader.Dispose ();
164                         if (source != null)
165                                 source.Close ();
166                         if (writer != null)
167                                 writer.Dispose ();
168                         if (dest != null)
169                                 dest.Close ();
170
171                         // since we're not first reading all entries in source, we may get a
172                         // read failure after we're started writing to the destination file
173                         // and leave behind a broken resources file, so remove it here
174                         try {
175                                 File.Delete (dname);
176                         } catch {
177                         }
178                         return 1;
179                 }
180                 return 0;
181         }
182         
183         static int Main (string[] args) {
184                 bool compileMultiple = false;
185                 bool useSourcePath = false;
186                 ArrayList inputFiles = new ArrayList ();
187
188                 for (int i = 0; i < args.Length; i++) {
189                         switch (args [i].ToLower ()) {
190                         case "-h":
191                         case "/h":
192                         case "-?":
193                         case "/?":
194                                 Usage ();
195                                 return 1;
196                         case "/compile":
197                         case "-compile":
198                                 if (inputFiles.Count > 0) {
199                                         // the /compile option should be specified before any files
200                                         Usage ();
201                                         return 1;
202                                 }
203                                 compileMultiple = true;
204                                 break;
205 #if NET_2_0
206                         case "/usesourcepath":
207                         case "-usesourcepath":
208                                 if (compileMultiple) {
209                                         // the /usesourcepath option should not appear after the
210                                         // /compile switch on the command-line
211                                         Console.WriteLine ("ResGen : error RG0000: Invalid "
212                                                 + "command line syntax.  Switch: \"/compile\"  Bad value: "
213                                                 + args [i] + ".  Use ResGen /? for usage information.");
214                                         return 1;
215                                 }
216                                 useSourcePath = true;
217                                 break;
218 #endif
219                         default:
220                                 if (!IsFileArgument (args [i])) {
221                                         Usage ();
222                                         return 1;
223                                 }
224
225                                 ResourceInfo resInf = new ResourceInfo ();
226                                 if (compileMultiple) {
227                                         string [] pair = args [i].Split (',');
228                                         switch (pair.Length) {
229                                         case 1:
230                                                 resInf.InputFile = Path.GetFullPath (pair [0]);
231                                                 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
232                                                         "resources");
233                                                 break;
234                                         case 2:
235                                                 if (pair [1].Length == 0) {
236                                                         Console.WriteLine (@"error: You must specify an input & outfile file name like this:");
237                                                         Console.WriteLine ("inFile.txt,outFile.resources.");
238                                                         Console.WriteLine ("You passed in '{0}'.", args [i]);
239                                                         return 1;
240                                                 }
241                                                 resInf.InputFile = Path.GetFullPath (pair [0]);
242                                                 resInf.OutputFile = Path.GetFullPath (pair [1]);
243                                                 break;
244                                         default:
245                                                 Usage ();
246                                                 return 1;
247                                         }
248                                 } else {
249                                         if ((i + 1) < args.Length) {
250                                                 resInf.InputFile = Path.GetFullPath (args [i]);
251                                                 // move to next arg, since we assume that one holds
252                                                 // the name of the output file
253                                                 i++;
254                                                 resInf.OutputFile = Path.GetFullPath (args [i]);
255                                         } else {
256                                                 resInf.InputFile = Path.GetFullPath (args [i]);
257                                                 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
258                                                         "resources");
259                                         }
260                                 }
261                                 inputFiles.Add (resInf);
262                                 break;
263                         }
264                 }
265
266                 if (inputFiles.Count == 0) {
267                         Usage ();
268                         return 1;
269                 }
270
271                 foreach (ResourceInfo res in inputFiles) {
272                         int ret = CompileResourceFile (res.InputFile, res.OutputFile, useSourcePath);
273                         if (ret != 0 )
274                                 return ret;
275                 }
276                 return 0;
277         }
278
279         private static bool RunningOnUnix {
280                 get {
281                         // check for Unix platforms - see FAQ for more details
282                         // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
283                         int platform = (int) Environment.OSVersion.Platform;
284                         return ((platform == 4) || (platform == 128));
285                 }
286         }
287
288         private static bool IsFileArgument (string arg)
289         {
290                 if ((arg [0] != '-') && (arg [0] != '/'))
291                         return true;
292
293                 // cope with absolute filenames for resx files on unix, as
294                 // they also match the option pattern
295                 //
296                 // `/home/test.resx' is considered as a resx file, however
297                 // '/test.resx' is considered as error
298                 return (RunningOnUnix && arg.Length > 2 && arg.IndexOf ('/', 2) != -1);
299         }
300 }
301
302 class TxtResourceWriter : IResourceWriter {
303         StreamWriter s;
304         
305         public TxtResourceWriter (Stream stream) {
306                 s = new StreamWriter (stream);
307         }
308         
309         public void AddResource (string name, byte[] value) {
310                 throw new Exception ("Binary data not valid in a text resource file");
311         }
312         
313         public void AddResource (string name, object value) {
314                 if (value is string) {
315                         AddResource (name, (string)value);
316                         return;
317                 }
318                 throw new Exception ("Objects not valid in a text resource file");
319         }
320         
321         public void AddResource (string name, string value) {
322                 s.WriteLine ("{0}={1}", name, Escape (value));
323         }
324
325         // \n -> \\n ...
326         static string Escape (string value)
327         {
328                 StringBuilder b = new StringBuilder ();
329                 for (int i = 0; i < value.Length; i++) {
330                         switch (value [i]) {
331                         case '\n':
332                                 b.Append ("\\n");
333                                 break;
334                         case '\r':
335                                 b.Append ("\\r");
336                                 break;
337                         case '\t':
338                                 b.Append ("\\t");
339                                 break;
340                         case '\\':
341                                 b.Append ("\\\\");
342                                 break;
343                         default:
344                                 b.Append (value [i]);
345                                 break;
346                         }
347                 }
348                 return b.ToString ();
349         }
350         
351         public void Close () {
352                 s.Close ();
353         }
354         
355         public void Dispose () {}
356         
357         public void Generate () {}
358 }
359
360 class TxtResourceReader : IResourceReader {
361         Hashtable data;
362         Stream s;
363         
364         public TxtResourceReader (Stream stream) {
365                 data = new Hashtable ();
366                 s = stream;
367                 Load ();
368         }
369         
370         public virtual void Close () {
371         }
372         
373         public IDictionaryEnumerator GetEnumerator() {
374                 return data.GetEnumerator ();
375         }
376         
377         void Load () {
378                 StreamReader reader = new StreamReader (s);
379                 string line, key, val;
380                 int epos, line_num = 0;
381                 while ((line = reader.ReadLine ()) != null) {
382                         line_num++;
383                         line = line.Trim ();
384                         if (line.Length == 0 || line [0] == '#' ||
385                             line [0] == ';')
386                                 continue;
387                         epos = line.IndexOf ('=');
388                         if (epos < 0) 
389                                 throw new Exception ("Invalid format at line " + line_num);
390                         key = line.Substring (0, epos);
391                         val = line.Substring (epos + 1);
392                         key = key.Trim ();
393                         val = val.Trim ();
394                         if (key.Length == 0) 
395                                 throw new Exception ("Key is empty at line " + line_num);
396
397                         val = Unescape (val);
398                         if (val == null)
399                                 throw new Exception (String.Format ("Unsupported escape character in value of key '{0}'.", key));
400
401
402                         data.Add (key, val);
403                 }
404         }
405
406         // \\n -> \n ...
407         static string Unescape (string value)
408         {
409                 StringBuilder b = new StringBuilder ();
410
411                 for (int i = 0; i < value.Length; i++) {
412                         if (value [i] == '\\') {
413                                 if (i == value.Length - 1)
414                                         return null;
415
416                                 i++;
417                                 switch (value [i]) {
418                                 case 'n':
419                                         b.Append ('\n');
420                                         break;
421                                 case 'r':
422                                         b.Append ('\r');
423                                         break;
424                                 case 't':
425                                         b.Append ('\t');
426                                         break;
427 #if NET_2_0
428                                 case 'u':
429                                         int ch = int.Parse (value.Substring (++i, 4), NumberStyles.HexNumber);
430                                         b.Append (char.ConvertFromUtf32 (ch));
431                                         i += 3;
432                                         break;
433 #endif
434                                 case '\\':
435                                         b.Append ('\\');
436                                         break;
437                                 default:
438                                         return null;
439                                 }
440
441                         } else {
442                                 b.Append (value [i]);
443                         }
444                 }
445
446                 return b.ToString ();
447         }
448         
449         IEnumerator IEnumerable.GetEnumerator () {
450                 return ((IResourceReader) this).GetEnumerator();
451         }
452
453         void IDisposable.Dispose () {}
454 }
455
456 class PoResourceReader : IResourceReader {
457         Hashtable data;
458         Stream s;
459         int line_num;
460         
461         public PoResourceReader (Stream stream)
462         {
463                 data = new Hashtable ();
464                 s = stream;
465                 Load ();
466         }
467         
468         public virtual void Close ()
469         {
470                 s.Close ();
471         }
472         
473         public IDictionaryEnumerator GetEnumerator()
474         {
475                 return data.GetEnumerator ();
476         }
477         
478         string GetValue (string line)
479         {
480                 int begin = line.IndexOf ('"');
481                 if (begin == -1)
482                         throw new FormatException (String.Format ("No begin quote at line {0}: {1}", line_num, line));
483
484                 int end = line.LastIndexOf ('"');
485                 if (end == -1)
486                         throw new FormatException (String.Format ("No closing quote at line {0}: {1}", line_num, line));
487
488                 return line.Substring (begin + 1, end - begin - 1);
489         }
490         
491         void Load ()
492         {
493                 StreamReader reader = new StreamReader (s);
494                 string line;
495                 string msgid = null;
496                 string msgstr = null;
497                 bool ignoreNext = false;
498
499                 while ((line = reader.ReadLine ()) != null) {
500                         line_num++;
501                         line = line.Trim ();
502                         if (line.Length == 0)
503                                 continue;
504                                 
505                         if (line [0] == '#') {
506                                 if (line.Length == 1 || line [1] != ',')
507                                         continue;
508
509                                 if (line.IndexOf ("fuzzy") != -1) {
510                                         ignoreNext = true;
511                                         if (msgid != null) {
512                                                 if (msgstr == null)
513                                                         throw new FormatException ("Error. Line: " + line_num);
514                                                 data.Add (msgid, msgstr);
515                                                 msgid = null;
516                                                 msgstr = null;
517                                         }
518                                 }
519                                 continue;
520                         }
521                         
522                         if (line.StartsWith ("msgid ")) {
523                                 if (msgid == null && msgstr != null)
524                                         throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num);
525
526                                 if (msgstr != null) {
527                                         if (!ignoreNext)
528                                                 data.Add (msgid, msgstr);
529
530                                         ignoreNext = false;
531                                         msgid = null;
532                                         msgstr = null;
533                                 }
534
535                                 msgid = GetValue (line);
536                                 continue;
537                         }
538
539                         if (line.StartsWith ("msgstr ")) {
540                                 if (msgid == null)
541                                         throw new FormatException ("msgstr with no msgid. Line: " + line_num);
542
543                                 msgstr = GetValue (line);
544                                 continue;
545                         }
546
547                         if (line [0] == '"') {
548                                 if (msgid == null || msgstr == null)
549                                         throw new FormatException ("Invalid format. Line: " + line_num);
550
551                                 msgstr += GetValue (line);
552                                 continue;
553                         }
554
555                         throw new FormatException ("Unexpected data. Line: " + line_num);
556                 }
557
558                 if (msgid != null) {
559                         if (msgstr == null)
560                                 throw new FormatException ("Expecting msgstr. Line: " + line_num);
561
562                         if (!ignoreNext)
563                                 data.Add (msgid, msgstr);
564                 }
565         }
566         
567         IEnumerator IEnumerable.GetEnumerator ()
568         {
569                 return GetEnumerator();
570         }
571
572         void IDisposable.Dispose ()
573         {
574                 if (data != null)
575                         data = null;
576
577                 if (s != null) {
578                         s.Close ();
579                         s = null;
580                 }
581         }
582 }
583
584 class PoResourceWriter : IResourceWriter
585 {
586         TextWriter s;
587         bool headerWritten;
588         
589         public PoResourceWriter (Stream stream)
590         {
591                 s = new StreamWriter (stream);
592         }
593         
594         public void AddResource (string name, byte [] value)
595         {
596                 throw new InvalidOperationException ("Binary data not valid in a po resource file");
597         }
598         
599         public void AddResource (string name, object value)
600         {
601                 if (value is string) {
602                         AddResource (name, (string) value);
603                         return;
604                 }
605                 throw new InvalidOperationException ("Objects not valid in a po resource file");
606         }
607
608         StringBuilder ebuilder = new StringBuilder ();
609         
610         public string Escape (string ns)
611         {
612                 ebuilder.Length = 0;
613
614                 foreach (char c in ns){
615                         switch (c){
616                         case '"':
617                         case '\\':
618                                 ebuilder.Append ('\\');
619                                 ebuilder.Append (c);
620                                 break;
621                         case '\a':
622                                 ebuilder.Append ("\\a");
623                                 break;
624                         case '\n':
625                                 ebuilder.Append ("\\n");
626                                 break;
627                         case '\r':
628                                 ebuilder.Append ("\\r");
629                                 break;
630                         default:
631                                 ebuilder.Append (c);
632                                 break;
633                         }
634                 }
635                 return ebuilder.ToString ();
636         }
637         
638         public void AddResource (string name, string value)
639         {
640                 if (!headerWritten) {
641                         headerWritten = true;
642                         WriteHeader ();
643                 }
644                 
645                 s.WriteLine ("msgid \"{0}\"", Escape (name));
646                 s.WriteLine ("msgstr \"{0}\"", Escape (value));
647                 s.WriteLine ("");
648         }
649         
650         void WriteHeader ()
651         {
652                 s.WriteLine ("msgid \"\"");
653                 s.WriteLine ("msgstr \"\"");
654                 s.WriteLine ("\"MIME-Version: 1.0\\n\"");
655                 s.WriteLine ("\"Content-Type: text/plain; charset=UTF-8\\n\"");
656                 s.WriteLine ("\"Content-Transfer-Encoding: 8bit\\n\"");
657                 s.WriteLine ("\"X-Generator: Mono resgen 0.1\\n\"");
658                 s.WriteLine ("#\"Project-Id-Version: FILLME\\n\"");
659                 s.WriteLine ("#\"POT-Creation-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
660                 s.WriteLine ("#\"PO-Revision-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
661                 s.WriteLine ("#\"Last-Translator: FILLME\\n\"");
662                 s.WriteLine ("#\"Language-Team: FILLME\\n\"");
663                 s.WriteLine ("#\"Report-Msgid-Bugs-To: \\n\"");
664                 s.WriteLine ();
665         }
666
667         public void Close ()
668         {
669                 s.Close ();
670         }
671         
672         public void Dispose () { }
673         
674         public void Generate () {}
675 }
676
677 class ResourceInfo
678 {
679         public string InputFile;
680         public string OutputFile;
681 }