2004-06-08 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / monoresgen / monoresgen.cs
1 /*
2  * monoresgen: 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 /*
12  * TODO:
13  * * escape/unescape in the .txt reader/writer to be able to roundtrip values with newlines
14  *   (unlike the MS ResGen utility)
15  */
16
17 using System;
18 using System.Text;
19 using System.IO;
20 using System.Collections;
21 using System.Resources;
22 using System.Reflection;
23
24 class ResGen {
25
26         static Assembly swf;
27         static Type resxr;
28         static Type resxw;
29
30         /*
31          * We load the ResX format stuff on demand, since the classes are in 
32          * System.Windows.Forms (!!!) and we can't depend on that assembly in mono, yet.
33          */
34         static void LoadResX () {
35                 if (swf != null)
36                         return;
37                 try {
38                         swf = Assembly.Load (Consts.AssemblySystem_Windows_Forms);
39                         resxr = swf.GetType ("System.Resources.ResXResourceReader");
40                         resxw = swf.GetType ("System.Resources.ResXResourceWriter");
41                 } catch (Exception e) {
42                         throw new Exception ("Cannot load support for ResX format: " + e.Message);
43                 }
44         }
45
46         static void Usage () {
47                 string Usage = @"Mono Resource Generator version 0.1
48 Usage:
49                 monoresgen source.ext [dest.ext]
50                 monoresgen /compile source.ext[,dest.resources] [...]
51
52 Convert a resource file from one format to another.
53 The currently supported formats are: '.txt' '.resources' '.resx' '.po'.
54 If the destination file is not specified, source.resources will be used.
55 The /compile option takes a list of .resX or .txt files to convert to
56 .resources files in one bulk operation, replacing .ext with .resources for
57 the output file name.
58 ";
59                 Console.WriteLine( Usage );
60         }
61         
62         static IResourceReader GetReader (Stream stream, string name) {
63                 string format = Path.GetExtension (name);
64                 switch (format.ToLower ()) {
65                 case ".po":
66                         return new PoResourceReader (stream);
67                 case ".txt":
68                 case ".text":
69                         return new TxtResourceReader (stream);
70                 case ".resources":
71                         return new ResourceReader (stream);
72                 case ".resx":
73                         LoadResX ();
74                         return (IResourceReader)Activator.CreateInstance (resxr, new object[] {stream});
75                 default:
76                         throw new Exception ("Unknown format in file " + name);
77                 }
78         }
79         
80         static IResourceWriter GetWriter (Stream stream, string name) {
81                 string format = Path.GetExtension (name);
82                 switch (format.ToLower ()) {
83                 case ".po":
84                         return new PoResourceWriter (stream);
85                 case ".txt":
86                 case ".text":
87                         return new TxtResourceWriter (stream);
88                 case ".resources":
89                         return new ResourceWriter (stream);
90                 case ".resx":
91                         LoadResX ();
92                         return (IResourceWriter)Activator.CreateInstance (resxw, new object[] {stream});
93                 default:
94                         throw new Exception ("Unknown format in file " + name);
95                 }
96         }
97         
98         static int CompileResourceFile(string sname, string dname ) {
99                 FileStream source, dest;
100                 IResourceReader reader;
101                 IResourceWriter writer;
102
103                 try {
104                         source = new FileStream (sname, FileMode.Open, FileAccess.Read);
105
106                         reader = GetReader (source, sname);
107
108                         dest = new FileStream (dname, FileMode.OpenOrCreate, FileAccess.Write);
109                         writer = GetWriter (dest, dname);
110
111                         int rescount = 0;
112                         foreach (DictionaryEntry e in reader) {
113                                 rescount++;
114                                 object val = e.Value;
115                                 if (val is string)
116                                         writer.AddResource ((string)e.Key, (string)e.Value);
117                                 else
118                                         writer.AddResource ((string)e.Key, e.Value);
119                         }
120                         Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
121
122                         reader.Close ();
123                         writer.Close ();
124                         Console.WriteLine("Writing resource file...  Done.");
125                 } catch (Exception e) {
126                         Console.WriteLine ("Error: {0}", e.Message);
127                         Exception inner = e.InnerException;
128                         if (inner != null)
129                                 Console.WriteLine ("Inner exception: {0}", inner.Message);
130                         return 1;
131                 }
132                 return 0;
133         }
134         
135         static int Main (string[] args) {
136                 string sname = "", dname = ""; 
137                 if ((int) args.Length < 1 || args[0] == "-h" || args[0] == "-?" || args[0] == "/h" || args[0] == "/?") {
138                           Usage();
139                           return 1;
140                 }               
141                 if (args[0] == "/compile" || args[0] == "-compile") {
142                         for ( int i=1; i< args.Length; i++ ) {                          
143                                 if ( args[i].IndexOf(",") != -1 ){
144                                         string[] pair =  args[i].Split(',');
145                                         sname = pair[0]; 
146                                         dname = pair[1];
147                                         if (dname == ""){
148                                                 Console.WriteLine(@"error: You must specify an input & outfile file name like this:");
149                                                 Console.WriteLine("inFile.txt,outFile.resources." );
150                                                 Console.WriteLine("You passed in '{0}'.", args[i] );
151                                                 return 1;
152                                         }
153                                 } else {
154                                         sname = args[i]; 
155                                         dname = Path.ChangeExtension (sname, "resources");
156                                 }
157                                 int ret = CompileResourceFile( sname, dname );
158                                 if (ret != 0 ) {
159                                         return ret;
160                                 }
161                         }
162                         return 0;
163                 
164                 }
165                 else if (args.Length == 1) {
166                         sname = args [0];
167                         dname = Path.ChangeExtension (sname, "resources");
168                 } else if (args.Length != 2) {
169                         Usage ();
170                         return 1;
171                 } else {
172                         sname = args [0];
173                         dname = args [1];                       
174                 }               
175                 return CompileResourceFile( sname, dname );
176         }
177 }
178
179 class TxtResourceWriter : IResourceWriter {
180         StreamWriter s;
181         
182         public TxtResourceWriter (Stream stream) {
183                 s = new StreamWriter (stream);
184         }
185         
186         public void AddResource (string name, byte[] value) {
187                 throw new Exception ("Binary data not valid in a text resource file");
188         }
189         
190         public void AddResource (string name, object value) {
191                 if (value is string) {
192                         AddResource (name, (string)value);
193                         return;
194                 }
195                 throw new Exception ("Objects not valid in a text resource file");
196         }
197         
198         /* FIXME: handle newlines */
199         public void AddResource (string name, string value) {
200                 s.WriteLine ("{0}={1}", name, value);
201         }
202         
203         public void Close () {
204                 s.Close ();
205         }
206         
207         public void Dispose () {}
208         
209         public void Generate () {}
210 }
211
212 class TxtResourceReader : IResourceReader {
213         Hashtable data;
214         Stream s;
215         
216         public TxtResourceReader (Stream stream) {
217                 data = new Hashtable ();
218                 s = stream;
219                 Load ();
220         }
221         
222         public virtual void Close () {
223         }
224         
225         public IDictionaryEnumerator GetEnumerator() {
226                 return data.GetEnumerator ();
227         }
228         
229         void Load () {
230                 StreamReader reader = new StreamReader (s);
231                 string line, key, val;
232                 int epos, line_num = 0;
233                 while ((line = reader.ReadLine ()) != null) {
234                         line_num++;
235                         line = line.Trim ();
236                         if (line.Length == 0 || line [0] == '#' ||
237                             line [0] == ';')
238                                 continue;
239                         epos = line.IndexOf ('=');
240                         if (epos < 0) 
241                                 throw new Exception ("Invalid format at line " + line_num);
242                         key = line.Substring (0, epos);
243                         val = line.Substring (epos + 1);
244                         key = key.Trim ();
245                         val = val.Trim ();
246                         if (key.Length == 0) 
247                                 throw new Exception ("Key is empty at line " + line_num);
248                         data.Add (key, val);
249                 }
250         }
251         
252         IEnumerator IEnumerable.GetEnumerator () {
253                 return ((IResourceReader) this).GetEnumerator();
254         }
255
256         void IDisposable.Dispose () {}
257 }
258
259 class PoResourceReader : IResourceReader {
260         Hashtable data;
261         Stream s;
262         int line_num;
263         
264         public PoResourceReader (Stream stream)
265         {
266                 data = new Hashtable ();
267                 s = stream;
268                 Load ();
269         }
270         
271         public virtual void Close ()
272         {
273                 s.Close ();
274         }
275         
276         public IDictionaryEnumerator GetEnumerator()
277         {
278                 return data.GetEnumerator ();
279         }
280         
281         string GetValue (string line)
282         {
283                 int begin = line.IndexOf ('"');
284                 if (begin == -1)
285                         throw new FormatException (String.Format ("No begin quote at line {0}: {1}", line_num, line));
286
287                 int end = line.LastIndexOf ('"');
288                 if (end == -1)
289                         throw new FormatException (String.Format ("No closing quote at line {0}: {1}", line_num, line));
290
291                 return line.Substring (begin + 1, end - begin - 1);
292         }
293         
294         void Load ()
295         {
296                 StreamReader reader = new StreamReader (s);
297                 string line;
298                 string msgid = null;
299                 string msgstr = null;
300                 bool ignoreNext = false;
301
302                 while ((line = reader.ReadLine ()) != null) {
303                         line_num++;
304                         line = line.Trim ();
305                         if (line.Length == 0)
306                                 continue;
307                                 
308                         if (line [0] == '#') {
309                                 if (line.Length == 1 || line [1] != ',')
310                                         continue;
311
312                                 if (line.IndexOf ("fuzzy") != -1) {
313                                         ignoreNext = true;
314                                         if (msgid != null) {
315                                                 if (msgstr == null)
316                                                         throw new FormatException ("Error. Line: " + line_num);
317                                                 data.Add (msgid, msgstr);
318                                                 msgid = null;
319                                                 msgstr = null;
320                                         }
321                                 }
322                                 continue;
323                         }
324                         
325                         if (line.StartsWith ("msgid ")) {
326                                 if (msgid == null && msgstr != null)
327                                         throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num);
328
329                                 if (msgstr != null) {
330                                         if (!ignoreNext)
331                                                 data.Add (msgid, msgstr);
332
333                                         ignoreNext = false;
334                                         msgid = null;
335                                         msgstr = null;
336                                 }
337
338                                 msgid = GetValue (line);
339                                 continue;
340                         }
341
342                         if (line.StartsWith ("msgstr ")) {
343                                 if (msgid == null)
344                                         throw new FormatException ("msgstr with no msgid. Line: " + line_num);
345
346                                 msgstr = GetValue (line);
347                                 continue;
348                         }
349
350                         if (line [0] == '"') {
351                                 if (msgid == null || msgstr == null)
352                                         throw new FormatException ("Invalid format. Line: " + line_num);
353
354                                 msgstr += GetValue (line);
355                                 continue;
356                         }
357
358                         throw new FormatException ("Unexpected data. Line: " + line_num);
359                 }
360
361                 if (msgid != null) {
362                         if (msgstr == null)
363                                 throw new FormatException ("Expecting msgstr. Line: " + line_num);
364
365                         if (!ignoreNext)
366                                 data.Add (msgid, msgstr);
367                 }
368         }
369         
370         IEnumerator IEnumerable.GetEnumerator ()
371         {
372                 return GetEnumerator();
373         }
374
375         void IDisposable.Dispose ()
376         {
377                 if (data != null)
378                         data = null;
379
380                 if (s != null) {
381                         s.Close ();
382                         s = null;
383                 }
384         }
385 }
386
387 class PoResourceWriter : IResourceWriter
388 {
389         TextWriter s;
390         bool headerWritten;
391         
392         public PoResourceWriter (Stream stream)
393         {
394                 s = new StreamWriter (stream);
395         }
396         
397         public void AddResource (string name, byte [] value)
398         {
399                 throw new InvalidOperationException ("Binary data not valid in a po resource file");
400         }
401         
402         public void AddResource (string name, object value)
403         {
404                 if (value is string) {
405                         AddResource (name, (string) value);
406                         return;
407                 }
408                 throw new InvalidOperationException ("Objects not valid in a po resource file");
409         }
410
411         StringBuilder ebuilder = new StringBuilder ();
412         
413         public string Escape (string ns)
414         {
415                 ebuilder.Length = 0;
416
417                 foreach (char c in ns){
418                         switch (c){
419                         case '"':
420                         case '\\':
421                                 ebuilder.Append ('\\');
422                                 ebuilder.Append (c);
423                                 break;
424                         case '\a':
425                                 ebuilder.Append ("\\a");
426                                 break;
427                         case '\n':
428                                 ebuilder.Append ("\\n");
429                                 break;
430                         case '\r':
431                                 ebuilder.Append ("\\r");
432                                 break;
433                         default:
434                                 ebuilder.Append (c);
435                                 break;
436                         }
437                 }
438                 return ebuilder.ToString ();
439         }
440         
441         public void AddResource (string name, string value)
442         {
443                 if (!headerWritten) {
444                         headerWritten = true;
445                         WriteHeader ();
446                 }
447                 
448                 s.WriteLine ("msgid \"{0}\"", Escape (name));
449                 s.WriteLine ("msgstr \"{0}\"", Escape (value));
450                 s.WriteLine ("");
451         }
452         
453         void WriteHeader ()
454         {
455                 s.WriteLine ("msgid \"\"");
456                 s.WriteLine ("msgstr \"\"");
457                 s.WriteLine ("\"MIME-Version: 1.0\\n\"");
458                 s.WriteLine ("\"Content-Type: text/plain; charset=UTF-8\\n\"");
459                 s.WriteLine ("\"Content-Transfer-Encoding: 8bit\\n\"");
460                 s.WriteLine ("\"X-Generator: monoresgen 0.1\\n\"");
461                 s.WriteLine ("#\"Project-Id-Version: FILLME\\n\"");
462                 s.WriteLine ("#\"POT-Creation-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
463                 s.WriteLine ("#\"PO-Revision-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
464                 s.WriteLine ("#\"Last-Translator: FILLME\\n\"");
465                 s.WriteLine ("#\"Language-Team: FILLME\\n\"");
466                 s.WriteLine ("#\"Report-Msgid-Bugs-To: \\n\"");
467                 s.WriteLine ();
468         }
469
470         public void Close ()
471         {
472                 s.Close ();
473         }
474         
475         public void Dispose () { }
476         
477         public void Generate () {}
478 }
479