2 * Copyright (C) 2003-2004, 2007, 2015 Free Software Foundation, Inc.
3 * Written by Bruno Haible <bruno@clisp.org>, 2003.
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.
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * This program dumps a GettextResourceSet subclass (in a satellite assembly)
21 * or a .resources file as a PO file.
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 */
32 namespace GNU.Gettext {
33 public class DumpResource {
34 private TextWriter Out;
35 private void DumpString (String str) {
38 for (int i = 0; i < n; i++) {
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);
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);
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]);
73 Out.Write("msgstr "); DumpString(msgstr as String); Out.Write('\n');
78 // ---------------- Dumping a GettextResourceSet ----------------
80 private void Dump (GettextResourceSet catalog) {
81 MethodInfo pluralMethod =
82 catalog.GetType().GetMethod("GetMsgidPluralTable", Type.EmptyTypes);
83 // Search for the header entry.
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);
93 // Now the other messages.
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);
102 (plural != null && value is String[] ? plural[key] as String : null);
103 DumpMessage(key, key_plural, value);
107 // Essentially taken from class GettextResourceManager.
108 private static Assembly GetSatelliteAssembly (String baseDirectory, String resourceName, String cultureName) {
109 String satelliteExpectedLocation =
111 + Path.DirectorySeparatorChar + cultureName
112 + Path.DirectorySeparatorChar + resourceName + ".resources.dll";
113 return Assembly.LoadFrom(satelliteExpectedLocation);
115 // Taken from class GettextResourceManager.
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')))
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);
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]);
154 } else if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
155 || (c >= '0' && c <= '9'))) {
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]);
169 // Essentially taken from class GettextResourceManager.
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;
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());
190 // ----------------- Dumping a .resources file ------------------
192 public DumpResource (String filename) {
193 BufferedStream stream = new BufferedStream(Console.OpenStandardOutput());
194 Out = new StreamWriter(stream, new UTF8Encoding());
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.
202 MemoryStream tmpstream = new MemoryStream();
203 byte[] buf = new byte[1024];
205 int n = input.Read(buf, 0, 1024);
208 tmpstream.Write(buf, 0, n);
210 contents = tmpstream.ToArray();
213 MemoryStream tmpinput = new MemoryStream(contents);
214 rr = new ResourceReader(tmpinput);
216 rr = new ResourceReader(filename);
218 foreach (DictionaryEntry entry in rr) // uses rr.GetEnumerator()
219 DumpMessage(entry.Key as String, null, entry.Value as String);
225 // --------------------------------------------------------------
227 public static int Main (String[] args) {
230 new DumpResource(args[0], args[1], args[2]);
232 new DumpResource(args[0]);
233 } catch (Exception e) {
234 Console.Error.WriteLine(e);
235 Console.Error.WriteLine(e.StackTrace);