po2wxl.cpp

Go to the documentation of this file.
00001 /*
00002 **  $Id: po2wxl.cpp 3679 2009-04-08 05:04:15Z edmanm $
00003 **
00004 **  This file is part of Vidalia, and is subject to the license terms in the
00005 **  LICENSE file, found in the top level directory of this distribution. If you
00006 **  did not receive the LICENSE file with this file, you may obtain it from the
00007 **  Vidalia source package distributed by the Vidalia Project at
00008 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00009 **  may be copied, modified, propagated, or distributed except according to the
00010 **  terms described in the LICENSE file.
00011 */
00012 
00013 #include <QFile>
00014 #include <QDomDocument>
00015 #include <QTextStream>
00016 #include <QTextCodec>
00017 #include <stdlib.h>
00018 
00019 #define WXL_NAMESPACE                  "http://schemas.microsoft.com/wix/2006/localization"
00020 #define WXL_ELEMENT_ROOT               "WixLocalization"
00021 #define WXL_ELEMENT_MESSAGE            "String"
00022 #define WXL_ATTR_MESSAGE_ID            "Id"
00023 #define WXL_ATTR_LANGUAGE              "LCID"
00024 #define WXL_ATTR_TRANSLATION_TYPE      "Culture"
00025 #define WXL_ATTR_OVERRIDABLE           "Overridable"
00026 
00027 /** We need to provide an element with the LCID for this locale 
00028  * that is used in the WiX Product definition. */
00029 QString
00030 culture_lcid(const QString &culture)
00031 {
00032   /* For now character encoding focused, not generally locale / dialect aware. */
00033   QString lcid = "0";
00034   if(!culture.compare("en", Qt::CaseInsensitive)) 
00035     lcid = "1033";
00036   else if(!culture.compare("cs", Qt::CaseInsensitive)) 
00037     lcid = "1029";
00038   else if(!culture.compare("de", Qt::CaseInsensitive)) 
00039     lcid = "1031";
00040   else if(!culture.compare("es", Qt::CaseInsensitive)) 
00041     lcid = "1034";
00042   else if(!culture.compare("fa", Qt::CaseInsensitive)) 
00043     lcid = "1065";
00044   else if(!culture.compare("fi", Qt::CaseInsensitive)) 
00045     lcid = "1035";
00046   else if(!culture.compare("fr", Qt::CaseInsensitive)) 
00047     lcid = "1036";
00048   else if(!culture.compare("he", Qt::CaseInsensitive)) 
00049     lcid = "1037";
00050   else if(!culture.compare("it", Qt::CaseInsensitive)) 
00051     lcid = "1040";
00052   else if(!culture.compare("nl", Qt::CaseInsensitive)) 
00053     lcid = "1043";
00054   else if(!culture.compare("pl", Qt::CaseInsensitive)) 
00055     lcid = "1045";
00056   else if(!culture.compare("pt", Qt::CaseInsensitive)) 
00057     lcid = "1046";
00058   else if(!culture.compare("ro", Qt::CaseInsensitive)) 
00059     lcid = "1048";
00060   else if(!culture.compare("ru", Qt::CaseInsensitive)) 
00061     lcid = "1049";
00062   else if(!culture.compare("sv", Qt::CaseInsensitive)) 
00063     lcid = "1053";
00064   return lcid;
00065 }
00066 
00067 /** Create a new message string element using the source string <b>msgid</b>
00068  * and the translation <b>msgstr</b> and assign identifier attribute. */
00069 QDomElement
00070 new_message_element(QDomDocument *wxl, const QString &strid,
00071                     const QString &msgid, const QString &msgstr)
00072 {
00073   QDomElement message;
00074 
00075   message = wxl->createElement(WXL_ELEMENT_MESSAGE);
00076   message.setAttribute(WXL_ATTR_MESSAGE_ID, strid);
00077 
00078   /* Always allow localized string to be dynamic. This is required for
00079    * multi-language packages to link correctly.
00080    */
00081   message.setAttribute(WXL_ATTR_OVERRIDABLE, "yes");
00082   if (!msgstr.isEmpty())
00083     message.appendChild(wxl->createTextNode(msgstr));
00084   else
00085     message.appendChild(wxl->createTextNode(msgid));
00086 
00087   return message;
00088 }
00089 
00090 /** Create a new WXL document of the appropriate doctype and root
00091  * element with the Microsoft style culture name for locale. */
00092 QDomDocument
00093 new_wxl_document(const QString &culture)
00094 {
00095   QDomDocument wxl;
00096   
00097   QDomElement root = wxl.createElementNS(WXL_NAMESPACE, WXL_ELEMENT_ROOT);
00098   root.setAttribute(WXL_ATTR_TRANSLATION_TYPE, culture);
00099   wxl.appendChild(root);
00100 
00101   return wxl;
00102 }
00103 
00104 /** Parse the context name from <b>str</b>, where the context name is of the
00105  * form DQUOTE ContextName DQUOTE. */
00106 QString
00107 parse_message_context(const QString &str)
00108 {
00109   QString out = str.trimmed();
00110   out = out.replace("\"", "");
00111   return out;
00112 }
00113 
00114 /** Parse the context name from <b>str</b>, where <b>str</b> is of the
00115  * form ContextName#Number. This is the format used by translate-toolkit. */
00116 QString
00117 parse_message_context_lame(const QString &str)
00118 {
00119   if (str.contains("#"))
00120     return str.section("#", 0, 0);
00121   return QString();
00122 }
00123 
00124 /** Parse the PO-formatted message string from <b>msg</b>. If <b>msg</b> is a
00125  * multiline string, the extra double quotes will be replaced with newlines
00126  * appropriately. */
00127 QString
00128 parse_message_string(const QString &msg)
00129 {
00130   QString out = msg.trimmed(); 
00131   
00132   out.replace("\"\n\"", "\n");
00133   if (out.startsWith("\""))
00134     out = out.remove(0, 1);
00135   if (out.endsWith("\""))
00136     out.chop(1);
00137   out.replace("\\\"", "\"");
00138 
00139   /* convert NSIS style vars to Properties; avoid QRegExp if possible. */
00140   int lind, rind;
00141   while ( ((lind = out.indexOf("${")) >= 0) &&
00142           ((rind = out.indexOf("}", lind)) > lind) ) {
00143     out.replace(lind, 2, "[");
00144     out.replace(--rind, 1, "]");
00145   }
00146   return out;
00147 }
00148 
00149 /** Read and return the next non-empty line from <b>stream</b>. */
00150 QString
00151 read_next_line(QTextStream *stream)
00152 {
00153   stream->skipWhiteSpace();
00154   return stream->readLine().append("\n");
00155 }
00156 
00157 /** Skip past the header portion of the PO file and any leading whitespace. 
00158  * The next line read from <b>po</b> will be the first non-header line in the
00159  * document. */
00160 void
00161 skip_po_header(QTextStream *po)
00162 {
00163   QString line;
00164   /* Skip any leading whitespace before the header */
00165   po->skipWhiteSpace();
00166   /* Read to the first empty line */
00167   line = po->readLine();
00168   while (!po->atEnd() && !line.isEmpty())
00169     line = po->readLine();
00170 }
00171 
00172 /** Convert <b>po</b> from the PO format to a WXL-formatted XML document.
00173  * <b>wxl</b> will be set to the resulting WXL document. Return the number of
00174  * converted strings on success, or -1 on error and <b>errorMessage</b> will
00175  * be set. */
00176 int
00177 po2wxl(const QString& culture, QTextStream *po, QDomDocument *wxl,
00178   QString *errorMessage)
00179 {
00180   QString line;
00181   QString msgctxt, msgid, msgstr;
00182   QDomElement msgElement;
00183   int n_strings = 0;
00184 
00185   Q_ASSERT(po);
00186   Q_ASSERT(wxl);
00187   Q_ASSERT(errorMessage);
00188 
00189   *wxl = new_wxl_document(culture);
00190 
00191   /* Set the LCID to Language code for use as !(loc.LCID) in Product. */
00192   QString lcid = culture_lcid(culture); 
00193   wxl->documentElement().appendChild(
00194     new_message_element(wxl, WXL_ATTR_LANGUAGE, lcid, lcid)); 
00195 
00196   skip_po_header(po);
00197   line = read_next_line(po);
00198   while (!po->atEnd()) {
00199     /* Ignore all "#" lines except "#:" */
00200     while (line.startsWith("#")) {
00201       if (line.startsWith("#:")) {
00202         /* Context was specified with the stupid overloaded "#:" syntax.*/
00203         msgctxt = line.section(" ", 1);
00204         msgctxt = parse_message_context_lame(msgctxt);
00205       }
00206       line = read_next_line(po);
00207     }
00208 
00209     /* A context specified on a "msgctxt" line takes precedence over a context
00210      * specified using the overload "#:" notation. */
00211     if (line.startsWith("msgctxt ")) {    
00212       msgctxt = line.section(" ", 1);
00213       msgctxt = parse_message_context(msgctxt);
00214       line = read_next_line(po);
00215     }
00216     
00217     /* Parse the (possibly multiline) message source string */
00218     if (!line.startsWith("msgid ")) {
00219       *errorMessage = "expected 'msgid' line";
00220       return -1;
00221     }
00222     msgid = line.section(" ", 1);
00223     
00224     line = read_next_line(po);
00225     while (line.startsWith("\"")) {
00226       msgid.append(line);
00227       line = read_next_line(po);
00228     }
00229     msgid = parse_message_string(msgid);
00230 
00231     /* Parse the (possibly multiline) translated string */
00232     if (!line.startsWith("msgstr ")) {
00233       *errorMessage = "expected 'msgstr' line";
00234       return -1;
00235     }
00236     msgstr = line.section(" ", 1);
00237     
00238     line = read_next_line(po);
00239     while (line.startsWith("\"")) {
00240       msgstr.append(line);
00241       line = read_next_line(po);
00242     }
00243     msgstr = parse_message_string(msgstr);
00244 
00245     /* Add the message and translation to the .wxl document */
00246     wxl->documentElement().appendChild(
00247       new_message_element(wxl, msgctxt, msgid, msgstr)); 
00248     
00249     n_strings++;
00250   }
00251   return n_strings;
00252 }
00253 
00254 /** Display application usage and exit. */
00255 void
00256 print_usage_and_exit()
00257 {
00258   QTextStream error(stderr);
00259   error << "usage: po2wxl [-q] -n <culture name> -i <infile.po> -o <outfile.wxl> "
00260            "[-c <encoding>]\n";
00261   error << "  -q (optional)    Quiet mode (errors are still displayed)\n";
00262   error << "  -n <culture>     Culture name for translation\n";
00263   error << "  -i <infile.po>   Input .po file\n";
00264   error << "  -o <outfile.wxl> Output .wxl file\n";
00265   error << "  -c <encoding>    Text encoding (default: utf-8)\n";
00266   error.flush();
00267   exit(1);
00268 }
00269 
00270 int
00271 main(int argc, char *argv[])
00272 {
00273   QTextStream error(stderr);
00274   QString culture, errorMessage;
00275   char *infile, *outfile;
00276   QTextCodec *codec = QTextCodec::codecForName("utf-8");
00277   bool quiet = false;
00278 
00279   /* Check for the correct number of input parameters. */
00280   if (argc < 5 || argc > 9)
00281     print_usage_and_exit();
00282   for (int i = 1; i < argc; i++) {
00283     QString arg(argv[i]);
00284     if (!arg.compare("-q", Qt::CaseInsensitive))
00285       quiet = true;
00286     else if (!arg.compare("-n", Qt::CaseInsensitive) && ++i < argc)
00287       culture = argv[i];
00288     else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc)
00289       infile = argv[i];
00290     else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc)
00291       outfile = argv[i];
00292     else if (!arg.compare("-c", Qt::CaseInsensitive) && ++i < argc) {
00293       codec = QTextCodec::codecForName(argv[i]);
00294       if (!codec) {
00295         error << "Invalid text encoding specified\n";
00296         return 1;
00297       }
00298     } else
00299       print_usage_and_exit(); 
00300   }
00301 
00302   /* Open the input PO file for reading. */
00303   QFile poFile(infile);
00304   if (!poFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
00305     error << QString("Unable to open '%1' for reading: %2\n").arg(infile)
00306                                                 .arg(poFile.errorString());
00307     return 2;
00308   }
00309 
00310   QDomDocument wxl;
00311   QTextStream po(&poFile);
00312   po.setCodec(codec);
00313   int n_strings = po2wxl(culture, &po, &wxl, &errorMessage);
00314   if (n_strings < 0) {
00315     error << QString("Unable to convert '%1': %2\n").arg(infile)
00316                                                     .arg(errorMessage);
00317     return 3;
00318   }
00319 
00320   /* Open the WXL file for writing. */
00321   QFile wxlFile(outfile);
00322   if (!wxlFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
00323     error << QString("Unable to open '%1' for writing: %2\n").arg(outfile)
00324                                                 .arg(wxlFile.errorString());
00325     return 4;
00326   }
00327 
00328   /* Write the .wxl output. */
00329   QTextStream out(&wxlFile);
00330   out.setCodec(codec);
00331   out << QString("<?xml version=\"1.0\" encoding=\"%1\"?>\n")
00332                                                   .arg(QString(codec->name()));
00333   out << wxl.toString(4);
00334 
00335   if (!quiet) {
00336     QTextStream results(stdout);
00337     results << QString("Converted %1 strings from %2 to %3.\n").arg(n_strings)
00338                                                                .arg(infile)
00339                                                                .arg(outfile);
00340   }
00341   return 0;
00342 }
00343 

Generated on Mon Aug 30 19:09:59 2010 for Vidalia by  doxygen 1.5.9