Vidalia
0.2.17
|
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.torproject.org/projects/vidalia.html. No part of Vidalia, 00007 ** including this file, may be copied, modified, propagated, or distributed 00008 ** except according to the 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().trimmed(); 00108 msgid.replace("\r", ""); 00109 msgid.replace("\"", "\\\""); 00110 msgid.replace("\n", "\\n\"\n\""); 00111 00112 /* Extract the <translation> tags */ 00113 translation = msg.firstChildElement(TS_ELEMENT_TRANSLATION); 00114 msgstr = translation.text().trimmed(); 00115 msgstr.replace("\r", ""); 00116 msgstr.replace("\"", "\\\""); 00117 msgstr.replace("\n", "\\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