Xamarin Public Jenkins (auto-signing) 966bba02bb Imported Upstream version 5.2.0.175
Former-commit-id: bb0468d0f257ff100aa895eb5fe583fb5dfbf900
2017-06-07 13:16:24 +00:00

750 lines
18 KiB
C#

/*
* resgen: convert between the resource formats (.txt, .resources, .resx).
*
* Copyright (c) 2002 Ximian, Inc
*
* Authors:
* Paolo Molaro (lupus@ximian.com)
* Gonzalo Paniagua Javier (gonzalo@ximian.com)
*/
using System;
using System.Globalization;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Resources;
using System.Xml;
class ResGen {
static HashSet<string> symbols = new HashSet<string> ();
static void Usage () {
string Usage = @"Mono Resource Generator version " + Consts.MonoVersion +
@"
Usage:
resgen source.ext [dest.ext]
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.";
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, bool useSourcePath) {
string format = Path.GetExtension (name);
switch (format.ToLower (System.Globalization.CultureInfo.InvariantCulture)) {
case ".po":
return new PoResourceReader (stream);
case ".txt":
case ".text":
return new TxtResourceReader (stream, symbols);
case ".resources":
return new ResourceReader (stream);
case ".resx":
var reader = new ResXResourceReader (stream);
reader.BasePath = Path.GetDirectoryName (name);
return reader;
default:
throw new Exception ("Unknown format in file " + name);
}
}
static IResourceWriter GetWriter (Stream stream, string name) {
string format = Path.GetExtension (name);
switch (format.ToLower ()) {
case ".po":
return new PoResourceWriter (stream);
case ".txt":
case ".text":
return new TxtResourceWriter (stream);
case ".resources":
return new ResourceWriter (stream);
case ".resx":
return new ResXResourceWriter (stream);
default:
throw new Exception ("Unknown format in file " + name);
}
}
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, useSourcePath);
dest = new FileStream (dname, FileMode.Create, FileAccess.Write);
writer = GetWriter (dest, dname);
int rescount = 0;
foreach (DictionaryEntry e in reader) {
rescount++;
object val = e.Value;
if (val is string)
writer.AddResource ((string)e.Key, (string)e.Value);
else
writer.AddResource ((string)e.Key, e.Value);
}
Console.WriteLine( "Read in {0} resources from '{1}'", rescount, sname );
reader.Close ();
writer.Close ();
Console.WriteLine("Writing resource file... Done.");
} 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 System.Reflection.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) {
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 {
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;
}
}
if (inputFiles.Count == 0) {
Usage ();
return 1;
}
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 TxtResourceWriter : IResourceWriter {
StreamWriter s;
public TxtResourceWriter (Stream stream) {
s = new StreamWriter (stream);
}
public void AddResource (string name, byte[] value) {
throw new Exception ("Binary data not valid in a text resource file");
}
public void AddResource (string name, object value) {
if (value is string) {
AddResource (name, (string)value);
return;
}
throw new Exception ("Objects not valid in a text resource file");
}
public void AddResource (string name, string value) {
s.WriteLine ("{0}={1}", name, Escape (value));
}
// \n -> \\n ...
static string Escape (string value)
{
StringBuilder b = new StringBuilder ();
for (int i = 0; i < value.Length; i++) {
switch (value [i]) {
case '\n':
b.Append ("\\n");
break;
case '\r':
b.Append ("\\r");
break;
case '\t':
b.Append ("\\t");
break;
case '\\':
b.Append ("\\\\");
break;
default:
b.Append (value [i]);
break;
}
}
return b.ToString ();
}
public void Close () {
s.Close ();
}
public void Dispose () {}
public void Generate () {}
}
class TxtResourceReader : IResourceReader {
Hashtable data;
Stream s;
HashSet <String> defines;
public TxtResourceReader (Stream stream, IEnumerable<string> symbols) {
data = new Hashtable ();
s = stream;
defines = new HashSet<String> (symbols);
Load ();
}
public virtual void Close () {
}
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] == ';')
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);
key = line.Substring (0, epos);
val = line.Substring (epos + 1);
key = key.Trim ();
val = val.Trim ();
if (key.Length == 0)
throw new Exception ("Key is empty at line " + line_num);
val = Unescape (val);
if (val == null)
throw new Exception (String.Format ("Unsupported escape character in value of key '{0}'.", key));
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 ...
static string Unescape (string value)
{
StringBuilder b = new StringBuilder ();
for (int i = 0; i < value.Length; i++) {
if (value [i] == '\\') {
if (i == value.Length - 1)
return null;
i++;
switch (value [i]) {
case 'n':
b.Append ('\n');
break;
case 'r':
b.Append ('\r');
break;
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;
default:
return null;
}
} else {
b.Append (value [i]);
}
}
return b.ToString ();
}
IEnumerator IEnumerable.GetEnumerator () {
return ((IResourceReader) this).GetEnumerator();
}
void IDisposable.Dispose () {}
}
class PoResourceReader : IResourceReader {
Hashtable data;
Stream s;
int line_num;
public PoResourceReader (Stream stream)
{
data = new Hashtable ();
s = stream;
Load ();
}
public virtual void Close ()
{
s.Close ();
}
public IDictionaryEnumerator GetEnumerator()
{
return data.GetEnumerator ();
}
string GetValue (string line)
{
int begin = line.IndexOf ('"');
if (begin == -1)
throw new FormatException (String.Format ("No begin quote at line {0}: {1}", line_num, line));
int end = line.LastIndexOf ('"');
if (end == -1)
throw new FormatException (String.Format ("No closing quote at line {0}: {1}", line_num, line));
return line.Substring (begin + 1, end - begin - 1);
}
void Load ()
{
StreamReader reader = new StreamReader (s);
string line;
string msgid = null;
string msgstr = null;
bool ignoreNext = false;
while ((line = reader.ReadLine ()) != null) {
line_num++;
line = line.Trim ();
if (line.Length == 0)
continue;
if (line [0] == '#') {
if (line.Length == 1 || line [1] != ',')
continue;
if (line.IndexOf ("fuzzy") != -1) {
ignoreNext = true;
if (msgid != null) {
if (msgstr == null)
throw new FormatException ("Error. Line: " + line_num);
data.Add (msgid, msgstr);
msgid = null;
msgstr = null;
}
}
continue;
}
if (line.StartsWith ("msgid ")) {
if (msgid == null && msgstr != null)
throw new FormatException ("Found 2 consecutive msgid. Line: " + line_num);
if (msgstr != null) {
if (!ignoreNext)
data.Add (msgid, msgstr);
ignoreNext = false;
msgid = null;
msgstr = null;
}
msgid = GetValue (line);
continue;
}
if (line.StartsWith ("msgstr ")) {
if (msgid == null)
throw new FormatException ("msgstr with no msgid. Line: " + line_num);
msgstr = GetValue (line);
continue;
}
if (line [0] == '"') {
if (msgid == null || msgstr == null)
throw new FormatException ("Invalid format. Line: " + line_num);
msgstr += GetValue (line);
continue;
}
throw new FormatException ("Unexpected data. Line: " + line_num);
}
if (msgid != null) {
if (msgstr == null)
throw new FormatException ("Expecting msgstr. Line: " + line_num);
if (!ignoreNext)
data.Add (msgid, msgstr);
}
}
IEnumerator IEnumerable.GetEnumerator ()
{
return GetEnumerator();
}
void IDisposable.Dispose ()
{
if (data != null)
data = null;
if (s != null) {
s.Close ();
s = null;
}
}
}
class PoResourceWriter : IResourceWriter
{
TextWriter s;
bool headerWritten;
public PoResourceWriter (Stream stream)
{
s = new StreamWriter (stream);
}
public void AddResource (string name, byte [] value)
{
throw new InvalidOperationException ("Binary data not valid in a po resource file");
}
public void AddResource (string name, object value)
{
if (value is string) {
AddResource (name, (string) value);
return;
}
throw new InvalidOperationException ("Objects not valid in a po resource file");
}
StringBuilder ebuilder = new StringBuilder ();
public string Escape (string ns)
{
ebuilder.Length = 0;
foreach (char c in ns){
switch (c){
case '"':
case '\\':
ebuilder.Append ('\\');
ebuilder.Append (c);
break;
case '\a':
ebuilder.Append ("\\a");
break;
case '\n':
ebuilder.Append ("\\n");
break;
case '\r':
ebuilder.Append ("\\r");
break;
default:
ebuilder.Append (c);
break;
}
}
return ebuilder.ToString ();
}
public void AddResource (string name, string value)
{
if (!headerWritten) {
headerWritten = true;
WriteHeader ();
}
s.WriteLine ("msgid \"{0}\"", Escape (name));
s.WriteLine ("msgstr \"{0}\"", Escape (value));
s.WriteLine ("");
}
void WriteHeader ()
{
s.WriteLine ("msgid \"\"");
s.WriteLine ("msgstr \"\"");
s.WriteLine ("\"MIME-Version: 1.0\\n\"");
s.WriteLine ("\"Content-Type: text/plain; charset=UTF-8\\n\"");
s.WriteLine ("\"Content-Transfer-Encoding: 8bit\\n\"");
s.WriteLine ("\"X-Generator: Mono resgen 0.1\\n\"");
s.WriteLine ("#\"Project-Id-Version: FILLME\\n\"");
s.WriteLine ("#\"POT-Creation-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
s.WriteLine ("#\"PO-Revision-Date: yyyy-MM-dd HH:MM+zzzz\\n\"");
s.WriteLine ("#\"Last-Translator: FILLME\\n\"");
s.WriteLine ("#\"Language-Team: FILLME\\n\"");
s.WriteLine ("#\"Report-Msgid-Bugs-To: \\n\"");
s.WriteLine ();
}
public void Close ()
{
s.Close ();
}
public void Dispose () { }
public void Generate () {}
}
class ResourceInfo
{
public string InputFile;
public string OutputFile;
}