Vidalia  0.2.21
po2wxl.cpp
Go to the documentation of this file.
1 /*
2 ** $Id$
3 **
4 ** This file is part of Vidalia, and is subject to the license terms in the
5 ** LICENSE file, found in the top level directory of this distribution. If you
6 ** did not receive the LICENSE file with this file, you may obtain it from the
7 ** Vidalia source package distributed by the Vidalia Project at
8 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
9 ** including this file, may be copied, modified, propagated, or distributed
10 ** except according to the terms described in the LICENSE file.
11 */
12 
13 #include <QFile>
14 #include <QDomDocument>
15 #include <QTextStream>
16 #include <QTextCodec>
17 #include <stdlib.h>
18 
19 #define WXL_NAMESPACE "http://schemas.microsoft.com/wix/2006/localization"
20 #define WXL_ELEMENT_ROOT "WixLocalization"
21 #define WXL_ELEMENT_MESSAGE "String"
22 #define WXL_ATTR_MESSAGE_ID "Id"
23 #define WXL_ATTR_LANGUAGE "LCID"
24 #define WXL_ATTR_TRANSLATION_TYPE "Culture"
25 #define WXL_ATTR_OVERRIDABLE "Overridable"
26 
27 /** We need to provide an element with the LCID for this locale
28  * that is used in the WiX Product definition. */
29 QString
30 culture_lcid(const QString &culture)
31 {
32  /* For now character encoding focused, not generally locale / dialect aware. */
33  QString lcid = "0";
34  if(!culture.compare("en", Qt::CaseInsensitive))
35  lcid = "1033";
36  else if(!culture.compare("cs", Qt::CaseInsensitive))
37  lcid = "1029";
38  else if(!culture.compare("de", Qt::CaseInsensitive))
39  lcid = "1031";
40  else if(!culture.compare("es", Qt::CaseInsensitive))
41  lcid = "1034";
42  else if(!culture.compare("fa", Qt::CaseInsensitive))
43  lcid = "1065";
44  else if(!culture.compare("fi", Qt::CaseInsensitive))
45  lcid = "1035";
46  else if(!culture.compare("fr", Qt::CaseInsensitive))
47  lcid = "1036";
48  else if(!culture.compare("he", Qt::CaseInsensitive))
49  lcid = "1037";
50  else if(!culture.compare("it", Qt::CaseInsensitive))
51  lcid = "1040";
52  else if(!culture.compare("nl", Qt::CaseInsensitive))
53  lcid = "1043";
54  else if(!culture.compare("pl", Qt::CaseInsensitive))
55  lcid = "1045";
56  else if(!culture.compare("pt", Qt::CaseInsensitive))
57  lcid = "1046";
58  else if(!culture.compare("ro", Qt::CaseInsensitive))
59  lcid = "1048";
60  else if(!culture.compare("ru", Qt::CaseInsensitive))
61  lcid = "1049";
62  else if(!culture.compare("sv", Qt::CaseInsensitive))
63  lcid = "1053";
64  return lcid;
65 }
66 
67 /** Create a new message string element using the source string <b>msgid</b>
68  * and the translation <b>msgstr</b> and assign identifier attribute. */
69 QDomElement
70 new_message_element(QDomDocument *wxl, const QString &strid,
71  const QString &msgid, const QString &msgstr)
72 {
73  QDomElement message;
74 
75  message = wxl->createElement(WXL_ELEMENT_MESSAGE);
76  message.setAttribute(WXL_ATTR_MESSAGE_ID, strid);
77 
78  /* Always allow localized string to be dynamic. This is required for
79  * multi-language packages to link correctly.
80  */
81  message.setAttribute(WXL_ATTR_OVERRIDABLE, "yes");
82  if (!msgstr.isEmpty())
83  message.appendChild(wxl->createTextNode(msgstr));
84  else
85  message.appendChild(wxl->createTextNode(msgid));
86 
87  return message;
88 }
89 
90 /** Create a new WXL document of the appropriate doctype and root
91  * element with the Microsoft style culture name for locale. */
92 QDomDocument
93 new_wxl_document(const QString &culture)
94 {
95  QDomDocument wxl;
96 
97  QDomElement root = wxl.createElementNS(WXL_NAMESPACE, WXL_ELEMENT_ROOT);
98  root.setAttribute(WXL_ATTR_TRANSLATION_TYPE, culture);
99  wxl.appendChild(root);
100 
101  return wxl;
102 }
103 
104 /** Parse the context name from <b>str</b>, where the context name is of the
105  * form DQUOTE ContextName DQUOTE. */
106 QString
107 parse_message_context(const QString &str)
108 {
109  QString out = str.trimmed();
110  out = out.replace("\"", "");
111  return out;
112 }
113 
114 /** Parse the context name from <b>str</b>, where <b>str</b> is of the
115  * form ContextName#Number. This is the format used by translate-toolkit. */
116 QString
117 parse_message_context_lame(const QString &str)
118 {
119  if (str.contains("#"))
120  return str.section("#", 0, 0);
121  return QString();
122 }
123 
124 /** Parse the PO-formatted message string from <b>msg</b>. If <b>msg</b> is a
125  * multiline string, the extra double quotes will be replaced with newlines
126  * appropriately. */
127 QString
128 parse_message_string(const QString &msg)
129 {
130  QString out = msg.trimmed();
131 
132  out.replace("\"\n\"", "\n");
133  if (out.startsWith("\""))
134  out = out.remove(0, 1);
135  if (out.endsWith("\""))
136  out.chop(1);
137  out.replace("\\\"", "\"");
138 
139  /* convert NSIS style vars to Properties; avoid QRegExp if possible. */
140  int lind, rind;
141  while ( ((lind = out.indexOf("${")) >= 0) &&
142  ((rind = out.indexOf("}", lind)) > lind) ) {
143  out.replace(lind, 2, "[");
144  out.replace(--rind, 1, "]");
145  }
146  return out;
147 }
148 
149 /** Read and return the next non-empty line from <b>stream</b>. */
150 QString
151 read_next_line(QTextStream *stream)
152 {
153  stream->skipWhiteSpace();
154  return stream->readLine().append("\n");
155 }
156 
157 /** Skip past the header portion of the PO file and any leading whitespace.
158  * The next line read from <b>po</b> will be the first non-header line in the
159  * document. */
160 void
161 skip_po_header(QTextStream *po)
162 {
163  QString line;
164  /* Skip any leading whitespace before the header */
165  po->skipWhiteSpace();
166  /* Read to the first empty line */
167  line = po->readLine();
168  while (!po->atEnd() && !line.isEmpty())
169  line = po->readLine();
170 }
171 
172 /** Convert <b>po</b> from the PO format to a WXL-formatted XML document.
173  * <b>wxl</b> will be set to the resulting WXL document. Return the number of
174  * converted strings on success, or -1 on error and <b>errorMessage</b> will
175  * be set. */
176 int
177 po2wxl(const QString& culture, QTextStream *po, QDomDocument *wxl,
178  QString *errorMessage)
179 {
180  QString line;
181  QString msgctxt, msgid, msgstr;
182  QDomElement msgElement;
183  int n_strings = 0;
184 
185  Q_ASSERT(po);
186  Q_ASSERT(wxl);
187  Q_ASSERT(errorMessage);
188 
189  *wxl = new_wxl_document(culture);
190 
191  /* Set the LCID to Language code for use as !(loc.LCID) in Product. */
192  QString lcid = culture_lcid(culture);
193  wxl->documentElement().appendChild(
194  new_message_element(wxl, WXL_ATTR_LANGUAGE, lcid, lcid));
195 
196  skip_po_header(po);
197  line = read_next_line(po);
198  while (!po->atEnd()) {
199  /* Ignore all "#" lines except "#:" */
200  while (line.startsWith("#")) {
201  if (line.startsWith("#:")) {
202  /* Context was specified with the stupid overloaded "#:" syntax.*/
203  msgctxt = line.section(" ", 1);
204  msgctxt = parse_message_context_lame(msgctxt);
205  }
206  line = read_next_line(po);
207  }
208 
209  /* A context specified on a "msgctxt" line takes precedence over a context
210  * specified using the overload "#:" notation. */
211  if (line.startsWith("msgctxt ")) {
212  msgctxt = line.section(" ", 1);
213  msgctxt = parse_message_context(msgctxt);
214  line = read_next_line(po);
215  }
216 
217  /* Parse the (possibly multiline) message source string */
218  if (!line.startsWith("msgid ")) {
219  *errorMessage = "expected 'msgid' line";
220  return -1;
221  }
222  msgid = line.section(" ", 1);
223 
224  line = read_next_line(po);
225  while (line.startsWith("\"")) {
226  msgid.append(line);
227  line = read_next_line(po);
228  }
229  msgid = parse_message_string(msgid);
230 
231  /* Parse the (possibly multiline) translated string */
232  if (!line.startsWith("msgstr ")) {
233  *errorMessage = "expected 'msgstr' line";
234  return -1;
235  }
236  msgstr = line.section(" ", 1);
237 
238  line = read_next_line(po);
239  while (line.startsWith("\"")) {
240  msgstr.append(line);
241  line = read_next_line(po);
242  }
243  msgstr = parse_message_string(msgstr);
244 
245  /* Add the message and translation to the .wxl document */
246  wxl->documentElement().appendChild(
247  new_message_element(wxl, msgctxt, msgid, msgstr));
248 
249  n_strings++;
250  }
251  return n_strings;
252 }
253 
254 /** Display application usage and exit. */
255 void
257 {
258  QTextStream error(stderr);
259  error << "usage: po2wxl [-q] -n <culture name> -i <infile.po> -o <outfile.wxl> "
260  "[-c <encoding>]\n";
261  error << " -q (optional) Quiet mode (errors are still displayed)\n";
262  error << " -n <culture> Culture name for translation\n";
263  error << " -i <infile.po> Input .po file\n";
264  error << " -o <outfile.wxl> Output .wxl file\n";
265  error << " -c <encoding> Text encoding (default: utf-8)\n";
266  error.flush();
267  exit(1);
268 }
269 
270 int
271 main(int argc, char *argv[])
272 {
273  QTextStream error(stderr);
274  QString culture, errorMessage;
275  char *infile, *outfile;
276  QTextCodec *codec = QTextCodec::codecForName("utf-8");
277  bool quiet = false;
278 
279  /* Check for the correct number of input parameters. */
280  if (argc < 5 || argc > 9)
282  for (int i = 1; i < argc; i++) {
283  QString arg(argv[i]);
284  if (!arg.compare("-q", Qt::CaseInsensitive))
285  quiet = true;
286  else if (!arg.compare("-n", Qt::CaseInsensitive) && ++i < argc)
287  culture = argv[i];
288  else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc)
289  infile = argv[i];
290  else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc)
291  outfile = argv[i];
292  else if (!arg.compare("-c", Qt::CaseInsensitive) && ++i < argc) {
293  codec = QTextCodec::codecForName(argv[i]);
294  if (!codec) {
295  error << "Invalid text encoding specified\n";
296  return 1;
297  }
298  } else
300  }
301 
302  /* Open the input PO file for reading. */
303  QFile poFile(infile);
304  if (!poFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
305  error << QString("Unable to open '%1' for reading: %2\n").arg(infile)
306  .arg(poFile.errorString());
307  return 2;
308  }
309 
310  QDomDocument wxl;
311  QTextStream po(&poFile);
312  po.setCodec(codec);
313  int n_strings = po2wxl(culture, &po, &wxl, &errorMessage);
314  if (n_strings < 0) {
315  error << QString("Unable to convert '%1': %2\n").arg(infile)
316  .arg(errorMessage);
317  return 3;
318  }
319 
320  /* Open the WXL file for writing. */
321  QFile wxlFile(outfile);
322  if (!wxlFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
323  error << QString("Unable to open '%1' for writing: %2\n").arg(outfile)
324  .arg(wxlFile.errorString());
325  return 4;
326  }
327 
328  /* Write the .wxl output. */
329  QTextStream out(&wxlFile);
330  out.setCodec(codec);
331  out << QString("<?xml version=\"1.0\" encoding=\"%1\"?>\n")
332  .arg(QString(codec->name()));
333  out << wxl.toString(4);
334 
335  if (!quiet) {
336  QTextStream results(stdout);
337  results << QString("Converted %1 strings from %2 to %3.\n").arg(n_strings)
338  .arg(infile)
339  .arg(outfile);
340  }
341  return 0;
342 }
343