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. */
24QString
25parse_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. */
34QString
35parse_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>. */
43QString
44parse_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>. */
58QString
59parse_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. */
74QString
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. */
82QString
83create_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>. */
108QString
109read_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. */
119void
120skip_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>. */
135bool
136parse_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. */
190int
191nsh2po(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. */
241bool
242write_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. */
258void
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
273int
274main(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 arg(const QString &a)
Definition: tcglobal.h:48
QString i(QString str)
Definition: html.cpp:32
DebugMessage error(const QString &fmt)
Definition: tcglobal.cpp:40
QString parse_message_context_lame(const QString &str)
Definition: nsh2po.cpp:35
void print_usage_and_exit()
Definition: nsh2po.cpp:259
int main(int argc, char *argv[])
Definition: nsh2po.cpp:274
QString parse_message_string(const QString &msg)
Definition: nsh2po.cpp:44
QString read_next_line(QTextStream *stream)
Definition: nsh2po.cpp:109
QString parse_message_context(const QString &str)
Definition: nsh2po.cpp:25
QString create_po_timestamp()
Definition: nsh2po.cpp:75
bool parse_po_template(QTextStream *pot, QHash< QString, QString > *out, QString *errmsg)
Definition: nsh2po.cpp:136
QString create_po_header(const QString &charset)
Definition: nsh2po.cpp:83
bool write_po_output(const char *poFileName, const QString &po, QTextCodec *codec, QString *errmsg)
Definition: nsh2po.cpp:242
QString parse_nsh_langstring(const QString &msg)
Definition: nsh2po.cpp:59
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