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>. */
30 QDomElement
31 new_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>. */
47 QDomElement
48 new_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. */
75 QDomDocument
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. */
89 QString
90 parse_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. */
99 QString
100 parse_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. */
110 QString
111 parse_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>. */
125 QString
126 read_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. */
135 void
136 skip_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. */
151 int
152 po2ts(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. */
231 void
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 
245 int
246 main(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 
#define TS_ELEMENT_TRANSLATION
Definition: po2ts.cpp:24
DebugMessage error(const QString &fmt)
Definition: tcglobal.cpp:40
void skip_po_header(QTextStream *po)
Definition: po2ts.cpp:136
#define TS_ELEMENT_NAME
Definition: po2ts.cpp:21
QString parse_message_context(const QString &str)
Definition: po2ts.cpp:90
QDomDocument new_ts_document()
Definition: po2ts.cpp:76
QDomElement new_context_element(QDomDocument *ts, const QString &contextName)
Definition: po2ts.cpp:31
QDomElement new_message_element(QDomDocument *ts, const QString &msgid, const QString &msgstr)
Definition: po2ts.cpp:48
#define TS_ATTR_TRANSLATION_TYPE
Definition: po2ts.cpp:25
#define TS_DOCTYPE
Definition: po2ts.cpp:18
QString i(QString str)
Definition: html.cpp:32
#define TS_ELEMENT_MESSAGE
Definition: po2ts.cpp:22
#define TS_ELEMENT_SOURCE
Definition: po2ts.cpp:23
void print_usage_and_exit()
Definition: po2ts.cpp:232
#define TS_ELEMENT_ROOT
Definition: po2ts.cpp:19
#define TS_ELEMENT_CONTEXT
Definition: po2ts.cpp:20
QString parse_message_context_lame(const QString &str)
Definition: po2ts.cpp:100
int main(int argc, char *argv[])
Definition: po2ts.cpp:246
QString read_next_line(QTextStream *stream)
Definition: po2ts.cpp:126
#define TS_ATTR_VERSION
Definition: po2ts.cpp:26
int po2ts(QTextStream *po, QDomDocument *ts, QString *errorMessage)
Definition: po2ts.cpp:152
QString parse_message_string(const QString &msg)
Definition: po2ts.cpp:111