1 /* GNU gettext for C# 2 * Copyright (C) 2003-2004, 2007 Free Software Foundation, Inc. 3 * Written by Bruno Haible <bruno@clisp.org>, 2003. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 /* 20 * This program dumps a GettextResourceSet subclass (in a satellite assembly) 21 * or a .resources file as a PO file. 22 */ 23 24 using System; /* Object, String, Type, Console, Exception */ 25 using System.Reflection; /* Assembly, MethodInfo, ConstructorInfo */ 26 using System.Collections; /* Hashtable, DictionaryEntry */ 27 using System.IO; /* BufferedStream, StreamWriter, TextWriter, FileNotFoundException, Path */ 28 using System.Text; /* StringBuilder, UTF8Encoding */ 29 using System.Resources; /* ResourceReader */ 30 using GNU.Gettext; /* GettextResourceSet */ 31 32 namespace GNU.Gettext { 33 public class DumpResource { 34 private TextWriter Out; DumpString(String str)35 private void DumpString (String str) { 36 int n = str.Length; 37 Out.Write('"'); 38 for (int i = 0; i < n; i++) { 39 char c = str[i]; 40 if (c == 0x0008) { 41 Out.Write('\\'); Out.Write('b'); 42 } else if (c == 0x000c) { 43 Out.Write('\\'); Out.Write('f'); 44 } else if (c == 0x000a) { 45 Out.Write('\\'); Out.Write('n'); 46 } else if (c == 0x000d) { 47 Out.Write('\\'); Out.Write('r'); 48 } else if (c == 0x0009) { 49 Out.Write('\\'); Out.Write('t'); 50 } else if (c == '\\' || c == '"') { 51 Out.Write('\\'); Out.Write(c); 52 } else 53 Out.Write(c); 54 } 55 Out.Write('"'); 56 } DumpMessage(String msgid, String msgid_plural, Object msgstr)57 private void DumpMessage (String msgid, String msgid_plural, Object msgstr) { 58 int separatorPos = msgid.IndexOf('\u0004'); 59 if (separatorPos >= 0) { 60 String msgctxt = msgid.Substring(0,separatorPos); 61 msgid = msgid.Substring(separatorPos+1); 62 Out.Write("msgctxt "); DumpString(msgctxt); 63 } 64 Out.Write("msgid "); DumpString(msgid); Out.Write('\n'); 65 if (msgid_plural != null) { 66 Out.Write("msgid_plural "); DumpString(msgid_plural); Out.Write('\n'); 67 for (int i = 0; i < (msgstr as String[]).Length; i++) { 68 Out.Write("msgstr[" + i + "] "); 69 DumpString((msgstr as String[])[i]); 70 Out.Write('\n'); 71 } 72 } else { 73 Out.Write("msgstr "); DumpString(msgstr as String); Out.Write('\n'); 74 } 75 Out.Write('\n'); 76 } 77 78 // ---------------- Dumping a GettextResourceSet ---------------- 79 Dump(GettextResourceSet catalog)80 private void Dump (GettextResourceSet catalog) { 81 MethodInfo pluralMethod = 82 catalog.GetType().GetMethod("GetMsgidPluralTable", Type.EmptyTypes); 83 // Search for the header entry. 84 { 85 Object header_entry = catalog.GetObject(""); 86 // If there is no header entry, fake one. 87 // FIXME: This is not needed; right after po_lex_charset_init set 88 // the PO charset to UTF-8. 89 if (header_entry == null) 90 header_entry = "Content-Type: text/plain; charset=UTF-8\n"; 91 DumpMessage("", null, header_entry); 92 } 93 // Now the other messages. 94 { 95 Hashtable plural = null; 96 if (pluralMethod != null) 97 plural = pluralMethod.Invoke(catalog, new Object[0]) as Hashtable; 98 foreach (String key in catalog.Keys) 99 if (!"".Equals(key)) { 100 Object value = catalog.GetObject(key); 101 String key_plural = 102 (plural != null && value is String[] ? plural[key] as String : null); 103 DumpMessage(key, key_plural, value); 104 } 105 } 106 } 107 // Essentially taken from class GettextResourceManager. GetSatelliteAssembly(String baseDirectory, String resourceName, String cultureName)108 private static Assembly GetSatelliteAssembly (String baseDirectory, String resourceName, String cultureName) { 109 String satelliteExpectedLocation = 110 baseDirectory 111 + Path.DirectorySeparatorChar + cultureName 112 + Path.DirectorySeparatorChar + resourceName + ".resources.dll"; 113 return Assembly.LoadFrom(satelliteExpectedLocation); 114 } 115 // Taken from class GettextResourceManager. ConstructClassName(String resourceName)116 private static String ConstructClassName (String resourceName) { 117 // We could just return an arbitrary fixed class name, like "Messages", 118 // assuming that every assembly will only ever contain one 119 // GettextResourceSet subclass, but this assumption would break the day 120 // we want to support multi-domain PO files in the same format... 121 bool valid = (resourceName.Length > 0); 122 for (int i = 0; valid && i < resourceName.Length; i++) { 123 char c = resourceName[i]; 124 if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') 125 || (i > 0 && c >= '0' && c <= '9'))) 126 valid = false; 127 } 128 if (valid) 129 return resourceName; 130 else { 131 // Use hexadecimal escapes, using the underscore as escape character. 132 String hexdigit = "0123456789abcdef"; 133 StringBuilder b = new StringBuilder(); 134 b.Append("__UESCAPED__"); 135 for (int i = 0; i < resourceName.Length; i++) { 136 char c = resourceName[i]; 137 if (c >= 0xd800 && c < 0xdc00 138 && i+1 < resourceName.Length 139 && resourceName[i+1] >= 0xdc00 && resourceName[i+1] < 0xe000) { 140 // Combine two UTF-16 words to a character. 141 char c2 = resourceName[i+1]; 142 int uc = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); 143 b.Append('_'); 144 b.Append('U'); 145 b.Append(hexdigit[(uc >> 28) & 0x0f]); 146 b.Append(hexdigit[(uc >> 24) & 0x0f]); 147 b.Append(hexdigit[(uc >> 20) & 0x0f]); 148 b.Append(hexdigit[(uc >> 16) & 0x0f]); 149 b.Append(hexdigit[(uc >> 12) & 0x0f]); 150 b.Append(hexdigit[(uc >> 8) & 0x0f]); 151 b.Append(hexdigit[(uc >> 4) & 0x0f]); 152 b.Append(hexdigit[uc & 0x0f]); 153 i++; 154 } else if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') 155 || (c >= '0' && c <= '9'))) { 156 int uc = c; 157 b.Append('_'); 158 b.Append('u'); 159 b.Append(hexdigit[(uc >> 12) & 0x0f]); 160 b.Append(hexdigit[(uc >> 8) & 0x0f]); 161 b.Append(hexdigit[(uc >> 4) & 0x0f]); 162 b.Append(hexdigit[uc & 0x0f]); 163 } else 164 b.Append(c); 165 } 166 return b.ToString(); 167 } 168 } 169 // Essentially taken from class GettextResourceManager. InstantiateResourceSet(Assembly satelliteAssembly, String resourceName, String cultureName)170 private static GettextResourceSet InstantiateResourceSet (Assembly satelliteAssembly, String resourceName, String cultureName) { 171 Type clazz = satelliteAssembly.GetType(ConstructClassName(resourceName)+"_"+cultureName.Replace('-','_')); 172 ConstructorInfo constructor = clazz.GetConstructor(Type.EmptyTypes); 173 return constructor.Invoke(null) as GettextResourceSet; 174 } DumpResource(String baseDirectory, String resourceName, String cultureName)175 public DumpResource (String baseDirectory, String resourceName, String cultureName) { 176 // We are only interested in the messages belonging to the locale 177 // itself, not in the inherited messages. Therefore we instantiate just 178 // the GettextResourceSet, not a GettextResourceManager. 179 Assembly satelliteAssembly = 180 GetSatelliteAssembly(baseDirectory, resourceName, cultureName); 181 GettextResourceSet catalog = 182 InstantiateResourceSet(satelliteAssembly, resourceName, cultureName); 183 BufferedStream stream = new BufferedStream(Console.OpenStandardOutput()); 184 Out = new StreamWriter(stream, new UTF8Encoding()); 185 Dump(catalog); 186 Out.Close(); 187 stream.Close(); 188 } 189 190 // ----------------- Dumping a .resources file ------------------ 191 DumpResource(String filename)192 public DumpResource (String filename) { 193 BufferedStream stream = new BufferedStream(Console.OpenStandardOutput()); 194 Out = new StreamWriter(stream, new UTF8Encoding()); 195 ResourceReader rr; 196 if (filename.Equals("-")) { 197 BufferedStream input = new BufferedStream(Console.OpenStandardInput()); 198 // A temporary output stream is needed because ResourceReader expects 199 // to be able to seek in the Stream. 200 byte[] contents; 201 { 202 MemoryStream tmpstream = new MemoryStream(); 203 byte[] buf = new byte[1024]; 204 for (;;) { 205 int n = input.Read(buf, 0, 1024); 206 if (n == 0) 207 break; 208 tmpstream.Write(buf, 0, n); 209 } 210 contents = tmpstream.ToArray(); 211 tmpstream.Close(); 212 } 213 MemoryStream tmpinput = new MemoryStream(contents); 214 rr = new ResourceReader(tmpinput); 215 } else { 216 rr = new ResourceReader(filename); 217 } 218 foreach (DictionaryEntry entry in rr) // uses rr.GetEnumerator() 219 DumpMessage(entry.Key as String, null, entry.Value as String); 220 rr.Close(); 221 Out.Close(); 222 stream.Close(); 223 } 224 225 // -------------------------------------------------------------- 226 Main(String[] args)227 public static int Main (String[] args) { 228 try { 229 if (args.Length > 1) 230 new DumpResource(args[0], args[1], args[2]); 231 else 232 new DumpResource(args[0]); 233 } catch (Exception e) { 234 Console.Error.WriteLine(e); 235 Console.Error.WriteLine(e.StackTrace); 236 return 1; 237 } 238 return 0; 239 } 240 } 241 } 242