Vidalia 0.2.12
|
00001 /* 00002 ** $Id$ 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