in System.Collections.Generic:
[mcs.git] / tools / resgen / monoresgen.cs
blob39d62593fa67c9f790c31040233c2c8f6b595677
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;
18 #if NET_2_0
19 using System.Xml;
20 #endif
22 class ResGen {
24 static Assembly swf;
25 static Type resxr;
26 static Type resxw;
29 * We load the ResX format stuff on demand, since the classes are in
30 * System.Windows.Forms (!!!) and we can't depend on that assembly in mono, yet.
32 static void LoadResX () {
33 if (swf != null)
34 return;
35 try {
36 swf = Assembly.Load (Consts.AssemblySystem_Windows_Forms);
37 resxr = swf.GetType ("System.Resources.ResXResourceReader");
38 resxw = swf.GetType ("System.Resources.ResXResourceWriter");
39 } catch (Exception e) {
40 throw new Exception ("Cannot load support for ResX format: " + e.Message);
44 static void Usage () {
45 #if NET_2_0
46 string Usage = @"Mono Resource Generator version " + Consts.MonoVersion +
47 @" for the 2.0 profile
48 Usage:
49 resgen2 source.ext [dest.ext]
50 resgen2 [options] /compile source.ext[,dest.resources] [...]";
51 #else
52 string Usage = @"Mono Resource Generator version " + Consts.MonoVersion +
53 @" for the 1.0 profile
54 Usage:
55 resgen source.ext [dest.ext]
56 resgen /compile source.ext[,dest.resources] [...]";
57 #endif
58 Usage += @"
60 Convert a resource file from one format to another.
61 The currently supported formats are: '.txt' '.resources' '.resx' '.po'.
62 If the destination file is not specified, source.resources will be used.";
63 #if NET_2_0
64 Usage += @"
66 Options:
67 -compile, /compile
68 takes a list of .resX or .txt files to convert to .resources files
69 in one bulk operation, replacing .ext with .resources for the
70 output file name (if not set).
71 -usesourcepath, /useSourcePath
72 to resolve relative file paths, use the directory of the resource
73 file as current directory.";
74 #else
75 Usage += @"
76 The /compile option takes a list of .resX or .txt files to convert to
77 .resources files in one bulk operation, replacing .ext with .resources for
78 the output file name (if not set).";
79 #endif
80 Usage += @"
82 Console.WriteLine( Usage );
85 static IResourceReader GetReader (Stream stream, string name, bool useSourcePath) {
86 string format = Path.GetExtension (name);
87 switch (format.ToLower (System.Globalization.CultureInfo.InvariantCulture)) {
88 case ".po":
89 return new PoResourceReader (stream);
90 case ".txt":
91 case ".text":
92 return new TxtResourceReader (stream);
93 case ".resources":
94 return new ResourceReader (stream);
95 case ".resx":
96 LoadResX ();
97 IResourceReader reader = (IResourceReader) Activator.CreateInstance (
98 resxr, new object[] {stream});
99 if (useSourcePath) { // only possible on 2.0 profile, or higher
100 PropertyInfo p = reader.GetType ().GetProperty ("BasePath",
101 BindingFlags.Public | BindingFlags.Instance);
102 if (p != null && p.CanWrite) {
103 p.SetValue (reader, Path.GetDirectoryName (name), null);
106 return reader;
107 default:
108 throw new Exception ("Unknown format in file " + name);
112 static IResourceWriter GetWriter (Stream stream, string name) {
113 string format = Path.GetExtension (name);
114 switch (format.ToLower ()) {
115 case ".po":
116 return new PoResourceWriter (stream);
117 case ".txt":
118 case ".text":
119 return new TxtResourceWriter (stream);
120 case ".resources":
121 return new ResourceWriter (stream);
122 case ".resx":
123 LoadResX ();
124 return (IResourceWriter)Activator.CreateInstance (resxw, new object[] {stream});
125 default:
126 throw new Exception ("Unknown format in file " + name);
130 static int CompileResourceFile (string sname, string dname, bool useSourcePath) {
131 FileStream source = null;
132 FileStream dest = null;
133 IResourceReader reader = null;
134 IResourceWriter writer = null;
136 try {
137 source = new FileStream (sname, FileMode.Open, FileAccess.Read);
138 reader = GetReader (source, sname, useSourcePath);
140 dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
141 writer = GetWriter (dest, dname);
143 int rescount = 0;
144 foreach (DictionaryEntry e in reader) {
145 rescount++;
146 object val = e.Value;
147 if (val is string)
148 writer.AddResource ((string)e.Key, (string)e.Value);
149 else
150 writer.AddResource ((string)e.Key, e.Value);
152 Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
154 reader.Close ();
155 writer.Close ();
156 Console.WriteLine("Writing resource file... Done.");
157 } catch (Exception e) {
158 Console.WriteLine ("Error: {0}", e.Message);
159 Exception inner = e.InnerException;
160 #if NET_2_0
161 // under 2.0 ResXResourceReader can wrap an exception into an XmlException
162 // and this hides some helpful message from the original exception
163 XmlException xex = (inner as XmlException);
164 if (xex != null) {
165 // message is identical to the inner exception (from MWF ResXResourceReader)
166 Console.WriteLine ("Position: Line {0}, Column {1}.", xex.LineNumber, xex.LinePosition);
167 inner = inner.InnerException;
169 #endif
170 if (inner is TargetInvocationException && inner.InnerException != null)
171 inner = inner.InnerException;
172 if (inner != null)
173 Console.WriteLine ("Inner exception: {0}", inner.Message);
175 if (reader != null)
176 reader.Dispose ();
177 if (source != null)
178 source.Close ();
179 if (writer != null)
180 writer.Dispose ();
181 if (dest != null)
182 dest.Close ();
184 // since we're not first reading all entries in source, we may get a
185 // read failure after we're started writing to the destination file
186 // and leave behind a broken resources file, so remove it here
187 try {
188 File.Delete (dname);
189 } catch {
191 return 1;
193 return 0;
196 static int Main (string[] args) {
197 bool compileMultiple = false;
198 bool useSourcePath = false;
199 ArrayList inputFiles = new ArrayList ();
201 for (int i = 0; i < args.Length; i++) {
202 switch (args [i].ToLower ()) {
203 case "-h":
204 case "/h":
205 case "-?":
206 case "/?":
207 Usage ();
208 return 1;
209 case "/compile":
210 case "-compile":
211 if (inputFiles.Count > 0) {
212 // the /compile option should be specified before any files
213 Usage ();
214 return 1;
216 compileMultiple = true;
217 break;
218 #if NET_2_0
219 case "/usesourcepath":
220 case "-usesourcepath":
221 if (compileMultiple) {
222 // the /usesourcepath option should not appear after the
223 // /compile switch on the command-line
224 Console.WriteLine ("ResGen : error RG0000: Invalid "
225 + "command line syntax. Switch: \"/compile\" Bad value: "
226 + args [i] + ". Use ResGen /? for usage information.");
227 return 1;
229 useSourcePath = true;
230 break;
231 #endif
232 default:
233 if (!IsFileArgument (args [i])) {
234 Usage ();
235 return 1;
238 ResourceInfo resInf = new ResourceInfo ();
239 if (compileMultiple) {
240 string [] pair = args [i].Split (',');
241 switch (pair.Length) {
242 case 1:
243 resInf.InputFile = Path.GetFullPath (pair [0]);
244 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
245 "resources");
246 break;
247 case 2:
248 if (pair [1].Length == 0) {
249 Console.WriteLine (@"error: You must specify an input & outfile file name like this:");
250 Console.WriteLine ("inFile.txt,outFile.resources.");
251 Console.WriteLine ("You passed in '{0}'.", args [i]);
252 return 1;
254 resInf.InputFile = Path.GetFullPath (pair [0]);
255 resInf.OutputFile = Path.GetFullPath (pair [1]);
256 break;
257 default:
258 Usage ();
259 return 1;
261 } else {
262 if ((i + 1) < args.Length) {
263 resInf.InputFile = Path.GetFullPath (args [i]);
264 // move to next arg, since we assume that one holds
265 // the name of the output file
266 i++;
267 resInf.OutputFile = Path.GetFullPath (args [i]);
268 } else {
269 resInf.InputFile = Path.GetFullPath (args [i]);
270 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
271 "resources");
274 inputFiles.Add (resInf);
275 break;
279 if (inputFiles.Count == 0) {
280 Usage ();
281 return 1;
284 foreach (ResourceInfo res in inputFiles) {
285 int ret = CompileResourceFile (res.InputFile, res.OutputFile, useSourcePath);
286 if (ret != 0 )
287 return ret;
289 return 0;
292 private static bool RunningOnUnix {
293 get {
294 // check for Unix platforms - see FAQ for more details
295 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
296 int platform = (int) Environment.OSVersion.Platform;
297 return ((platform == 4) || (platform == 128) || (platform == 6));
301 private static bool IsFileArgument (string arg)
303 if ((arg [0] != '-') && (arg [0] != '/'))
304 return true;
306 // cope with absolute filenames for resx files on unix, as
307 // they also match the option pattern
309 // `/home/test.resx' is considered as a resx file, however
310 // '/test.resx' is considered as error
311 return (RunningOnUnix && arg.Length > 2 && arg.IndexOf ('/', 2) != -1);
315 class TxtResourceWriter : IResourceWriter {
316 StreamWriter s;
318 public TxtResourceWriter (Stream stream) {
319 s = new StreamWriter (stream);
322 public void AddResource (string name, byte[] value) {
323 throw new Exception ("Binary data not valid in a text resource file");
326 public void AddResource (string name, object value) {
327 if (value is string) {
328 AddResource (name, (string)value);
329 return;
331 throw new Exception ("Objects not valid in a text resource file");
334 public void AddResource (string name, string value) {
335 s.WriteLine ("{0}={1}", name, Escape (value));
338 // \n -> \\n ...
339 static string Escape (string value)
341 StringBuilder b = new StringBuilder ();
342 for (int i = 0; i < value.Length; i++) {
343 switch (value [i]) {
344 case '\n':
345 b.Append ("\\n");
346 break;
347 case '\r':
348 b.Append ("\\r");
349 break;
350 case '\t':
351 b.Append ("\\t");
352 break;
353 case '\\':
354 b.Append ("\\\\");
355 break;
356 default:
357 b.Append (value [i]);
358 break;
361 return b.ToString ();
364 public void Close () {
365 s.Close ();
368 public void Dispose () {}
370 public void Generate () {}
373 class TxtResourceReader : IResourceReader {
374 Hashtable data;
375 Stream s;
377 public TxtResourceReader (Stream stream) {
378 data = new Hashtable ();
379 s = stream;
380 Load ();
383 public virtual void Close () {
386 public IDictionaryEnumerator GetEnumerator() {
387 return data.GetEnumerator ();
390 void Load () {
391 StreamReader reader = new StreamReader (s);
392 string line, key, val;
393 int epos, line_num = 0;
394 while ((line = reader.ReadLine ()) != null) {
395 line_num++;
396 line = line.Trim ();
397 if (line.Length == 0 || line [0] == '#' ||
398 line [0] == ';')
399 continue;
400 epos = line.IndexOf ('=');
401 if (epos < 0)
402 throw new Exception ("Invalid format at line " + line_num);
403 key = line.Substring (0, epos);
404 val = line.Substring (epos + 1);
405 key = key.Trim ();
406 val = val.Trim ();
407 if (key.Length == 0)
408 throw new Exception ("Key is empty at line " + line_num);
410 val = Unescape (val);
411 if (val == null)
412 throw new Exception (String.Format ("Unsupported escape character in value of key '{0}'.", key));
415 data.Add (key, val);
419 // \\n -> \n ...
420 static string Unescape (string value)
422 StringBuilder b = new StringBuilder ();
424 for (int i = 0; i < value.Length; i++) {
425 if (value [i] == '\\') {
426 if (i == value.Length - 1)
427 return null;
429 i++;
430 switch (value [i]) {
431 case 'n':
432 b.Append ('\n');
433 break;
434 case 'r':
435 b.Append ('\r');
436 break;
437 case 't':
438 b.Append ('\t');
439 break;
440 #if NET_2_0
441 case 'u':
442 int ch = int.Parse (value.Substring (++i, 4), NumberStyles.HexNumber);
443 b.Append (char.ConvertFromUtf32 (ch));
444 i += 3;
445 break;
446 #endif
447 case '\\':
448 b.Append ('\\');
449 break;
450 default:
451 return null;
454 } else {
455 b.Append (value [i]);
459 return b.ToString ();
462 IEnumerator IEnumerable.GetEnumerator () {
463 return ((IResourceReader) this).GetEnumerator();
466 void IDisposable.Dispose () {}
469 class PoResourceReader : IResourceReader {
470 Hashtable data;
471 Stream s;
472 int line_num;
474 public PoResourceReader (Stream stream)
476 data = new Hashtable ();
477 s = stream;
478 Load ();
481 public virtual void Close ()
483 s.Close ();
486 public IDictionaryEnumerator GetEnumerator()
488 return data.GetEnumerator ();
491 string GetValue (string line)
493 int begin = line.IndexOf ('"');
494 if (begin == -1)
495 throw new FormatException (String.Format ("No begin quote at line {0}: {1}", line_num, line));
497 int end = line.LastIndexOf ('"');
498 if (end == -1)
499 throw new FormatException (String.Format ("No closing quote at line {0}: {1}", line_num, line));
501 return line.Substring (begin + 1, end - begin - 1);
504 void Load ()
506 StreamReader reader = new StreamReader (s);
507 string line;
508 string msgid = null;
509 string msgstr = null;
510 bool ignoreNext = false;
512 while ((line = reader.ReadLine ()) != null) {
513 line_num++;
514 line = line.Trim ();
515 if (line.Length == 0)
516 continue;
518 if (line [0] == '#') {
519 if (line.Length == 1 || line [1] != ',')
520 continue;
522 if (line.IndexOf ("fuzzy") != -1) {
523 ignoreNext = true;
524 if (msgid != null) {
525 if (msgstr == null)
526 throw new FormatException ("Error. Line: " + line_num);
527 data.Add (msgid, msgstr);
528 msgid = null;
529 msgstr = null;
532 continue;
535 if (line.StartsWith ("msgid ")) {
536 if (msgid == null && msgstr != null)
537 throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num);
539 if (msgstr != null) {
540 if (!ignoreNext)
541 data.Add (msgid, msgstr);
543 ignoreNext = false;
544 msgid = null;
545 msgstr = null;
548 msgid = GetValue (line);
549 continue;
552 if (line.StartsWith ("msgstr ")) {
553 if (msgid == null)
554 throw new FormatException ("msgstr with no msgid. Line: " + line_num);
556 msgstr = GetValue (line);
557 continue;
560 if (line [0] == '"') {
561 if (msgid == null || msgstr == null)
562 throw new FormatException ("Invalid format. Line: " + line_num);
564 msgstr += GetValue (line);
565 continue;
568 throw new FormatException ("Unexpected data. Line: " + line_num);
571 if (msgid != null) {
572 if (msgstr == null)
573 throw new FormatException ("Expecting msgstr. Line: " + line_num);
575 if (!ignoreNext)
576 data.Add (msgid, msgstr);
580 IEnumerator IEnumerable.GetEnumerator ()
582 return GetEnumerator();
585 void IDisposable.Dispose ()
587 if (data != null)
588 data = null;
590 if (s != null) {
591 s.Close ();
592 s = null;
597 class PoResourceWriter : IResourceWriter
599 TextWriter s;
600 bool headerWritten;
602 public PoResourceWriter (Stream stream)
604 s = new StreamWriter (stream);
607 public void AddResource (string name, byte [] value)
609 throw new InvalidOperationException ("Binary data not valid in a po resource file");
612 public void AddResource (string name, object value)
614 if (value is string) {
615 AddResource (name, (string) value);
616 return;
618 throw new InvalidOperationException ("Objects not valid in a po resource file");
621 StringBuilder ebuilder = new StringBuilder ();
623 public string Escape (string ns)
625 ebuilder.Length = 0;
627 foreach (char c in ns){
628 switch (c){
629 case '"':
630 case '\\':
631 ebuilder.Append ('\\');
632 ebuilder.Append (c);
633 break;
634 case '\a':
635 ebuilder.Append ("\\a");
636 break;
637 case '\n':
638 ebuilder.Append ("\\n");
639 break;
640 case '\r':
641 ebuilder.Append ("\\r");
642 break;
643 default:
644 ebuilder.Append (c);
645 break;
648 return ebuilder.ToString ();
651 public void AddResource (string name, string value)
653 if (!headerWritten) {
654 headerWritten = true;
655 WriteHeader ();
658 s.WriteLine ("msgid \"{0}\"", Escape (name));
659 s.WriteLine ("msgstr \"{0}\"", Escape (value));
660 s.WriteLine ("");
663 void WriteHeader ()
665 s.WriteLine ("msgid \"\"");
666 s.WriteLine ("msgstr \"\"");
667 s.WriteLine ("\"MIME-Version: 1.0\\n\"");
668 s.WriteLine ("\"Content-Type: text/plain; charset=UTF-8\\n\"");
669 s.WriteLine ("\"Content-Transfer-Encoding: 8bit\\n\"");
670 s.WriteLine ("\"X-Generator: Mono resgen 0.1\\n\"");
671 s.WriteLine ("#\"Project-Id-Version: FILLME\\n\"");
672 s.WriteLine ("#\"POT-Creation-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
673 s.WriteLine ("#\"PO-Revision-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
674 s.WriteLine ("#\"Last-Translator: FILLME\\n\"");
675 s.WriteLine ("#\"Language-Team: FILLME\\n\"");
676 s.WriteLine ("#\"Report-Msgid-Bugs-To: \\n\"");
677 s.WriteLine ();
680 public void Close ()
682 s.Close ();
685 public void Dispose () { }
687 public void Generate () {}
690 class ResourceInfo
692 public string InputFile;
693 public string OutputFile;