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