Vidalia 0.3.1
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. */
29QString
30culture_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. */
69QDomElement
70new_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. */
92QDomDocument
93new_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. */
106QString
107parse_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. */
116QString
117parse_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. */
127QString
128parse_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>. */
150QString
151read_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. */
160void
161skip_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. */
176int
177po2wxl(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. */
255void
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
270int
271main(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
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: po2wxl.cpp:117
void print_usage_and_exit()
Definition: po2wxl.cpp:256
int main(int argc, char *argv[])
Definition: po2wxl.cpp:271
QString culture_lcid(const QString &culture)
Definition: po2wxl.cpp:30
QString parse_message_string(const QString &msg)
Definition: po2wxl.cpp:128
QString read_next_line(QTextStream *stream)
Definition: po2wxl.cpp:151
QString parse_message_context(const QString &str)
Definition: po2wxl.cpp:107
QDomDocument new_wxl_document(const QString &culture)
Definition: po2wxl.cpp:93
#define WXL_ELEMENT_ROOT
Definition: po2wxl.cpp:20
#define WXL_ATTR_LANGUAGE
Definition: po2wxl.cpp:23
#define WXL_NAMESPACE
Definition: po2wxl.cpp:19
void skip_po_header(QTextStream *po)
Definition: po2wxl.cpp:161
QDomElement new_message_element(QDomDocument *wxl, const QString &strid, const QString &msgid, const QString &msgstr)
Definition: po2wxl.cpp:70
int po2wxl(const QString &culture, QTextStream *po, QDomDocument *wxl, QString *errorMessage)
Definition: po2wxl.cpp:177
#define WXL_ATTR_OVERRIDABLE
Definition: po2wxl.cpp:25
#define WXL_ELEMENT_MESSAGE
Definition: po2wxl.cpp:21
#define WXL_ATTR_MESSAGE_ID
Definition: po2wxl.cpp:22
#define WXL_ATTR_TRANSLATION_TYPE
Definition: po2wxl.cpp:24