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