ts2po.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to the
00008 **  terms described in the LICENSE file.
00009 */
00010 
00011 #include <QFile>
00012 #include <QFileInfo>
00013 #include <QDomDocument>
00014 #include <QTextStream>
00015 #include <QTextCodec>
00016 #include <QDateTime>
00017 #include <stdlib.h>
00018 
00019 #include "ts2po_config.h"
00020 
00021 #define TS_DOCTYPE                    "TS"
00022 #define TS_ELEMENT_CONTEXT            "context"
00023 #define TS_ELEMENT_NAME               "name"
00024 #define TS_ELEMENT_MESSAGE            "message"
00025 #define TS_ELEMENT_SOURCE             "source"
00026 #define TS_ELEMENT_TRANSLATION        "translation"
00027 #define TS_ELEMENT_LOCATION           "location"
00028 #define TS_ATTR_FILENAME              "filename"
00029 #define TS_ATTR_LINE                  "line"
00030 
00031 
00032 /** Return the current time (in UTC) in the format YYYY-MM-DD HH:MM+0000. */
00033 QString
00034 create_po_timestamp()
00035 {
00036   QDateTime now = QDateTime::currentDateTime().toUTC();
00037   return now.toString("yyyy-MM-dd hh:mm+0000");
00038 }
00039 
00040 /** Return a header to be placed at the top of the .po file. The header will
00041  * include <b>encoding</b> in the Content-Type header line. */
00042 QString
00043 create_po_header(const QString &encoding)
00044 {
00045   QString header;
00046   QString tstamp = create_po_timestamp();
00047 
00048   header.append("msgid \"\"\n");
00049   header.append("msgstr \"\"\n");
00050   header.append("\"Project-Id-Version: "TS2PO_PROJECT_ID"\\n\"\n");
00051   header.append("\"Report-Msgid-Bugs-To: "TS2PO_CONTACT_ADDR"\\n\"\n");
00052   header.append(QString("\"POT-Creation-Date: %1\\n\"\n").arg(tstamp));
00053   header.append("\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n");
00054   header.append("\"Last-Translator: \\n\"\n");
00055   header.append("\"Language-Team: "TS2PO_LANGUAGE_TEAM"\\n\"\n");
00056   header.append("\"MIME-Version: 1.0\\n\"\n");
00057   header.append("\"Content-Type: text/plain; ");
00058   header.append(QString("charset=%1\\n\"\n").arg(encoding));
00059   header.append("\"Content-Transfer-Encoding: 8bit\\n\"\n");
00060   header.append("\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n");
00061   header.append("\"X-Generator: Vidalia ts2po "TS2PO_VERSION"\\n\"\n");
00062   header.append("\n");
00063 
00064   return header;
00065 }
00066 
00067 /** Parse the filename from the relative or absolute path given in
00068  * <b>filePath</b>. */
00069 QString
00070 parse_filename(const QString &filePath)
00071 {
00072   QFileInfo file(filePath);
00073   return file.fileName();
00074 }
00075 
00076 /** Convert the messages in <b>context</b> to PO format. The output will be
00077  * appended to <b>po</b>. Returns the number of source messages converted on
00078  * success, or -1 on error and <b>errorMessage</b> will be set. */
00079 int
00080 convert_context(const QDomElement &context, QString *po, QString *errorMessage)
00081 {
00082   QString msgctxt, msgid, msgstr;
00083   QString filename, line;
00084   QDomElement location, source, translation;
00085   int n = 0;
00086 
00087   Q_ASSERT(po);
00088   Q_ASSERT(errorMessage);
00089 
00090   QDomElement name = context.firstChildElement(TS_ELEMENT_NAME);
00091   if (name.isNull()) {
00092     *errorMessage = QString("context element with no name (line %1)")
00093                                                  .arg(context.lineNumber());
00094     return -1;
00095   }
00096   msgctxt = name.text();
00097 
00098   QDomElement msg = context.firstChildElement(TS_ELEMENT_MESSAGE);
00099   while (!msg.isNull()) {
00100     /* Extract the <source> tags */
00101     source = msg.firstChildElement(TS_ELEMENT_SOURCE);
00102     if (source.isNull()) {
00103       *errorMessage = QString("message element with no source string "
00104                               "(line %1)").arg(msg.lineNumber());
00105       return -1;
00106     }
00107     msgid = source.text();
00108     msgid.replace("\r", "");
00109     msgid.replace("\"", "\\\"");
00110     msgid.replace("\n", "\"\n\"");
00111 
00112     /* Extract the <translation> tags */
00113     translation = msg.firstChildElement(TS_ELEMENT_TRANSLATION);
00114     msgstr = translation.text();
00115     msgstr.replace("\r", "");
00116     msgstr.replace("\"", "\\\"");
00117     msgstr.replace("\n", "\"\n\"");
00118   
00119     /* Try to extract the <location> tags (optional) */
00120     location = msg.firstChildElement(TS_ELEMENT_LOCATION);
00121     filename = parse_filename(location.attribute(TS_ATTR_FILENAME));
00122     line = location.attribute(TS_ATTR_LINE);
00123 
00124     /* Format the .po entry for this string */
00125     if (!filename.isEmpty() && !line.isEmpty())
00126       (*po).append(QString("#: %1:%2\n").arg(filename).arg(line));
00127     (*po).append(QString("msgctxt \"%1\"\n").arg(msgctxt));
00128     (*po).append(QString("msgid \"%1\"\n").arg(msgid));
00129     (*po).append(QString("msgstr \"%1\"\n").arg(msgstr));
00130     (*po).append("\n");
00131   
00132     /* Find the next source message in the current context */
00133     msg = msg.nextSiblingElement(TS_ELEMENT_MESSAGE);
00134     n++;
00135   }
00136   return n;
00137 }
00138 
00139 /** Convert the TS-formatted document in <b>ts</b> to a PO-formatted document.
00140  * The output will be written to <b>po</b>, including a file header that
00141  * specifies <b>encoding</b> as the character set. Returns the number of strings
00142  * converted on success, or -1 on error and <b>errorMessage</b> will be set. */
00143 int
00144 ts2po(const QDomDocument *ts, QString *po, const QString &encoding,
00145       QString *errorMessage)
00146 {
00147   int n_strings = 0;
00148   QString context;
00149 
00150   Q_ASSERT(ts);
00151   Q_ASSERT(po);
00152   Q_ASSERT(errorMessage);
00153 
00154   /* Get the document root and check that it's valid */
00155   QDomElement root = ts->documentElement(); 
00156   if (root.tagName() != TS_DOCTYPE)
00157     return -1;
00158 
00159   /* Start with the PO header */
00160   *po = create_po_header(encoding);
00161 
00162   /* Iterate through all of the translation contexts and build up the PO file
00163    * output. */
00164   QDomElement child = root.firstChildElement(TS_ELEMENT_CONTEXT);
00165   while (!child.isNull()) {
00166     QString context;
00167   
00168     /* Convert the current .ts context to .po */
00169     int n = convert_context(child, &context, errorMessage);
00170     if (n < 0)
00171       return -1;
00172     
00173     /* Add it to the output file */
00174     (*po).append(context);
00175     n_strings += n;
00176 
00177     /* Move to the next context */
00178     child = child.nextSiblingElement(TS_ELEMENT_CONTEXT);
00179   }
00180   return n_strings;
00181 }
00182 
00183 /** Display application usage and exit. */
00184 void
00185 print_usage_and_exit()
00186 {
00187   QTextStream error(stderr);
00188   error << "usage: ts2po [-q] -i <infile.ts> -o <outfile.po> "
00189            "[-c <encoding>]\n";
00190   error << "  -q (optional)   Quiet mode (errors are still displayed)\n";
00191   error << "  -i <infile.ts>  Input .ts file\n";
00192   error << "  -o <outfile.po> Output .po file\n";
00193   error << "  -c <encoding>   Text encoding (default: utf-8)\n";
00194   error.flush();
00195   exit(1);
00196 }
00197 
00198 int
00199 main(int argc, char *argv[])
00200 {
00201   QTextStream error(stderr);
00202   QString errorMessage;
00203   char *infile, *outfile;
00204   QTextCodec *codec = QTextCodec::codecForName("utf-8");
00205   bool quiet = false;
00206   
00207   /* Check for the correct number of input parameters. */
00208   if (argc < 5 || argc > 8)
00209     print_usage_and_exit();
00210   for (int i = 1; i < argc; i++) {
00211     QString arg(argv[i]);
00212     if (!arg.compare("-q", Qt::CaseInsensitive))
00213       quiet = true;
00214     else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc)
00215       infile = argv[i];
00216     else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc)
00217       outfile = argv[i];
00218     else if (!arg.compare("-c", Qt::CaseInsensitive) && ++i < argc) {
00219       codec = QTextCodec::codecForName(argv[i]);
00220       if (!codec) {
00221         error << "Invalid text encoding specified.\n";
00222         return 1;
00223       }
00224     } else
00225       print_usage_and_exit(); 
00226   }
00227  
00228   /* Read and parse the input .ts file. */
00229   QDomDocument ts;
00230   QFile tsFile(infile);
00231   if (!ts.setContent(&tsFile, true, &errorMessage)) {
00232     error << QString("Unable to parse '%1': %2\n").arg(infile)
00233                                                   .arg(errorMessage);
00234     return 1;
00235   }
00236   
00237   /* Try to open the output .po file for writing. */
00238   QFile poFile(outfile);
00239   if (!poFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
00240     error << QString("Unable to open '%1' for writing: %2\n")
00241                                                    .arg(outfile)
00242                                                    .arg(tsFile.errorString());
00243     return 2;
00244   }
00245  
00246   /* Convert the input .ts file to a .po formatted file. */
00247   QString po;
00248   int n_strings = ts2po(&ts, &po, QString(codec->name()), &errorMessage);
00249   if (n_strings < 0) {
00250     error << QString("Unable to convert '%1' to '%2': %3\n").arg(infile)
00251                                                             .arg(outfile)
00252                                                             .arg(errorMessage);
00253     return 3;
00254   }
00255 
00256   /* Write the .po output. */
00257   QTextStream out(&poFile);
00258   out.setCodec(codec);
00259   out << po;
00260   poFile.close();
00261  
00262   if (!quiet) {
00263     QTextStream results(stdout);
00264     results << QString("Converted %1 strings from %2 to %3.\n").arg(n_strings)
00265                                                                .arg(infile)
00266                                                                .arg(outfile);
00267   }
00268   return 0;
00269 }
00270 

Generated on Wed Nov 26 21:02:42 2008 for Vidalia by  doxygen 1.5.7.1