nsh2po.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 <QHash>
00012 #include <QFile>
00013 #include <QTextStream>
00014 #include <QTextCodec>
00015 #include <QDateTime>
00016 #include <QStringList>
00017 #include <stdlib.h>
00018 
00019 #include "nsh2po_config.h"
00020 
00021 
00022 /** Parse the context name from <b>str</b>, where the context name is of the
00023  * form DQUOTE ContextName DQUOTE. */
00024 QString
00025 parse_message_context(const QString &str)
00026 {
00027   QString out = str.trimmed();
00028   out = out.replace("\"", "");
00029   return out;
00030 }
00031 
00032 /** Parse the context name from <b>str</b>, where <b>str</b> is of the
00033  * form ContextName#Number. This is the format used by translate-toolkit. */
00034 QString
00035 parse_message_context_lame(const QString &str)
00036 {
00037   if (str.contains("#"))
00038     return str.section("#", 0, 0);
00039   return QString();
00040 }
00041 
00042 /** Parse the PO-formatted message string from <b>msg</b>. */
00043 QString
00044 parse_message_string(const QString &msg)
00045 {
00046   QString out = msg.trimmed(); 
00047   
00048   if (out.startsWith("\""))
00049     out = out.remove(0, 1);
00050   if (out.endsWith("\""))
00051     out.chop(1);
00052   out.replace("\\\"", "\"");
00053   out.replace("\\r\\n", "\\n");
00054   return out;
00055 }
00056 
00057 /** Parse the NSIS-formatted LangString message from <b>msg</b>. */
00058 QString
00059 parse_nsh_langstring(const QString &msg)
00060 {
00061   QString out = msg.trimmed();
00062   
00063   if (out.startsWith("\""))
00064     out = out.remove(0, 1);
00065   if (out.endsWith("\""))
00066     out.chop(1);
00067   out.replace("$\\n", "\\n");
00068   out.replace("$\\r", "");
00069   out.replace("\\r",  "");
00070   return out;
00071 }
00072 
00073 /** Return the current time (in UTC) in the format YYYY-MM-DD HH:MM+0000. */
00074 QString
00075 create_po_timestamp()
00076 {
00077   QDateTime now = QDateTime::currentDateTime().toUTC();
00078   return now.toString("yyyy-MM-dd hh:mm+0000");
00079 }
00080 
00081 /** Return a header to be placed at the top of the .po file. */
00082 QString
00083 create_po_header(const QString &charset)
00084 {
00085   QString header;
00086   QString tstamp = create_po_timestamp();
00087 
00088   header.append("msgid \"\"\n");
00089   header.append("msgstr \"\"\n");
00090   header.append("\"Project-Id-Version: "NSH2PO_PROJECT_ID"\\n\"\n");
00091   header.append("\"Report-Msgid-Bugs-To: "NSH2PO_CONTACT_ADDR"\\n\"\n");
00092   header.append(QString("\"POT-Creation-Date: %1\\n\"\n").arg(tstamp));
00093   header.append("\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n");
00094   header.append("\"Last-Translator: \\n\"\n");
00095   header.append("\"Language-Team: "NSH2PO_LANGUAGE_TEAM"\\n\"\n");
00096   header.append("\"MIME-Version: 1.0\\n\"\n");
00097   header.append(QString("\"Content-Type: text/plain; "
00098                         "charset=%1\\n\"\n").arg(charset));
00099   header.append("\"Content-Transfer-Encoding: 8bit\\n\"\n");
00100   header.append("\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n");
00101   header.append("\"X-Generator: Vidalia nsh2po "NSH2PO_VERSION"\\n\"\n");
00102   header.append("\n");
00103 
00104   return header;
00105 }
00106 
00107 /** Read and return the next non-empty line from <b>stream</b>. */
00108 QString
00109 read_next_line(QTextStream *stream)
00110 {
00111   Q_ASSERT(stream);
00112   stream->skipWhiteSpace();
00113   return stream->readLine();
00114 }
00115 
00116 /** Skip past the header portion of the POT file and any leading whitespace. 
00117  * The next line read from <b>po</b> will be the first non-header line in the
00118  * document. */
00119 void
00120 skip_pot_header(QTextStream *pot)
00121 {
00122   QString line;
00123   /* Skip any leading whitespace before the header */
00124   pot->skipWhiteSpace();
00125   /* Read to the first empty line */
00126   line = pot->readLine();
00127   while (!pot->atEnd() && !line.isEmpty())
00128     line = pot->readLine();
00129 }
00130 
00131 /** Parse a PO template file for (context,source string) pairs, which are
00132  * be stored in <b>out</b> using <i>msgctxt</i> as the key and <i>msgid</i>
00133  * as the value. Return true on success, or false on failure and set 
00134  * <b>errmsg</b>. */
00135 bool
00136 parse_po_template(QTextStream *pot, QHash<QString,QString> *out,
00137                   QString *errmsg)
00138 {
00139   QString line, msgctxt, msgid;
00140   
00141   skip_pot_header(pot);
00142   line = read_next_line(pot);
00143   while (!pot->atEnd()) {
00144     if (!line.startsWith("#:") && !line.startsWith("msgctxt")) {
00145       /* Skip to the start of the next message entry */
00146       line = read_next_line(pot);
00147       continue;
00148     }
00149     
00150     if (line.startsWith("#:")) {
00151       /* Context was specified with the stupid overloaded "#:" syntax.*/
00152       msgctxt = line.section(" ", 1);
00153       msgctxt = parse_message_context_lame(msgctxt);
00154       line = read_next_line(pot);
00155       continue;
00156     }
00157 
00158     if (line.startsWith("msgctxt ")) {
00159       /* A context specified on a "msgctxt" line takes precedence over a
00160        * context specified using the overload "#:" notation. */
00161       msgctxt = line.section(" ", 1);
00162       msgctxt = parse_message_context(msgctxt);
00163       line = read_next_line(pot);
00164     }
00165     
00166     if (!line.startsWith("msgid ")) {
00167       *errmsg = "expected 'msgid' line";
00168       return false;
00169     }
00170     msgid = line.section(" ", 1);
00171     
00172     line = read_next_line(pot);
00173     while (line.startsWith("\"")) {
00174       /* This msgid line had multiple parts to it */
00175       msgid.append(line);
00176       line = read_next_line(pot);
00177     }
00178     msgid = parse_message_string(msgid);
00179 
00180     out->insert(msgctxt, msgid);
00181   }
00182   
00183   return true;
00184 }
00185 
00186 /** Read an NSIS-formatted file containing LangString entries from <b>nsh</b>.
00187  * If a LangString entry has a corresponding entry in <b>pot</b>, then the
00188  * message entry is PO-formatted and appended to <b>po</b>. Return true on
00189  * success, or false on failure and <b>errmsg</b> will be set. */
00190 int
00191 nsh2po(QTextStream *nsh, const QString &charset, 
00192        const QHash<QString,QString> &pot, QString *po, QString *errmsg)
00193 {
00194   QString line, msgctxt, msgid, msgstr;
00195   QStringList parts;
00196   QHash<QString,QString> langStrings;
00197   int idx, n_strings;
00198 
00199   *po = create_po_header(charset);
00200 
00201   /* Parse the translated strings from the NSH file */
00202   while (!nsh->atEnd()) {
00203     line = read_next_line(nsh);
00204     if (!line.startsWith("LangString "))
00205       continue;
00206 
00207     parts = line.split(" ");
00208     if (parts.size() > 3)
00209       msgctxt = parts.at(1);
00210     else
00211       continue; /* Not properly formatted */
00212       
00213     idx = line.indexOf("\"");
00214     if (idx > 0)
00215       msgstr = parse_nsh_langstring(line.mid(idx));
00216     langStrings.insert(msgctxt, msgstr);
00217   }
00218 
00219   /* Format the PO file based on the template. */
00220   n_strings = 0;  
00221   foreach (QString msgctxt, pot.keys()) {
00222     msgid = pot.value(msgctxt);
00223     if (langStrings.contains(msgctxt)) {
00224       msgstr = langStrings.value(msgctxt);
00225       n_strings++;
00226     } else {
00227       msgstr = msgid;
00228     }
00229     
00230     po->append(QString("msgctxt \"%1\"\n").arg(msgctxt));
00231     po->append(QString("msgid \"%1\"\n").arg(msgid));
00232     po->append(QString("msgstr \"%1\"\n").arg(msgstr));
00233     po->append("\n");
00234   }
00235   return n_strings;
00236 }
00237 
00238 /** Write <b>po</b> to <b>poFileName</b> using <b>codec</b>. Return true on
00239  * success. On failure, return false and set <b>errmsg</b> to the reason for
00240  * failure. */
00241 bool
00242 write_po_output(const char *poFileName, const QString &po, QTextCodec *codec,
00243                 QString *errmsg)
00244 {
00245   QFile poFile(poFileName);
00246   if (!poFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
00247     *errmsg = QString("Unable to open '%1' for writing.").arg(poFileName);
00248     return false;
00249   }
00250   
00251   QTextStream out(&poFile);
00252   out.setCodec(codec);
00253   out << po;
00254   return true;
00255 }
00256 
00257 /** Display application usage and exit. */
00258 void
00259 print_usage_and_exit()
00260 {
00261   QTextStream error(stderr);
00262   error << "usage: nsh2po [-q] -t <template.pot> -i <infile.nsh> "
00263            "-o <outfile.po> [-c <encoding>]\n";
00264   error << "  -q (optional)       Quiet mode (errors are still displayed)\n";
00265   error << "  -t <template.pot>   PO template file\n";
00266   error << "  -i <infile.ts>      Input .ts file\n";
00267   error << "  -o <outfile.po>     Output .po file\n";
00268   error << "  -c <encoding>       Text encoding (default: utf-8)\n";
00269   error.flush();
00270   exit(1);
00271 }
00272 
00273 int
00274 main(int argc, char *argv[])
00275 {
00276   QTextStream error(stderr);
00277   QString po, errorMessage;
00278   char *outFileName;
00279   QFile potFile, nshFile;
00280   QTextStream pot, nsh;
00281   QTextCodec *codec = QTextCodec::codecForName("utf-8");
00282   bool quiet = false;
00283   
00284   /* Check for the correct number of input parameters. */
00285   if (argc < 7 || argc > 10)
00286     print_usage_and_exit();
00287   for (int i = 1; i < argc; i++) {
00288     QString arg(argv[i]);
00289     if (!arg.compare("-q", Qt::CaseInsensitive)) {
00290       quiet = true;
00291     } else if (!arg.compare("-t", Qt::CaseInsensitive) && ++i < argc) {
00292       /* Open the input PO template file */
00293       potFile.setFileName(argv[i]);
00294       if (!potFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
00295         error << QString("Couldn't open '%1' for reading: ").arg(argv[i])
00296               <<  potFile.errorString();
00297         return 1;
00298       }
00299       pot.setDevice(&potFile);
00300     } else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc) {
00301       /* Open the input NSH file */
00302       nshFile.setFileName(argv[i]);
00303       if (!nshFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
00304         error << QString("Couldn't open '%1' for reading: ").arg(argv[i])
00305               <<  nshFile.errorString();
00306         return 1;
00307       }
00308       nsh.setDevice(&nshFile);
00309     } else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc) {
00310       outFileName = argv[i];
00311     } else if (!arg.compare("-c", Qt::CaseInsensitive) && ++i < argc) {
00312       /* Set the text encoding used for input and output */
00313       codec = QTextCodec::codecForName(argv[i]);
00314       if (!codec) {
00315         error << "Invalid text encoding specified.\n";
00316         return 1;
00317       }
00318     } else
00319       print_usage_and_exit(); 
00320   }
00321   pot.setCodec(codec);
00322   nsh.setCodec(codec);
00323   
00324   /* Parse the template for the source strings */
00325   QHash<QString,QString> poTemplate;
00326   if (!parse_po_template(&pot, &poTemplate, &errorMessage)) {
00327     error << QString("Failed to parse PO template: %1\n").arg(errorMessage);
00328     return 1;
00329   }
00330   
00331   /* Parse the nsh for the translated strings */
00332   int n_strings = nsh2po(&nsh, QString(codec->name()), poTemplate,
00333                          &po, &errorMessage);
00334   if (n_strings < 0) {
00335     error << QString("Conversion failed: %1\n").arg(errorMessage);
00336     return 2;
00337   }
00338   
00339   /* Write the formatted PO output */
00340   if (!write_po_output(outFileName, po, codec, &errorMessage)) {
00341     error << QString("Failed to write PO output: %1\n").arg(errorMessage);
00342     return 3;
00343   }
00344   
00345   if (!quiet) {
00346     QTextStream out(stdout);
00347     out << QString("Wrote %1 strings to '%2'.\n").arg(n_strings)
00348                                                  .arg(outFileName);
00349   }
00350   return 0;
00351 }
00352 

Generated on 31 Mar 2010 for Vidalia by  doxygen 1.6.1