Vidalia 0.3.1
po2ts.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 <QDomDocument>
14#include <QTextStream>
15#include <QTextCodec>
16#include <stdlib.h>
17
18#define TS_DOCTYPE "TS"
19#define TS_ELEMENT_ROOT "TS"
20#define TS_ELEMENT_CONTEXT "context"
21#define TS_ELEMENT_NAME "name"
22#define TS_ELEMENT_MESSAGE "message"
23#define TS_ELEMENT_SOURCE "source"
24#define TS_ELEMENT_TRANSLATION "translation"
25#define TS_ATTR_TRANSLATION_TYPE "type"
26#define TS_ATTR_VERSION "version"
27
28
29/** Create a new context element with the name <b>contextName</b>. */
30QDomElement
31new_context_element(QDomDocument *ts, const QString &contextName)
32{
33 QDomElement context, name;
34
35 /* Create a <name> element */
36 name = ts->createElement(TS_ELEMENT_NAME);
37 name.appendChild(ts->createTextNode(contextName));
38
39 /* Create a <context> element and add the <name> element as a child */
40 context = ts->createElement(TS_ELEMENT_CONTEXT);
41 context.appendChild(name);
42 return context;
43}
44
45/** Create a new message element using the source string <b>msgid</b> and the
46 * translation <b>msgstr</b>. */
47QDomElement
48new_message_element(QDomDocument *ts,
49 const QString &msgid, const QString &msgstr)
50{
51 QDomElement message, source, translation;
52
53 /* Create and set the <source> element */
54 source = ts->createElement(TS_ELEMENT_SOURCE);
55 source.appendChild(ts->createTextNode(msgid));
56
57 /* Create and set the <translation> element */
58 translation = ts->createElement(TS_ELEMENT_TRANSLATION);
59 if (!msgstr.isEmpty())
60 translation.appendChild(ts->createTextNode(msgstr));
61 else
62 translation.setAttribute(TS_ATTR_TRANSLATION_TYPE, "unfinished");
63
64 /* Create a <message> element and add the <source> and <translation>
65 * elements as children */
66 message = ts->createElement(TS_ELEMENT_MESSAGE);
67 message.appendChild(source);
68 message.appendChild(translation);
69
70 return message;
71}
72
73/** Create a new TS document of the appropriate doctype and with a TS root
74 * element. */
75QDomDocument
77{
78 QDomDocument ts(TS_DOCTYPE);
79
80 QDomElement root = ts.createElement(TS_ELEMENT_ROOT);
81 root.setAttribute(TS_ATTR_VERSION, "1.1");
82 ts.appendChild(root);
83
84 return ts;
85}
86
87/** Parse the context name from <b>str</b>, where the context name is of the
88 * form DQUOTE ContextName DQUOTE. */
89QString
90parse_message_context(const QString &str)
91{
92 QString out = str.trimmed();
93 out = out.replace("\"", "");
94 return out;
95}
96
97/** Parse the context name from <b>str</b>, where <b>str</b> is of the
98 * form ContextName#Number. This is the format used by translate-toolkit. */
99QString
100parse_message_context_lame(const QString &str)
101{
102 if (str.contains("#"))
103 return str.section("#", 0, 0);
104 return QString();
105}
106
107/** Parse the PO-formatted message string from <b>msg</b>. If <b>msg</b> is a
108 * multiline string, the extra double quotes will be replaced with newlines
109 * appropriately. */
110QString
111parse_message_string(const QString &msg)
112{
113 QString out = msg.trimmed();
114
115 out.replace("\"\n\"", "");
116 if (out.startsWith("\""))
117 out = out.remove(0, 1);
118 if (out.endsWith("\""))
119 out.chop(1);
120 out.replace("\\\"", "\"");
121 return out;
122}
123
124/** Read and return the next non-empty line from <b>stream</b>. */
125QString
126read_next_line(QTextStream *stream)
127{
128 stream->skipWhiteSpace();
129 return stream->readLine().append("\n");
130}
131
132/** Skip past the header portion of the PO file and any leading whitespace.
133 * The next line read from <b>po</b> will be the first non-header line in the
134 * document. */
135void
136skip_po_header(QTextStream *po)
137{
138 QString line;
139 /* Skip any leading whitespace before the header */
140 po->skipWhiteSpace();
141 /* Read to the first empty line */
142 line = po->readLine();
143 while (!po->atEnd() && !line.isEmpty())
144 line = po->readLine();
145}
146
147/** Convert <b>po</b> from the PO format to a TS-formatted XML document.
148 * <b>ts</b> will be set to the resulting TS document. Return the number of
149 * converted strings on success, or -1 on error and <b>errorMessage</b> will
150 * be set. */
151int
152po2ts(QTextStream *po, QDomDocument *ts, QString *errorMessage)
153{
154 QString line;
155 QString msgctxt, msgid, msgstr;
156 QHash<QString,QDomElement> contextElements;
157 QDomElement contextElement, msgElement, transElement;
158 int n_strings = 0;
159
160 Q_ASSERT(po);
161 Q_ASSERT(ts);
162 Q_ASSERT(errorMessage);
163
164 *ts = new_ts_document();
165
166 skip_po_header(po);
167 line = read_next_line(po);
168 while (!po->atEnd()) {
169 /* Ignore all "#" lines except "#:" */
170 while (line.startsWith("#")) {
171 if (line.startsWith("#:")) {
172 /* Context was specified with the stupid overloaded "#:" syntax.*/
173 msgctxt = line.section(" ", 1);
174 msgctxt = parse_message_context_lame(msgctxt);
175 }
176 line = read_next_line(po);
177 }
178
179 /* A context specified on a "msgctxt" line takes precedence over a context
180 * specified using the overload "#:" notation. */
181 if (line.startsWith("msgctxt ")) {
182 msgctxt = line.section(" ", 1);
183 msgctxt = parse_message_context(msgctxt);
184 line = read_next_line(po);
185 }
186
187 /* Parse the (possibly multiline) message source string */
188 if (!line.startsWith("msgid ")) {
189 *errorMessage = "expected 'msgid' line";
190 return -1;
191 }
192 msgid = line.section(" ", 1);
193
194 line = read_next_line(po);
195 while (line.startsWith("\"")) {
196 msgid.append(line);
197 line = read_next_line(po);
198 }
199 msgid = parse_message_string(msgid);
200
201 /* Parse the (possibly multiline) translated string */
202 if (!line.startsWith("msgstr ")) {
203 *errorMessage = "expected 'msgstr' line";
204 return -1;
205 }
206 msgstr = line.section(" ", 1);
207
208 line = read_next_line(po);
209 while (line.startsWith("\"")) {
210 msgstr.append(line);
211 line = read_next_line(po);
212 }
213 msgstr = parse_message_string(msgstr);
214
215 /* Add the message and translation to the .ts document */
216 if (contextElements.contains(msgctxt)) {
217 contextElement = contextElements.value(msgctxt);
218 } else {
219 contextElement = new_context_element(ts, msgctxt);
220 ts->documentElement().appendChild(contextElement);
221 contextElements.insert(msgctxt, contextElement);
222 }
223 contextElement.appendChild(new_message_element(ts, msgid, msgstr));
224
225 n_strings++;
226 }
227 return n_strings;
228}
229
230/** Display application usage and exit. */
231void
233{
234 QTextStream error(stderr);
235 error << "usage: po2ts [-q] -i <infile.po> -o <outfile.ts> "
236 "[-c <encoding>]\n";
237 error << " -q (optional) Quiet mode (errors are still displayed)\n";
238 error << " -i <infile.po> Input .po file\n";
239 error << " -o <outfile.ts> Output .ts file\n";
240 error << " -c <encoding> Text encoding (default: utf-8)\n";
241 error.flush();
242 exit(1);
243}
244
245int
246main(int argc, char *argv[])
247{
248 QTextStream error(stderr);
249 QString errorMessage;
250 char *infile, *outfile;
251 QTextCodec *codec = QTextCodec::codecForName("utf-8");
252 bool quiet = false;
253
254 /* Check for the correct number of input parameters. */
255 if (argc < 5 || argc > 8)
257 for (int i = 1; i < argc; i++) {
258 QString arg(argv[i]);
259 if (!arg.compare("-q", Qt::CaseInsensitive))
260 quiet = true;
261 else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc)
262 infile = argv[i];
263 else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc)
264 outfile = argv[i];
265 else if (!arg.compare("-c", Qt::CaseInsensitive) && ++i < argc) {
266 codec = QTextCodec::codecForName(argv[i]);
267 if (!codec) {
268 error << "Invalid text encoding specified\n";
269 return 1;
270 }
271 } else
273 }
274
275 /* Open the input PO file for reading. */
276 QFile poFile(infile);
277 if (!poFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
278 error << QString("Unable to open '%1' for reading: %2\n").arg(infile)
279 .arg(poFile.errorString());
280 return 2;
281 }
282
283 QDomDocument ts;
284 QTextStream po(&poFile);
285 po.setCodec(codec);
286 int n_strings = po2ts(&po, &ts, &errorMessage);
287 if (n_strings < 0) {
288 error << QString("Unable to convert '%1': %2\n").arg(infile)
289 .arg(errorMessage);
290 return 3;
291 }
292
293 /* Open the TS file for writing. */
294 QFile tsFile(outfile);
295 if (!tsFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
296 error << QString("Unable to open '%1' for writing: %2\n").arg(outfile)
297 .arg(tsFile.errorString());
298 return 4;
299 }
300
301 /* Write the .ts output. */
302 QTextStream out(&tsFile);
303 out.setCodec(codec);
304 out << QString("<?xml version=\"1.0\" encoding=\"%1\"?>\n")
305 .arg(QString(codec->name()));
306 out << ts.toString(4);
307
308 if (!quiet) {
309 QTextStream results(stdout);
310 results << QString("Converted %1 strings from %2 to %3.\n").arg(n_strings)
311 .arg(infile)
312 .arg(outfile);
313 }
314 return 0;
315}
316
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: po2ts.cpp:100
void print_usage_and_exit()
Definition: po2ts.cpp:232
#define TS_ELEMENT_MESSAGE
Definition: po2ts.cpp:22
int main(int argc, char *argv[])
Definition: po2ts.cpp:246
QString parse_message_string(const QString &msg)
Definition: po2ts.cpp:111
#define TS_ATTR_TRANSLATION_TYPE
Definition: po2ts.cpp:25
QString read_next_line(QTextStream *stream)
Definition: po2ts.cpp:126
QString parse_message_context(const QString &str)
Definition: po2ts.cpp:90
#define TS_ELEMENT_SOURCE
Definition: po2ts.cpp:23
int po2ts(QTextStream *po, QDomDocument *ts, QString *errorMessage)
Definition: po2ts.cpp:152
#define TS_ELEMENT_NAME
Definition: po2ts.cpp:21
QDomElement new_context_element(QDomDocument *ts, const QString &contextName)
Definition: po2ts.cpp:31
#define TS_ELEMENT_ROOT
Definition: po2ts.cpp:19
void skip_po_header(QTextStream *po)
Definition: po2ts.cpp:136
QDomDocument new_ts_document()
Definition: po2ts.cpp:76
#define TS_DOCTYPE
Definition: po2ts.cpp:18
#define TS_ELEMENT_TRANSLATION
Definition: po2ts.cpp:24
#define TS_ELEMENT_CONTEXT
Definition: po2ts.cpp:20
#define TS_ATTR_VERSION
Definition: po2ts.cpp:26
QDomElement new_message_element(QDomDocument *ts, const QString &msgid, const QString &msgstr)
Definition: po2ts.cpp:48