/* GNU gettext for C# * Copyright (C) 2003-2004, 2007, 2015-2016 Free Software Foundation, Inc. * Written by Bruno Haible , 2003. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* * This program dumps a GettextResourceSet subclass (in a satellite assembly) * or a .resources file as a PO file. */ using System; /* Object, String, Type, Console, Exception */ using System.Reflection; /* Assembly, MethodInfo, ConstructorInfo */ using System.Collections; /* Hashtable, DictionaryEntry */ using System.IO; /* BufferedStream, StreamWriter, TextWriter, FileNotFoundException, Path */ using System.Text; /* StringBuilder, UTF8Encoding */ using System.Resources; /* ResourceReader */ using GNU.Gettext; /* GettextResourceSet */ namespace GNU.Gettext { public class DumpResource { private TextWriter Out; private void DumpString (String str) { int n = str.Length; Out.Write('"'); for (int i = 0; i < n; i++) { char c = str[i]; if (c == 0x0008) { Out.Write('\\'); Out.Write('b'); } else if (c == 0x000c) { Out.Write('\\'); Out.Write('f'); } else if (c == 0x000a) { Out.Write('\\'); Out.Write('n'); } else if (c == 0x000d) { Out.Write('\\'); Out.Write('r'); } else if (c == 0x0009) { Out.Write('\\'); Out.Write('t'); } else if (c == '\\' || c == '"') { Out.Write('\\'); Out.Write(c); } else Out.Write(c); } Out.Write('"'); } private void DumpMessage (String msgid, String msgid_plural, Object msgstr) { int separatorPos = msgid.IndexOf('\u0004'); if (separatorPos >= 0) { String msgctxt = msgid.Substring(0,separatorPos); msgid = msgid.Substring(separatorPos+1); Out.Write("msgctxt "); DumpString(msgctxt); } Out.Write("msgid "); DumpString(msgid); Out.Write('\n'); if (msgid_plural != null) { Out.Write("msgid_plural "); DumpString(msgid_plural); Out.Write('\n'); for (int i = 0; i < (msgstr as String[]).Length; i++) { Out.Write("msgstr[" + i + "] "); DumpString((msgstr as String[])[i]); Out.Write('\n'); } } else { Out.Write("msgstr "); DumpString(msgstr as String); Out.Write('\n'); } Out.Write('\n'); } // ---------------- Dumping a GettextResourceSet ---------------- private void Dump (GettextResourceSet catalog) { MethodInfo pluralMethod = catalog.GetType().GetMethod("GetMsgidPluralTable", Type.EmptyTypes); // Search for the header entry. { Object header_entry = catalog.GetObject(""); // If there is no header entry, fake one. // FIXME: This is not needed; right after po_lex_charset_init set // the PO charset to UTF-8. if (header_entry == null) header_entry = "Content-Type: text/plain; charset=UTF-8\n"; DumpMessage("", null, header_entry); } // Now the other messages. { Hashtable plural = null; if (pluralMethod != null) plural = pluralMethod.Invoke(catalog, new Object[0]) as Hashtable; foreach (String key in catalog.Keys) if (!"".Equals(key)) { Object value = catalog.GetObject(key); String key_plural = (plural != null && value is String[] ? plural[key] as String : null); DumpMessage(key, key_plural, value); } } } // Essentially taken from class GettextResourceManager. private static Assembly GetSatelliteAssembly (String baseDirectory, String resourceName, String cultureName) { String satelliteExpectedLocation = baseDirectory + Path.DirectorySeparatorChar + cultureName + Path.DirectorySeparatorChar + resourceName + ".resources.dll"; return Assembly.LoadFrom(satelliteExpectedLocation); } // Taken from class GettextResourceManager. private static String ConstructClassName (String resourceName) { // We could just return an arbitrary fixed class name, like "Messages", // assuming that every assembly will only ever contain one // GettextResourceSet subclass, but this assumption would break the day // we want to support multi-domain PO files in the same format... bool valid = (resourceName.Length > 0); for (int i = 0; valid && i < resourceName.Length; i++) { char c = resourceName[i]; if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') || (i > 0 && c >= '0' && c <= '9'))) valid = false; } if (valid) return resourceName; else { // Use hexadecimal escapes, using the underscore as escape character. String hexdigit = "0123456789abcdef"; StringBuilder b = new StringBuilder(); b.Append("__UESCAPED__"); for (int i = 0; i < resourceName.Length; i++) { char c = resourceName[i]; if (c >= 0xd800 && c < 0xdc00 && i+1 < resourceName.Length && resourceName[i+1] >= 0xdc00 && resourceName[i+1] < 0xe000) { // Combine two UTF-16 words to a character. char c2 = resourceName[i+1]; int uc = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); b.Append('_'); b.Append('U'); b.Append(hexdigit[(uc >> 28) & 0x0f]); b.Append(hexdigit[(uc >> 24) & 0x0f]); b.Append(hexdigit[(uc >> 20) & 0x0f]); b.Append(hexdigit[(uc >> 16) & 0x0f]); b.Append(hexdigit[(uc >> 12) & 0x0f]); b.Append(hexdigit[(uc >> 8) & 0x0f]); b.Append(hexdigit[(uc >> 4) & 0x0f]); b.Append(hexdigit[uc & 0x0f]); i++; } else if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))) { int uc = c; b.Append('_'); b.Append('u'); b.Append(hexdigit[(uc >> 12) & 0x0f]); b.Append(hexdigit[(uc >> 8) & 0x0f]); b.Append(hexdigit[(uc >> 4) & 0x0f]); b.Append(hexdigit[uc & 0x0f]); } else b.Append(c); } return b.ToString(); } } // Essentially taken from class GettextResourceManager. private static GettextResourceSet InstantiateResourceSet (Assembly satelliteAssembly, String resourceName, String cultureName) { Type clazz = satelliteAssembly.GetType(ConstructClassName(resourceName)+"_"+cultureName.Replace('-','_')); ConstructorInfo constructor = clazz.GetConstructor(Type.EmptyTypes); return constructor.Invoke(null) as GettextResourceSet; } public DumpResource (String baseDirectory, String resourceName, String cultureName) { // We are only interested in the messages belonging to the locale // itself, not in the inherited messages. Therefore we instantiate just // the GettextResourceSet, not a GettextResourceManager. Assembly satelliteAssembly = GetSatelliteAssembly(baseDirectory, resourceName, cultureName); GettextResourceSet catalog = InstantiateResourceSet(satelliteAssembly, resourceName, cultureName); BufferedStream stream = new BufferedStream(Console.OpenStandardOutput()); Out = new StreamWriter(stream, new UTF8Encoding()); Dump(catalog); Out.Close(); stream.Close(); } // ----------------- Dumping a .resources file ------------------ public DumpResource (String filename) { BufferedStream stream = new BufferedStream(Console.OpenStandardOutput()); Out = new StreamWriter(stream, new UTF8Encoding()); ResourceReader rr; if (filename.Equals("-")) { BufferedStream input = new BufferedStream(Console.OpenStandardInput()); // A temporary output stream is needed because ResourceReader expects // to be able to seek in the Stream. byte[] contents; { MemoryStream tmpstream = new MemoryStream(); byte[] buf = new byte[1024]; for (;;) { int n = input.Read(buf, 0, 1024); if (n == 0) break; tmpstream.Write(buf, 0, n); } contents = tmpstream.ToArray(); tmpstream.Close(); } MemoryStream tmpinput = new MemoryStream(contents); rr = new ResourceReader(tmpinput); } else { rr = new ResourceReader(filename); } foreach (DictionaryEntry entry in rr) // uses rr.GetEnumerator() DumpMessage(entry.Key as String, null, entry.Value as String); rr.Close(); Out.Close(); stream.Close(); } // -------------------------------------------------------------- public static int Main (String[] args) { try { if (args.Length > 1) new DumpResource(args[0], args[1], args[2]); else new DumpResource(args[0]); } catch (Exception e) { Console.Error.WriteLine(e); Console.Error.WriteLine(e.StackTrace); return 1; } return 0; } } }