*/
using System;
+using System.Globalization;
using System.Text;
using System.IO;
using System.Collections;
+using System.Collections.Generic;
using System.Resources;
using System.Reflection;
+using System.Xml;
class ResGen {
static Assembly swf;
static Type resxr;
static Type resxw;
+ static HashSet<string> symbols = new HashSet<string> ();
/*
* We load the ResX format stuff on demand, since the classes are in
}
static void Usage () {
- string Usage = @"Mono Resource Generator version 0.1
+
+ string Usage = @"Mono Resource Generator version " + Consts.MonoVersion +
+ @"
Usage:
resgen source.ext [dest.ext]
- resgen /compile source.ext[,dest.resources] [...]
+ resgen [options] /compile source.ext[,dest.resources] [...]";
+ Usage += @"
Convert a resource file from one format to another.
The currently supported formats are: '.txt' '.resources' '.resx' '.po'.
-If the destination file is not specified, source.resources will be used.
-The /compile option takes a list of .resX or .txt files to convert to
-.resources files in one bulk operation, replacing .ext with .resources for
-the output file name.
+If the destination file is not specified, source.resources will be used.";
+
+ Usage += @"
+
+Options:
+-compile, /compile
+ takes a list of .resX or .txt files to convert to .resources files
+ in one bulk operation, replacing .ext with .resources for the
+ output file name (if not set).
+-usesourcepath, /useSourcePath
+ to resolve relative file paths, use the directory of the resource
+ file as current directory.
+-define, /define:SYMBOL1,SYMBOL2
+ takes a comma-separated list of symbols that control conditional
+ inclusion of resources file. The source file needs to be in
+ the '.txt' format.
+
+ Resources enclosed with #ifdef SYMBOL1 ... #endif directives
+ will be included in the destination file when SYMBOL1 has
+ been specified using /define option.
+
+ Resources enclosed with #if ! SYMBOL2 ... #endif directives
+ will be included only if SYMBOL2 has NOT been specified.";
+ Usage += @"
";
Console.WriteLine( Usage );
}
- static IResourceReader GetReader (Stream stream, string name) {
+ static IResourceReader GetReader (Stream stream, string name, bool useSourcePath) {
string format = Path.GetExtension (name);
- switch (format.ToLower ()) {
+ switch (format.ToLower (System.Globalization.CultureInfo.InvariantCulture)) {
case ".po":
return new PoResourceReader (stream);
case ".txt":
case ".text":
- return new TxtResourceReader (stream);
+ return new TxtResourceReader (stream, symbols);
case ".resources":
return new ResourceReader (stream);
case ".resx":
LoadResX ();
- return (IResourceReader)Activator.CreateInstance (resxr, new object[] {stream});
+ IResourceReader reader = (IResourceReader) Activator.CreateInstance (
+ resxr, new object[] {stream});
+ if (useSourcePath) { // only possible on 2.0 profile, or higher
+ PropertyInfo p = reader.GetType ().GetProperty ("BasePath",
+ BindingFlags.Public | BindingFlags.Instance);
+ if (p != null && p.CanWrite) {
+ p.SetValue (reader, Path.GetDirectoryName (name), null);
+ }
+ }
+ return reader;
default:
throw new Exception ("Unknown format in file " + name);
}
}
}
- static int CompileResourceFile(string sname, string dname ) {
- FileStream source, dest;
- IResourceReader reader;
- IResourceWriter writer;
+ static int CompileResourceFile (string sname, string dname, bool useSourcePath) {
+ FileStream source = null;
+ FileStream dest = null;
+ IResourceReader reader = null;
+ IResourceWriter writer = null;
try {
source = new FileStream (sname, FileMode.Open, FileAccess.Read);
-
- reader = GetReader (source, sname);
+ reader = GetReader (source, sname, useSourcePath);
dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
writer = GetWriter (dest, dname);
} catch (Exception e) {
Console.WriteLine ("Error: {0}", e.Message);
Exception inner = e.InnerException;
+
+ // under 2.0 ResXResourceReader can wrap an exception into an XmlException
+ // and this hides some helpful message from the original exception
+ XmlException xex = (inner as XmlException);
+ if (xex != null) {
+ // message is identical to the inner exception (from MWF ResXResourceReader)
+ Console.WriteLine ("Position: Line {0}, Column {1}.", xex.LineNumber, xex.LinePosition);
+ inner = inner.InnerException;
+ }
+
+ if (inner is TargetInvocationException && inner.InnerException != null)
+ inner = inner.InnerException;
if (inner != null)
Console.WriteLine ("Inner exception: {0}", inner.Message);
+
+ if (reader != null)
+ reader.Dispose ();
+ if (source != null)
+ source.Close ();
+ if (writer != null)
+ writer.Dispose ();
+ if (dest != null)
+ dest.Close ();
+
+ // since we're not first reading all entries in source, we may get a
+ // read failure after we're started writing to the destination file
+ // and leave behind a broken resources file, so remove it here
+ try {
+ File.Delete (dname);
+ } catch {
+ }
return 1;
}
return 0;
}
static int Main (string[] args) {
- string sname = "", dname = "";
- if ((int) args.Length < 1 || args[0] == "-h" || args[0] == "-?" || args[0] == "/h" || args[0] == "/?") {
- Usage();
- return 1;
- }
- if (args[0] == "/compile" || args[0] == "-compile") {
- for ( int i=1; i< args.Length; i++ ) {
- if ( args[i].IndexOf(",") != -1 ){
- string[] pair = args[i].Split(',');
- sname = pair[0];
- dname = pair[1];
- if (dname == ""){
- Console.WriteLine(@"error: You must specify an input & outfile file name like this:");
- Console.WriteLine("inFile.txt,outFile.resources." );
- Console.WriteLine("You passed in '{0}'.", args[i] );
+ bool compileMultiple = false;
+ bool useSourcePath = false;
+ ArrayList inputFiles = new ArrayList ();
+
+ for (int i = 0; i < args.Length; i++) {
+ switch (args [i].ToLower ()) {
+ case "-h":
+ case "/h":
+ case "-?":
+ case "/?":
+ Usage ();
+ return 1;
+ case "/compile":
+ case "-compile":
+ if (inputFiles.Count > 0) {
+ // the /compile option should be specified before any files
+ Usage ();
+ return 1;
+ }
+ compileMultiple = true;
+ break;
+
+ case "/usesourcepath":
+ case "-usesourcepath":
+ if (compileMultiple) {
+ // the /usesourcepath option should not appear after the
+ // /compile switch on the command-line
+ Console.WriteLine ("ResGen : error RG0000: Invalid "
+ + "command line syntax. Switch: \"/compile\" Bad value: "
+ + args [i] + ". Use ResGen /? for usage information.");
+ return 1;
+ }
+ useSourcePath = true;
+ break;
+
+ default:
+ if (args [i].StartsWith ("/d:") ||
+ args [i].StartsWith ("-d:") ||
+ args [i].StartsWith ("/define:") ||
+ args [i].StartsWith ("-define:") ||
+ args [i].StartsWith ("/D:") ||
+ args [i].StartsWith ("-D:") ||
+ args [i].StartsWith ("/DEFINE:") ||
+ args [i].StartsWith ("-DEFINE:")) {
+
+ string defines = args [i].Substring (args [i].IndexOf (':') + 1);
+ foreach (string s in defines.Split (',') ) {
+ symbols.Add(s);
+ }
+ break;
+ }
+
+ if (!IsFileArgument (args [i])) {
+ Usage ();
+ return 1;
+ }
+
+ ResourceInfo resInf = new ResourceInfo ();
+ if (compileMultiple) {
+ string [] pair = args [i].Split (',');
+ switch (pair.Length) {
+ case 1:
+ resInf.InputFile = Path.GetFullPath (pair [0]);
+ resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
+ "resources");
+ break;
+ case 2:
+ if (pair [1].Length == 0) {
+ Console.WriteLine (@"error: You must specify an input & outfile file name like this:");
+ Console.WriteLine ("inFile.txt,outFile.resources.");
+ Console.WriteLine ("You passed in '{0}'.", args [i]);
+ return 1;
+ }
+ resInf.InputFile = Path.GetFullPath (pair [0]);
+ resInf.OutputFile = Path.GetFullPath (pair [1]);
+ break;
+ default:
+ Usage ();
return 1;
}
} else {
- sname = args[i];
- dname = Path.ChangeExtension (sname, "resources");
- }
- int ret = CompileResourceFile( sname, dname );
- if (ret != 0 ) {
- return ret;
+ if ((i + 1) < args.Length) {
+ resInf.InputFile = Path.GetFullPath (args [i]);
+ // move to next arg, since we assume that one holds
+ // the name of the output file
+ i++;
+ resInf.OutputFile = Path.GetFullPath (args [i]);
+ } else {
+ resInf.InputFile = Path.GetFullPath (args [i]);
+ resInf.OutputFile = Path.ChangeExtension (resInf.InputFile,
+ "resources");
+ }
}
+ inputFiles.Add (resInf);
+ break;
}
- return 0;
-
}
- else if (args.Length == 1) {
- sname = args [0];
- dname = Path.ChangeExtension (sname, "resources");
- } else if (args.Length != 2) {
+
+ if (inputFiles.Count == 0) {
Usage ();
return 1;
- } else {
- sname = args [0];
- dname = args [1];
- }
- return CompileResourceFile( sname, dname );
+ }
+
+ foreach (ResourceInfo res in inputFiles) {
+ int ret = CompileResourceFile (res.InputFile, res.OutputFile, useSourcePath);
+ if (ret != 0 )
+ return ret;
+ }
+ return 0;
+ }
+
+ private static bool RunningOnUnix {
+ get {
+ // check for Unix platforms - see FAQ for more details
+ // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
+ int platform = (int) Environment.OSVersion.Platform;
+ return ((platform == 4) || (platform == 128) || (platform == 6));
+ }
+ }
+
+ private static bool IsFileArgument (string arg)
+ {
+ if ((arg [0] != '-') && (arg [0] != '/'))
+ return true;
+
+ // cope with absolute filenames for resx files on unix, as
+ // they also match the option pattern
+ //
+ // `/home/test.resx' is considered as a resx file, however
+ // '/test.resx' is considered as error
+ return (RunningOnUnix && arg.Length > 2 && arg.IndexOf ('/', 2) != -1);
}
}
class TxtResourceReader : IResourceReader {
Hashtable data;
Stream s;
+ HashSet <String> defines;
- public TxtResourceReader (Stream stream) {
+ public TxtResourceReader (Stream stream, IEnumerable<string> symbols) {
data = new Hashtable ();
s = stream;
+
+ defines = new HashSet<String> (symbols);
Load ();
}
public IDictionaryEnumerator GetEnumerator() {
return data.GetEnumerator ();
}
-
+
+ static string NextWord(ref string line) {
+ int i, j;
+ string keywd;
+ line = line.TrimStart ();
+ for (i = 0; i < line.Length && !Char.IsWhiteSpace (line [i]) && line [i] != ';'; i++ );
+
+ if (i < line.Length) {
+ for (j = i; j < line.Length && Char.IsWhiteSpace (line [j]) && line [j] != ';'; j++ );
+
+ keywd = line.Substring (0, i);
+ line = line.Substring (j).TrimStart ();
+ } else {
+ keywd = line;
+ line = "";
+ }
+ return keywd;
+ }
+
void Load () {
StreamReader reader = new StreamReader (s);
string line, key, val;
+ Stack<bool> conditional = new Stack<bool> (5);
int epos, line_num = 0;
+
+ conditional.Push(true);
while ((line = reader.ReadLine ()) != null) {
line_num++;
line = line.Trim ();
- if (line.Length == 0 || line [0] == '#' ||
- line [0] == ';')
+
+ if (line.Length == 0 || line [0] == ';')
+ continue;
+
+ if (line [0] == '#') {
+ bool stat;
+ bool neg = false;
+ string keywd, symbol;
+
+ line = line.Substring (1);
+ keywd = NextWord (ref line).ToLower ();
+ symbol = "";
+
+ if (line.Length > 0) {
+ if (line[0] == '!') {
+ line = line.Substring (1);
+ neg = true;
+ }
+ symbol = NextWord (ref line);
+ }
+
+ switch (keywd) {
+ case "endif":
+ case "else":
+ stat = conditional.Pop ();
+ if (conditional.Count < 1)
+ throw new Exception (String.Format ("Found an #{0} without matching #ifdef", keywd));
+
+ if (keywd == "else")
+ conditional.Push (conditional.Peek () && !stat);
+ break;
+
+ case "ifdef":
+ case "if":
+ if (symbol.Length == 0)
+ throw new Exception (String.Format ("Missing symbol after {0}", keywd));
+ stat = defines.Contains (symbol);
+ if (neg)
+ stat = !stat;
+
+ conditional.Push (conditional.Peek () && stat);
+ break;
+
+ }
+ continue;
+ }
+ if (conditional.Peek () == false)
continue;
+
epos = line.IndexOf ('=');
if (epos < 0)
throw new Exception ("Invalid format at line " + line_num);
data.Add (key, val);
}
+ if (conditional.Count > 1)
+ throw new Exception ("Found an #ifdef but not a matching #endif before reaching the end of the file.");
}
// \\n -> \n ...
case 't':
b.Append ('\t');
break;
+ case 'u':
+ int ch = int.Parse (value.Substring (++i, 4), NumberStyles.HexNumber);
+ b.Append (char.ConvertFromUtf32 (ch));
+ i += 3;
+ break;
case '\\':
b.Append ('\\');
break;
public void Generate () {}
}
+class ResourceInfo
+{
+ public string InputFile;
+ public string OutputFile;
+}