2008-05-05 Zoltan Varga <vargaz@gmail.com>
[mcs.git] / tools / resgen / monoresgen.cs
blob973825698cd8395a6707b60bee19943d56294aa2
1 /*
2 * resgen: convert between the resource formats (.txt, .resources, .resx).
4 * Copyright (c) 2002 Ximian, Inc
6 * Authors:
7 * Paolo Molaro (lupus@ximian.com)
8 * Gonzalo Paniagua Javier (gonzalo@ximian.com)
9 */
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;
19 class ResGen {
21 static Assembly swf;
22 static Type resxr;
23 static Type resxw;
26 * We load the ResX format stuff on demand, since the classes are in
27 * System.Windows.Forms (!!!) and we can't depend on that assembly in mono, yet.
29 static void LoadResX () {
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);
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 += @"
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 += @"
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 += @"
79 Console.WriteLine( Usage );
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);
103 return reader;
104 default:
105 throw new Exception ("Unknown format in file " + name);
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);
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;
133 try {
134 source = new FileStream (sname, FileMode.Open, FileAccess.Read);
135 reader = GetReader (source, sname, useSourcePath);
137 dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
138 writer = GetWriter (dest, dname);
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);
149 Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
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);
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 ();
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 {
178 return 1;
180 return 0;
183 static int Main (string[] args) {
184 bool compileMultiple = false;
185 bool useSourcePath = false;
186 ArrayList inputFiles = new ArrayList ();
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;
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;
216 useSourcePath = true;
217 break;
218 #endif
219 default:
220 if (!IsFileArgument (args [i])) {
221 Usage ();
222 return 1;
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;
241 resInf.InputFile = Path.GetFullPath (pair [0]);
242 resInf.OutputFile = Path.GetFullPath (pair [1]);
243 break;
244 default:
245 Usage ();
246 return 1;
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");
261 inputFiles.Add (resInf);
262 break;
266 if (inputFiles.Count == 0) {
267 Usage ();
268 return 1;
271 foreach (ResourceInfo res in inputFiles) {
272 int ret = CompileResourceFile (res.InputFile, res.OutputFile, useSourcePath);
273 if (ret != 0 )
274 return ret;
276 return 0;
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));
288 private static bool IsFileArgument (string arg)
290 if ((arg [0] != '-') && (arg [0] != '/'))
291 return true;
293 // cope with absolute filenames for resx files on unix, as
294 // they also match the option pattern
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);
302 class TxtResourceWriter : IResourceWriter {
303 StreamWriter s;
305 public TxtResourceWriter (Stream stream) {
306 s = new StreamWriter (stream);
309 public void AddResource (string name, byte[] value) {
310 throw new Exception ("Binary data not valid in a text resource file");
313 public void AddResource (string name, object value) {
314 if (value is string) {
315 AddResource (name, (string)value);
316 return;
318 throw new Exception ("Objects not valid in a text resource file");
321 public void AddResource (string name, string value) {
322 s.WriteLine ("{0}={1}", name, Escape (value));
325 // \n -> \\n ...
326 static string Escape (string value)
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;
348 return b.ToString ();
351 public void Close () {
352 s.Close ();
355 public void Dispose () {}
357 public void Generate () {}
360 class TxtResourceReader : IResourceReader {
361 Hashtable data;
362 Stream s;
364 public TxtResourceReader (Stream stream) {
365 data = new Hashtable ();
366 s = stream;
367 Load ();
370 public virtual void Close () {
373 public IDictionaryEnumerator GetEnumerator() {
374 return data.GetEnumerator ();
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);
397 val = Unescape (val);
398 if (val == null)
399 throw new Exception (String.Format ("Unsupported escape character in value of key '{0}'.", key));
402 data.Add (key, val);
406 // \\n -> \n ...
407 static string Unescape (string value)
409 StringBuilder b = new StringBuilder ();
411 for (int i = 0; i < value.Length; i++) {
412 if (value [i] == '\\') {
413 if (i == value.Length - 1)
414 return null;
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;
441 } else {
442 b.Append (value [i]);
446 return b.ToString ();
449 IEnumerator IEnumerable.GetEnumerator () {
450 return ((IResourceReader) this).GetEnumerator();
453 void IDisposable.Dispose () {}
456 class PoResourceReader : IResourceReader {
457 Hashtable data;
458 Stream s;
459 int line_num;
461 public PoResourceReader (Stream stream)
463 data = new Hashtable ();
464 s = stream;
465 Load ();
468 public virtual void Close ()
470 s.Close ();
473 public IDictionaryEnumerator GetEnumerator()
475 return data.GetEnumerator ();
478 string GetValue (string line)
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));
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));
488 return line.Substring (begin + 1, end - begin - 1);
491 void Load ()
493 StreamReader reader = new StreamReader (s);
494 string line;
495 string msgid = null;
496 string msgstr = null;
497 bool ignoreNext = false;
499 while ((line = reader.ReadLine ()) != null) {
500 line_num++;
501 line = line.Trim ();
502 if (line.Length == 0)
503 continue;
505 if (line [0] == '#') {
506 if (line.Length == 1 || line [1] != ',')
507 continue;
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;
519 continue;
522 if (line.StartsWith ("msgid ")) {
523 if (msgid == null && msgstr != null)
524 throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num);
526 if (msgstr != null) {
527 if (!ignoreNext)
528 data.Add (msgid, msgstr);
530 ignoreNext = false;
531 msgid = null;
532 msgstr = null;
535 msgid = GetValue (line);
536 continue;
539 if (line.StartsWith ("msgstr ")) {
540 if (msgid == null)
541 throw new FormatException ("msgstr with no msgid. Line: " + line_num);
543 msgstr = GetValue (line);
544 continue;
547 if (line [0] == '"') {
548 if (msgid == null || msgstr == null)
549 throw new FormatException ("Invalid format. Line: " + line_num);
551 msgstr += GetValue (line);
552 continue;
555 throw new FormatException ("Unexpected data. Line: " + line_num);
558 if (msgid != null) {
559 if (msgstr == null)
560 throw new FormatException ("Expecting msgstr. Line: " + line_num);
562 if (!ignoreNext)
563 data.Add (msgid, msgstr);
567 IEnumerator IEnumerable.GetEnumerator ()
569 return GetEnumerator();
572 void IDisposable.Dispose ()
574 if (data != null)
575 data = null;
577 if (s != null) {
578 s.Close ();
579 s = null;
584 class PoResourceWriter : IResourceWriter
586 TextWriter s;
587 bool headerWritten;
589 public PoResourceWriter (Stream stream)
591 s = new StreamWriter (stream);
594 public void AddResource (string name, byte [] value)
596 throw new InvalidOperationException ("Binary data not valid in a po resource file");
599 public void AddResource (string name, object value)
601 if (value is string) {
602 AddResource (name, (string) value);
603 return;
605 throw new InvalidOperationException ("Objects not valid in a po resource file");
608 StringBuilder ebuilder = new StringBuilder ();
610 public string Escape (string ns)
612 ebuilder.Length = 0;
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;
635 return ebuilder.ToString ();
638 public void AddResource (string name, string value)
640 if (!headerWritten) {
641 headerWritten = true;
642 WriteHeader ();
645 s.WriteLine ("msgid \"{0}\"", Escape (name));
646 s.WriteLine ("msgstr \"{0}\"", Escape (value));
647 s.WriteLine ("");
650 void WriteHeader ()
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 ();
667 public void Close ()
669 s.Close ();
672 public void Dispose () { }
674 public void Generate () {}
677 class ResourceInfo
679 public string InputFile;
680 public string OutputFile;