Vidalia  0.3.1
nsh2po.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 #include <QHash>
12 #include <QFile>
13 #include <QTextStream>
14 #include <QTextCodec>
15 #include <QDateTime>
16 #include <QStringList>
17 #include <stdlib.h>
18 
19 #include "nsh2po_config.h"
20 
21 
22 /** Parse the context name from <b>str</b>, where the context name is of the
23  * form DQUOTE ContextName DQUOTE. */
24 QString
25 parse_message_context(const QString &str)
26 {
27  QString out = str.trimmed();
28  out = out.replace("\"", "");
29  return out;
30 }
31 
32 /** Parse the context name from <b>str</b>, where <b>str</b> is of the
33  * form ContextName#Number. This is the format used by translate-toolkit. */
34 QString
35 parse_message_context_lame(const QString &str)
36 {
37  if (str.contains("#"))
38  return str.section("#", 0, 0);
39  return QString();
40 }
41 
42 /** Parse the PO-formatted message string from <b>msg</b>. */
43 QString
44 parse_message_string(const QString &msg)
45 {
46  QString out = msg.trimmed();
47 
48  if (out.startsWith("\""))
49  out = out.remove(0, 1);
50  if (out.endsWith("\""))
51  out.chop(1);
52  out.replace("\\\"", "\"");
53  out.replace("\\r\\n", "\\n");
54  return out;
55 }
56 
57 /** Parse the NSIS-formatted LangString message from <b>msg</b>. */
58 QString
59 parse_nsh_langstring(const QString &msg)
60 {
61  QString out = msg.trimmed();
62 
63  if (out.startsWith("\""))
64  out = out.remove(0, 1);
65  if (out.endsWith("\""))
66  out.chop(1);
67  out.replace("$\\n", "\\n");
68  out.replace("$\\r", "");
69  out.replace("\\r", "");
70  return out;
71 }
72 
73 /** Return the current time (in UTC) in the format YYYY-MM-DD HH:MM+0000. */
74 QString
76 {
77  QDateTime now = QDateTime::currentDateTime().toUTC();
78  return now.toString("yyyy-MM-dd hh:mm+0000");
79 }
80 
81 /** Return a header to be placed at the top of the .po file. */
82 QString
83 create_po_header(const QString &charset)
84 {
85  QString header;
86  QString tstamp = create_po_timestamp();
87 
88  header.append("msgid \"\"\n");
89  header.append("msgstr \"\"\n");
90  header.append("\"Project-Id-Version: "NSH2PO_PROJECT_ID"\\n\"\n");
91  header.append("\"Report-Msgid-Bugs-To: "NSH2PO_CONTACT_ADDR"\\n\"\n");
92  header.append(QString("\"POT-Creation-Date: %1\\n\"\n").arg(tstamp));
93  header.append("\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n");
94  header.append("\"Last-Translator: \\n\"\n");
95  header.append("\"Language-Team: "NSH2PO_LANGUAGE_TEAM"\\n\"\n");
96  header.append("\"MIME-Version: 1.0\\n\"\n");
97  header.append(QString("\"Content-Type: text/plain; "
98  "charset=%1\\n\"\n").arg(charset));
99  header.append("\"Content-Transfer-Encoding: 8bit\\n\"\n");
100  header.append("\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n");
101  header.append("\"X-Generator: Vidalia nsh2po "NSH2PO_VERSION"\\n\"\n");
102  header.append("\n");
103 
104  return header;
105 }
106 
107 /** Read and return the next non-empty line from <b>stream</b>. */
108 QString
109 read_next_line(QTextStream *stream)
110 {
111  Q_ASSERT(stream);
112  stream->skipWhiteSpace();
113  return stream->readLine();
114 }
115 
116 /** Skip past the header portion of the POT file and any leading whitespace.
117  * The next line read from <b>po</b> will be the first non-header line in the
118  * document. */
119 void
120 skip_pot_header(QTextStream *pot)
121 {
122  QString line;
123  /* Skip any leading whitespace before the header */
124  pot->skipWhiteSpace();
125  /* Read to the first empty line */
126  line = pot->readLine();
127  while (!pot->atEnd() && !line.isEmpty())
128  line = pot->readLine();
129 }
130 
131 /** Parse a PO template file for (context,source string) pairs, which are
132  * be stored in <b>out</b> using <i>msgctxt</i> as the key and <i>msgid</i>
133  * as the value. Return true on success, or false on failure and set
134  * <b>errmsg</b>. */
135 bool
136 parse_po_template(QTextStream *pot, QHash<QString,QString> *out,
137  QString *errmsg)
138 {
139  QString line, msgctxt, msgid;
140 
141  skip_pot_header(pot);
142  line = read_next_line(pot);
143  while (!pot->atEnd()) {
144  if (!line.startsWith("#:") && !line.startsWith("msgctxt")) {
145  /* Skip to the start of the next message entry */
146  line = read_next_line(pot);
147  continue;
148  }
149 
150  if (line.startsWith("#:")) {
151  /* Context was specified with the stupid overloaded "#:" syntax.*/
152  msgctxt = line.section(" ", 1);
153  msgctxt = parse_message_context_lame(msgctxt);
154  line = read_next_line(pot);
155  continue;
156  }
157 
158  if (line.startsWith("msgctxt ")) {
159  /* A context specified on a "msgctxt" line takes precedence over a
160  * context specified using the overload "#:" notation. */
161  msgctxt = line.section(" ", 1);
162  msgctxt = parse_message_context(msgctxt);
163  line = read_next_line(pot);
164  }
165 
166  if (!line.startsWith("msgid ")) {
167  *errmsg = "expected 'msgid' line";
168  return false;
169  }
170  msgid = line.section(" ", 1);
171 
172  line = read_next_line(pot);
173  while (line.startsWith("\"")) {
174  /* This msgid line had multiple parts to it */
175  msgid.append(line);
176  line = read_next_line(pot);
177  }
178  msgid = parse_message_string(msgid);
179 
180  out->insert(msgctxt, msgid);
181  }
182 
183  return true;
184 }
185 
186 /** Read an NSIS-formatted file containing LangString entries from <b>nsh</b>.
187  * If a LangString entry has a corresponding entry in <b>pot</b>, then the
188  * message entry is PO-formatted and appended to <b>po</b>. Return true on
189  * success, or false on failure and <b>errmsg</b> will be set. */
190 int
191 nsh2po(QTextStream *nsh, const QString &charset,
192  const QHash<QString,QString> &pot, QString *po, QString *errmsg)
193 {
194  QString line, msgctxt, msgid, msgstr;
195  QStringList parts;
196  QHash<QString,QString> langStrings;
197  int idx, n_strings;
198 
199  *po = create_po_header(charset);
200 
201  /* Parse the translated strings from the NSH file */
202  while (!nsh->atEnd()) {
203  line = read_next_line(nsh);
204  if (!line.startsWith("LangString "))
205  continue;
206 
207  parts = line.split(" ");
208  if (parts.size() > 3)
209  msgctxt = parts.at(1);
210  else
211  continue; /* Not properly formatted */
212 
213  idx = line.indexOf("\"");
214  if (idx > 0)
215  msgstr = parse_nsh_langstring(line.mid(idx));
216  langStrings.insert(msgctxt, msgstr);
217  }
218 
219  /* Format the PO file based on the template. */
220  n_strings = 0;
221  foreach (QString msgctxt, pot.keys()) {
222  msgid = pot.value(msgctxt);
223  if (langStrings.contains(msgctxt)) {
224  msgstr = langStrings.value(msgctxt);
225  n_strings++;
226  } else {
227  msgstr = msgid;
228  }
229 
230  po->append(QString("msgctxt \"%1\"\n").arg(msgctxt));
231  po->append(QString("msgid \"%1\"\n").arg(msgid));
232  po->append(QString("msgstr \"%1\"\n").arg(msgstr));
233  po->append("\n");
234  }
235  return n_strings;
236 }
237 
238 /** Write <b>po</b> to <b>poFileName</b> using <b>codec</b>. Return true on
239  * success. On failure, return false and set <b>errmsg</b> to the reason for
240  * failure. */
241 bool
242 write_po_output(const char *poFileName, const QString &po, QTextCodec *codec,
243  QString *errmsg)
244 {
245  QFile poFile(poFileName);
246  if (!poFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
247  *errmsg = QString("Unable to open '%1' for writing.").arg(poFileName);
248  return false;
249  }
250 
251  QTextStream out(&poFile);
252  out.setCodec(codec);
253  out << po;
254  return true;
255 }
256 
257 /** Display application usage and exit. */
258 void
260 {
261  QTextStream error(stderr);
262  error << "usage: nsh2po [-q] -t <template.pot> -i <infile.nsh> "
263  "-o <outfile.po> [-c <encoding>]\n";
264  error << " -q (optional) Quiet mode (errors are still displayed)\n";
265  error << " -t <template.pot> PO template file\n";
266  error << " -i <infile.ts> Input .ts file\n";
267  error << " -o <outfile.po> Output .po file\n";
268  error << " -c <encoding> Text encoding (default: utf-8)\n";
269  error.flush();
270  exit(1);
271 }
272 
273 int
274 main(int argc, char *argv[])
275 {
276  QTextStream error(stderr);
277  QString po, errorMessage;
278  char *outFileName;
279  QFile potFile, nshFile;
280  QTextStream pot, nsh;
281  QTextCodec *codec = QTextCodec::codecForName("utf-8");
282  bool quiet = false;
283 
284  /* Check for the correct number of input parameters. */
285  if (argc < 7 || argc > 10)
287  for (int i = 1; i < argc; i++) {
288  QString arg(argv[i]);
289  if (!arg.compare("-q", Qt::CaseInsensitive)) {
290  quiet = true;
291  } else if (!arg.compare("-t", Qt::CaseInsensitive) && ++i < argc) {
292  /* Open the input PO template file */
293  potFile.setFileName(argv[i]);
294  if (!potFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
295  error << QString("Couldn't open '%1' for reading: ").arg(argv[i])
296  << potFile.errorString();
297  return 1;
298  }
299  pot.setDevice(&potFile);
300  } else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc) {
301  /* Open the input NSH file */
302  nshFile.setFileName(argv[i]);
303  if (!nshFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
304  error << QString("Couldn't open '%1' for reading: ").arg(argv[i])
305  << nshFile.errorString();
306  return 1;
307  }
308  nsh.setDevice(&nshFile);
309  } else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc) {
310  outFileName = argv[i];
311  } else if (!arg.compare("-c", Qt::CaseInsensitive) && ++i < argc) {
312  /* Set the text encoding used for input and output */
313  codec = QTextCodec::codecForName(argv[i]);
314  if (!codec) {
315  error << "Invalid text encoding specified.\n";
316  return 1;
317  }
318  } else
320  }
321  pot.setCodec(codec);
322  nsh.setCodec(codec);
323 
324  /* Parse the template for the source strings */
325  QHash<QString,QString> poTemplate;
326  if (!parse_po_template(&pot, &poTemplate, &errorMessage)) {
327  error << QString("Failed to parse PO template: %1\n").arg(errorMessage);
328  return 1;
329  }
330 
331  /* Parse the nsh for the translated strings */
332  int n_strings = nsh2po(&nsh, QString(codec->name()), poTemplate,
333  &po, &errorMessage);
334  if (n_strings < 0) {
335  error << QString("Conversion failed: %1\n").arg(errorMessage);
336  return 2;
337  }
338 
339  /* Write the formatted PO output */
340  if (!write_po_output(outFileName, po, codec, &errorMessage)) {
341  error << QString("Failed to write PO output: %1\n").arg(errorMessage);
342  return 3;
343  }
344 
345  if (!quiet) {
346  QTextStream out(stdout);
347  out << QString("Wrote %1 strings to '%2'.\n").arg(n_strings)
348  .arg(outFileName);
349  }
350  return 0;
351 }
352 
DebugMessage error(const QString &fmt)
Definition: tcglobal.cpp:40
bool write_po_output(const char *poFileName, const QString &po, QTextCodec *codec, QString *errmsg)
Definition: nsh2po.cpp:242
QString parse_message_context(const QString &str)
Definition: nsh2po.cpp:25
bool parse_po_template(QTextStream *pot, QHash< QString, QString > *out, QString *errmsg)
Definition: nsh2po.cpp:136
int main(int argc, char *argv[])
Definition: nsh2po.cpp:274
QString parse_nsh_langstring(const QString &msg)
Definition: nsh2po.cpp:59
QString read_next_line(QTextStream *stream)
Definition: nsh2po.cpp:109
QString create_po_timestamp()
Definition: nsh2po.cpp:75
QString parse_message_string(const QString &msg)
Definition: nsh2po.cpp:44
QString i(QString str)
Definition: html.cpp:32
QString create_po_header(const QString &charset)
Definition: nsh2po.cpp:83
void print_usage_and_exit()
Definition: nsh2po.cpp:259
QString parse_message_context_lame(const QString &str)
Definition: nsh2po.cpp:35
int nsh2po(QTextStream *nsh, const QString &charset, const QHash< QString, QString > &pot, QString *po, QString *errmsg)
Definition: nsh2po.cpp:191
void skip_pot_header(QTextStream *pot)
Definition: nsh2po.cpp:120