xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/msgunfmt.cs (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 
25 using System; /* Object, String, Type, Console, Exception */
26 using System.Reflection; /* Assembly, MethodInfo, ConstructorInfo */
27 using System.Collections; /* Hashtable, DictionaryEntry */
28 using System.IO; /* BufferedStream, StreamWriter, TextWriter, FileNotFoundException, Path */
29 using System.Text; /* StringBuilder, UTF8Encoding */
30 using System.Resources; /* ResourceReader */
31 using GNU.Gettext; /* GettextResourceSet */
32 
33 namespace GNU.Gettext {
34   public class DumpResource {
35     private TextWriter Out;
DumpString(String str)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     }
DumpMessage(String msgid, String msgid_plural, Object msgstr)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 
Dump(GettextResourceSet catalog)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.
GetSatelliteAssembly(String baseDirectory, String resourceName, String cultureName)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.
ConstructClassName(String resourceName)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.
InstantiateResourceSet(Assembly satelliteAssembly, String resourceName, String cultureName)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     }
DumpResource(String baseDirectory, String resourceName, String cultureName)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 
DumpResource(String filename)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 
Main(String[] args)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