2 * resgen: convert between the resource formats (.txt, .resources, .resx).
4 * Copyright (c) 2002 Ximian, Inc
7 * Paolo Molaro (lupus@ximian.com)
8 * Gonzalo Paniagua Javier (gonzalo@ximian.com)
12 using System.Globalization;
15 using System.Collections;
16 using System.Collections.Generic;
17 using System.Resources;
18 using System.Reflection;
26 static HashSet<string> symbols = new HashSet<string> ();
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 () {
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 () {
46 string Usage = @"Mono Resource Generator version " + Consts.MonoVersion +
49 resgen source.ext [dest.ext]
50 resgen [options] /compile source.ext[,dest.resources] [...]";
53 Convert a resource file from one format to another.
54 The currently supported formats are: '.txt' '.resources' '.resx' '.po'.
55 If the destination file is not specified, source.resources will be used.";
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, /useSourcePath
65 to resolve relative file paths, use the directory of the resource
66 file as current directory.
67 -define, /define:SYMBOL1,SYMBOL2
68 takes a comma-separated list of symbols that control conditional
69 inclusion of resources file. The source file needs to be in
72 Resources enclosed with #ifdef SYMBOL1 ... #endif directives
73 will be included in the destination file when SYMBOL1 has
74 been specified using /define option.
76 Resources enclosed with #if ! SYMBOL2 ... #endif directives
77 will be included only if SYMBOL2 has NOT been specified.";
80 Console.WriteLine( Usage );
83 static IResourceReader GetReader (Stream stream, string name, bool useSourcePath) {
84 string format = Path.GetExtension (name);
85 switch (format.ToLower (System.Globalization.CultureInfo.InvariantCulture)) {
87 return new PoResourceReader (stream);
90 return new TxtResourceReader (stream, symbols);
92 return new ResourceReader (stream);
95 IResourceReader reader = (IResourceReader) Activator.CreateInstance (
96 resxr, new object[] {stream});
97 if (useSourcePath) { // only possible on 2.0 profile, or higher
98 PropertyInfo p = reader.GetType ().GetProperty ("BasePath",
99 BindingFlags.Public | BindingFlags.Instance);
100 if (p != null && p.CanWrite) {
101 p.SetValue (reader, Path.GetDirectoryName (name), null);
106 throw new Exception ("Unknown format in file " + name);
110 static IResourceWriter GetWriter (Stream stream, string name) {
111 string format = Path.GetExtension (name);
112 switch (format.ToLower ()) {
114 return new PoResourceWriter (stream);
117 return new TxtResourceWriter (stream);
119 return new ResourceWriter (stream);
122 return (IResourceWriter)Activator.CreateInstance (resxw, new object[] {stream});
124 throw new Exception ("Unknown format in file " + name);
128 static int CompileResourceFile (string sname, string dname, bool useSourcePath) {
129 FileStream source = null;
130 FileStream dest = null;
131 IResourceReader reader = null;
132 IResourceWriter writer = null;
135 source = new FileStream (sname, FileMode.Open, FileAccess.Read);
136 reader = GetReader (source, sname, useSourcePath);
138 dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
139 writer = GetWriter (dest, dname);
142 foreach (DictionaryEntry e in reader) {
144 object val = e.Value;
146 writer.AddResource ((string)e.Key, (string)e.Value);
148 writer.AddResource ((string)e.Key, e.Value);
150 Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
154 Console.WriteLine("Writing resource file... Done.");
155 } catch (Exception e) {
156 Console.WriteLine ("Error: {0}", e.Message);
157 Exception inner = e.InnerException;
159 // under 2.0 ResXResourceReader can wrap an exception into an XmlException
160 // and this hides some helpful message from the original exception
161 XmlException xex = (inner as XmlException);
163 // message is identical to the inner exception (from MWF ResXResourceReader)
164 Console.WriteLine ("Position: Line {0}, Column {1}.", xex.LineNumber, xex.LinePosition);
165 inner = inner.InnerException;
168 if (inner is TargetInvocationException && inner.InnerException != null)
169 inner = inner.InnerException;
171 Console.WriteLine ("Inner exception: {0}", inner.Message);
182 // since we're not first reading all entries in source, we may get a
183 // read failure after we're started writing to the destination file
184 // and leave behind a broken resources file, so remove it here
194 static int Main (string[] args) {
195 bool compileMultiple = false;
196 bool useSourcePath = false;
197 ArrayList inputFiles = new ArrayList ();
199 for (int i = 0; i < args.Length; i++) {
200 switch (args [i].ToLower ()) {
209 if (inputFiles.Count > 0) {
210 // the /compile option should be specified before any files
214 compileMultiple = true;
217 case "/usesourcepath":
218 case "-usesourcepath":
219 if (compileMultiple) {
220 // the /usesourcepath option should not appear after the
221 // /compile switch on the command-line
222 Console.WriteLine ("ResGen : error RG0000: Invalid "
223 + "command line syntax. Switch: \"/compile\" Bad value: "
224 + args [i] + ". Use ResGen /? for usage information.");
227 useSourcePath = true;
231 if (args [i].StartsWith ("/d:") ||
232 args [i].StartsWith ("-d:") ||
233 args [i].StartsWith ("/define:") ||
234 args [i].StartsWith ("-define:") ||
235 args [i].StartsWith ("/D:") ||
236 args [i].StartsWith ("-D:") ||
237 args [i].StartsWith ("/DEFINE:") ||
238 args [i].StartsWith ("-DEFINE:")) {
240 string defines = args [i].Substring (args [i].IndexOf (':') + 1);
241 foreach (string s in defines.Split (',') ) {
247 if (!IsFileArgument (args [i])) {
252 ResourceInfo resInf = new ResourceInfo ();
253 if (compileMultiple) {
254 string [] pair = args [i].Split (',');
255 switch (pair.Length) {
257 resInf.InputFile = Path.GetFullPath (pair [0]);
258 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
262 if (pair [1].Length == 0) {
263 Console.WriteLine (@"error: You must specify an input & outfile file name like this:");
264 Console.WriteLine ("inFile.txt,outFile.resources.");
265 Console.WriteLine ("You passed in '{0}'.", args [i]);
268 resInf.InputFile = Path.GetFullPath (pair [0]);
269 resInf.OutputFile = Path.GetFullPath (pair [1]);
276 if ((i + 1) < args.Length) {
277 resInf.InputFile = Path.GetFullPath (args [i]);
278 // move to next arg, since we assume that one holds
279 // the name of the output file
281 resInf.OutputFile = Path.GetFullPath (args [i]);
283 resInf.InputFile = Path.GetFullPath (args [i]);
284 resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
288 inputFiles.Add (resInf);
293 if (inputFiles.Count == 0) {
298 foreach (ResourceInfo res in inputFiles) {
299 int ret = CompileResourceFile (res.InputFile, res.OutputFile, useSourcePath);
306 private static bool RunningOnUnix {
308 // check for Unix platforms - see FAQ for more details
309 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
310 int platform = (int) Environment.OSVersion.Platform;
311 return ((platform == 4) || (platform == 128) || (platform == 6));
315 private static bool IsFileArgument (string arg)
317 if ((arg [0] != '-') && (arg [0] != '/'))
320 // cope with absolute filenames for resx files on unix, as
321 // they also match the option pattern
323 // `/home/test.resx' is considered as a resx file, however
324 // '/test.resx' is considered as error
325 return (RunningOnUnix && arg.Length > 2 && arg.IndexOf ('/', 2) != -1);
329 class TxtResourceWriter : IResourceWriter {
332 public TxtResourceWriter (Stream stream) {
333 s = new StreamWriter (stream);
336 public void AddResource (string name, byte[] value) {
337 throw new Exception ("Binary data not valid in a text resource file");
340 public void AddResource (string name, object value) {
341 if (value is string) {
342 AddResource (name, (string)value);
345 throw new Exception ("Objects not valid in a text resource file");
348 public void AddResource (string name, string value) {
349 s.WriteLine ("{0}={1}", name, Escape (value));
353 static string Escape (string value)
355 StringBuilder b = new StringBuilder ();
356 for (int i = 0; i < value.Length; i++) {
371 b.Append (value [i]);
375 return b.ToString ();
378 public void Close () {
382 public void Dispose () {}
384 public void Generate () {}
387 class TxtResourceReader : IResourceReader {
390 HashSet <String> defines;
392 public TxtResourceReader (Stream stream, IEnumerable<string> symbols) {
393 data = new Hashtable ();
396 defines = new HashSet<String> (symbols);
400 public virtual void Close () {
403 public IDictionaryEnumerator GetEnumerator() {
404 return data.GetEnumerator ();
407 static string NextWord(ref string line) {
410 line = line.TrimStart ();
411 for (i = 0; i < line.Length && !Char.IsWhiteSpace (line [i]) && line [i] != ';'; i++ );
413 if (i < line.Length) {
414 for (j = i; j < line.Length && Char.IsWhiteSpace (line [j]) && line [j] != ';'; j++ );
416 keywd = line.Substring (0, i);
417 line = line.Substring (j).TrimStart ();
426 StreamReader reader = new StreamReader (s);
427 string line, key, val;
428 Stack<bool> conditional = new Stack<bool> (5);
429 int epos, line_num = 0;
431 conditional.Push(true);
432 while ((line = reader.ReadLine ()) != null) {
436 if (line.Length == 0 || line [0] == ';')
439 if (line [0] == '#') {
442 string keywd, symbol;
444 line = line.Substring (1);
445 keywd = NextWord (ref line).ToLower ();
448 if (line.Length > 0) {
449 if (line[0] == '!') {
450 line = line.Substring (1);
453 symbol = NextWord (ref line);
459 stat = conditional.Pop ();
460 if (conditional.Count < 1)
461 throw new Exception (String.Format ("Found an #{0} without matching #ifdef", keywd));
464 conditional.Push (conditional.Peek () && !stat);
469 if (symbol.Length == 0)
470 throw new Exception (String.Format ("Missing symbol after {0}", keywd));
471 stat = defines.Contains (symbol);
475 conditional.Push (conditional.Peek () && stat);
481 if (conditional.Peek () == false)
484 epos = line.IndexOf ('=');
486 throw new Exception ("Invalid format at line " + line_num);
487 key = line.Substring (0, epos);
488 val = line.Substring (epos + 1);
492 throw new Exception ("Key is empty at line " + line_num);
494 val = Unescape (val);
496 throw new Exception (String.Format ("Unsupported escape character in value of key '{0}'.", key));
501 if (conditional.Count > 1)
502 throw new Exception ("Found an #ifdef but not a matching #endif before reaching the end of the file.");
506 static string Unescape (string value)
508 StringBuilder b = new StringBuilder ();
510 for (int i = 0; i < value.Length; i++) {
511 if (value [i] == '\\') {
512 if (i == value.Length - 1)
527 int ch = int.Parse (value.Substring (++i, 4), NumberStyles.HexNumber);
528 b.Append (char.ConvertFromUtf32 (ch));
539 b.Append (value [i]);
543 return b.ToString ();
546 IEnumerator IEnumerable.GetEnumerator () {
547 return ((IResourceReader) this).GetEnumerator();
550 void IDisposable.Dispose () {}
553 class PoResourceReader : IResourceReader {
558 public PoResourceReader (Stream stream)
560 data = new Hashtable ();
565 public virtual void Close ()
570 public IDictionaryEnumerator GetEnumerator()
572 return data.GetEnumerator ();
575 string GetValue (string line)
577 int begin = line.IndexOf ('"');
579 throw new FormatException (String.Format ("No begin quote at line {0}: {1}", line_num, line));
581 int end = line.LastIndexOf ('"');
583 throw new FormatException (String.Format ("No closing quote at line {0}: {1}", line_num, line));
585 return line.Substring (begin + 1, end - begin - 1);
590 StreamReader reader = new StreamReader (s);
593 string msgstr = null;
594 bool ignoreNext = false;
596 while ((line = reader.ReadLine ()) != null) {
599 if (line.Length == 0)
602 if (line [0] == '#') {
603 if (line.Length == 1 || line [1] != ',')
606 if (line.IndexOf ("fuzzy") != -1) {
610 throw new FormatException ("Error. Line: " + line_num);
611 data.Add (msgid, msgstr);
619 if (line.StartsWith ("msgid ")) {
620 if (msgid == null && msgstr != null)
621 throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num);
623 if (msgstr != null) {
625 data.Add (msgid, msgstr);
632 msgid = GetValue (line);
636 if (line.StartsWith ("msgstr ")) {
638 throw new FormatException ("msgstr with no msgid. Line: " + line_num);
640 msgstr = GetValue (line);
644 if (line [0] == '"') {
645 if (msgid == null || msgstr == null)
646 throw new FormatException ("Invalid format. Line: " + line_num);
648 msgstr += GetValue (line);
652 throw new FormatException ("Unexpected data. Line: " + line_num);
657 throw new FormatException ("Expecting msgstr. Line: " + line_num);
660 data.Add (msgid, msgstr);
664 IEnumerator IEnumerable.GetEnumerator ()
666 return GetEnumerator();
669 void IDisposable.Dispose ()
681 class PoResourceWriter : IResourceWriter
686 public PoResourceWriter (Stream stream)
688 s = new StreamWriter (stream);
691 public void AddResource (string name, byte [] value)
693 throw new InvalidOperationException ("Binary data not valid in a po resource file");
696 public void AddResource (string name, object value)
698 if (value is string) {
699 AddResource (name, (string) value);
702 throw new InvalidOperationException ("Objects not valid in a po resource file");
705 StringBuilder ebuilder = new StringBuilder ();
707 public string Escape (string ns)
711 foreach (char c in ns){
715 ebuilder.Append ('\\');
719 ebuilder.Append ("\\a");
722 ebuilder.Append ("\\n");
725 ebuilder.Append ("\\r");
732 return ebuilder.ToString ();
735 public void AddResource (string name, string value)
737 if (!headerWritten) {
738 headerWritten = true;
742 s.WriteLine ("msgid \"{0}\"", Escape (name));
743 s.WriteLine ("msgstr \"{0}\"", Escape (value));
749 s.WriteLine ("msgid \"\"");
750 s.WriteLine ("msgstr \"\"");
751 s.WriteLine ("\"MIME-Version: 1.0\\n\"");
752 s.WriteLine ("\"Content-Type: text/plain; charset=UTF-8\\n\"");
753 s.WriteLine ("\"Content-Transfer-Encoding: 8bit\\n\"");
754 s.WriteLine ("\"X-Generator: Mono resgen 0.1\\n\"");
755 s.WriteLine ("#\"Project-Id-Version: FILLME\\n\"");
756 s.WriteLine ("#\"POT-Creation-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
757 s.WriteLine ("#\"PO-Revision-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
758 s.WriteLine ("#\"Last-Translator: FILLME\\n\"");
759 s.WriteLine ("#\"Language-Team: FILLME\\n\"");
760 s.WriteLine ("#\"Report-Msgid-Bugs-To: \\n\"");
769 public void Dispose () { }
771 public void Generate () {}
776 public string InputFile;
777 public string OutputFile;