Vidalia 0.2.12
|
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 <QDomDocument> 00014 #include <QTextStream> 00015 #include <QTextCodec> 00016 #include <stdlib.h> 00017 00018 #define TS_DOCTYPE "TS" 00019 #define TS_ELEMENT_ROOT "TS" 00020 #define TS_ELEMENT_CONTEXT "context" 00021 #define TS_ELEMENT_NAME "name" 00022 #define TS_ELEMENT_MESSAGE "message" 00023 #define TS_ELEMENT_SOURCE "source" 00024 #define TS_ELEMENT_TRANSLATION "translation" 00025 #define TS_ATTR_TRANSLATION_TYPE "type" 00026 #define TS_ATTR_VERSION "version" 00027 00028 00029 /** Create a new context element with the name <b>contextName</b>. */ 00030 QDomElement 00031 new_context_element(QDomDocument *ts, const QString &contextName) 00032 { 00033 QDomElement context, name; 00034 00035 /* Create a <name> element */ 00036 name = ts->createElement(TS_ELEMENT_NAME); 00037 name.appendChild(ts->createTextNode(contextName)); 00038 00039 /* Create a <context> element and add the <name> element as a child */ 00040 context = ts->createElement(TS_ELEMENT_CONTEXT); 00041 context.appendChild(name); 00042 return context; 00043 } 00044 00045 /** Create a new message element using the source string <b>msgid</b> and the 00046 * translation <b>msgstr</b>. */ 00047 QDomElement 00048 new_message_element(QDomDocument *ts, 00049 const QString &msgid, const QString &msgstr) 00050 { 00051 QDomElement message, source, translation; 00052 00053 /* Create and set the <source> element */ 00054 source = ts->createElement(TS_ELEMENT_SOURCE); 00055 source.appendChild(ts->createTextNode(msgid)); 00056 00057 /* Create and set the <translation> element */ 00058 translation = ts->createElement(TS_ELEMENT_TRANSLATION); 00059 if (!msgstr.isEmpty()) 00060 translation.appendChild(ts->createTextNode(msgstr)); 00061 else 00062 translation.setAttribute(TS_ATTR_TRANSLATION_TYPE, "unfinished"); 00063 00064 /* Create a <message> element and add the <source> and <translation> 00065 * elements as children */ 00066 message = ts->createElement(TS_ELEMENT_MESSAGE); 00067 message.appendChild(source); 00068 message.appendChild(translation); 00069 00070 return message; 00071 } 00072 00073 /** Create a new TS document of the appropriate doctype and with a TS root 00074 * element. */ 00075 QDomDocument 00076 new_ts_document() 00077 { 00078 QDomDocument ts(TS_DOCTYPE); 00079 00080 QDomElement root = ts.createElement(TS_ELEMENT_ROOT); 00081 root.setAttribute(TS_ATTR_VERSION, "1.1"); 00082 ts.appendChild(root); 00083 00084 return ts; 00085 } 00086 00087 /** Parse the context name from <b>str</b>, where the context name is of the 00088 * form DQUOTE ContextName DQUOTE. */ 00089 QString 00090 parse_message_context(const QString &str) 00091 { 00092 QString out = str.trimmed(); 00093 out = out.replace("\"", ""); 00094 return out; 00095 } 00096 00097 /** Parse the context name from <b>str</b>, where <b>str</b> is of the 00098 * form ContextName#Number. This is the format used by translate-toolkit. */ 00099 QString 00100 parse_message_context_lame(const QString &str) 00101 { 00102 if (str.contains("#")) 00103 return str.section("#", 0, 0); 00104 return QString(); 00105 } 00106 00107 /** Parse the PO-formatted message string from <b>msg</b>. If <b>msg</b> is a 00108 * multiline string, the extra double quotes will be replaced with newlines 00109 * appropriately. */ 00110 QString 00111 parse_message_string(const QString &msg) 00112 { 00113 QString out = msg.trimmed(); 00114 00115 out.replace("\"\n\"", ""); 00116 if (out.startsWith("\"")) 00117 out = out.remove(0, 1); 00118 if (out.endsWith("\"")) 00119 out.chop(1); 00120 out.replace("\\\"", "\""); 00121 return out; 00122 } 00123 00124 /** Read and return the next non-empty line from <b>stream</b>. */ 00125 QString 00126 read_next_line(QTextStream *stream) 00127 { 00128 stream->skipWhiteSpace(); 00129 return stream->readLine().append("\n"); 00130 } 00131 00132 /** Skip past the header portion of the PO file and any leading whitespace. 00133 * The next line read from <b>po</b> will be the first non-header line in the 00134 * document. */ 00135 void 00136 skip_po_header(QTextStream *po) 00137 { 00138 QString line; 00139 /* Skip any leading whitespace before the header */ 00140 po->skipWhiteSpace(); 00141 /* Read to the first empty line */ 00142 line = po->readLine(); 00143 while (!po->atEnd() && !line.isEmpty()) 00144 line = po->readLine(); 00145 } 00146 00147 /** Convert <b>po</b> from the PO format to a TS-formatted XML document. 00148 * <b>ts</b> will be set to the resulting TS document. Return the number of 00149 * converted strings on success, or -1 on error and <b>errorMessage</b> will 00150 * be set. */ 00151 int 00152 po2ts(QTextStream *po, QDomDocument *ts, QString *errorMessage) 00153 { 00154 QString line; 00155 QString msgctxt, msgid, msgstr; 00156 QHash<QString,QDomElement> contextElements; 00157 QDomElement contextElement, msgElement, transElement; 00158 int n_strings = 0; 00159 00160 Q_ASSERT(po); 00161 Q_ASSERT(ts); 00162 Q_ASSERT(errorMessage); 00163 00164 *ts = new_ts_document(); 00165 00166 skip_po_header(po); 00167 line = read_next_line(po); 00168 while (!po->atEnd()) { 00169 /* Ignore all "#" lines except "#:" */ 00170 while (line.startsWith("#")) { 00171 if (line.startsWith("#:")) { 00172 /* Context was specified with the stupid overloaded "#:" syntax.*/ 00173 msgctxt = line.section(" ", 1); 00174 msgctxt = parse_message_context_lame(msgctxt); 00175 } 00176 line = read_next_line(po); 00177 } 00178 00179 /* A context specified on a "msgctxt" line takes precedence over a context 00180 * specified using the overload "#:" notation. */ 00181 if (line.startsWith("msgctxt ")) { 00182 msgctxt = line.section(" ", 1); 00183 msgctxt = parse_message_context(msgctxt); 00184 line = read_next_line(po); 00185 } 00186 00187 /* Parse the (possibly multiline) message source string */ 00188 if (!line.startsWith("msgid ")) { 00189 *errorMessage = "expected 'msgid' line"; 00190 return -1; 00191 } 00192 msgid = line.section(" ", 1); 00193 00194 line = read_next_line(po); 00195 while (line.startsWith("\"")) { 00196 msgid.append(line); 00197 line = read_next_line(po); 00198 } 00199 msgid = parse_message_string(msgid); 00200 00201 /* Parse the (possibly multiline) translated string */ 00202 if (!line.startsWith("msgstr ")) { 00203 *errorMessage = "expected 'msgstr' line"; 00204 return -1; 00205 } 00206 msgstr = line.section(" ", 1); 00207 00208 line = read_next_line(po); 00209 while (line.startsWith("\"")) { 00210 msgstr.append(line); 00211 line = read_next_line(po); 00212 } 00213 msgstr = parse_message_string(msgstr); 00214 00215 /* Add the message and translation to the .ts document */ 00216 if (contextElements.contains(msgctxt)) { 00217 contextElement = contextElements.value(msgctxt); 00218 } else { 00219 contextElement = new_context_element(ts, msgctxt); 00220 ts->documentElement().appendChild(contextElement); 00221 contextElements.insert(msgctxt, contextElement); 00222 } 00223 contextElement.appendChild(new_message_element(ts, msgid, msgstr)); 00224 00225 n_strings++; 00226 } 00227 return n_strings; 00228 } 00229 00230 /** Display application usage and exit. */ 00231 void 00232 print_usage_and_exit() 00233 { 00234 QTextStream error(stderr); 00235 error << "usage: po2ts [-q] -i <infile.po> -o <outfile.ts> " 00236 "[-c <encoding>]\n"; 00237 error << " -q (optional) Quiet mode (errors are still displayed)\n"; 00238 error << " -i <infile.po> Input .po file\n"; 00239 error << " -o <outfile.ts> Output .ts file\n"; 00240 error << " -c <encoding> Text encoding (default: utf-8)\n"; 00241 error.flush(); 00242 exit(1); 00243 } 00244 00245 int 00246 main(int argc, char *argv[]) 00247 { 00248 QTextStream error(stderr); 00249 QString errorMessage; 00250 char *infile, *outfile; 00251 QTextCodec *codec = QTextCodec::codecForName("utf-8"); 00252 bool quiet = false; 00253 00254 /* Check for the correct number of input parameters. */ 00255 if (argc < 5 || argc > 8) 00256 print_usage_and_exit(); 00257 for (int i = 1; i < argc; i++) { 00258 QString arg(argv[i]); 00259 if (!arg.compare("-q", Qt::CaseInsensitive)) 00260 quiet = true; 00261 else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc) 00262 infile = argv[i]; 00263 else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc) 00264 outfile = argv[i]; 00265 else if (!arg.compare("-c", Qt::CaseInsensitive) && ++i < argc) { 00266 codec = QTextCodec::codecForName(argv[i]); 00267 if (!codec) { 00268 error << "Invalid text encoding specified\n"; 00269 return 1; 00270 } 00271 } else 00272 print_usage_and_exit(); 00273 } 00274 00275 /* Open the input PO file for reading. */ 00276 QFile poFile(infile); 00277 if (!poFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 00278 error << QString("Unable to open '%1' for reading: %2\n").arg(infile) 00279 .arg(poFile.errorString()); 00280 return 2; 00281 } 00282 00283 QDomDocument ts; 00284 QTextStream po(&poFile); 00285 po.setCodec(codec); 00286 int n_strings = po2ts(&po, &ts, &errorMessage); 00287 if (n_strings < 0) { 00288 error << QString("Unable to convert '%1': %2\n").arg(infile) 00289 .arg(errorMessage); 00290 return 3; 00291 } 00292 00293 /* Open the TS file for writing. */ 00294 QFile tsFile(outfile); 00295 if (!tsFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 00296 error << QString("Unable to open '%1' for writing: %2\n").arg(outfile) 00297 .arg(tsFile.errorString()); 00298 return 4; 00299 } 00300 00301 /* Write the .ts output. */ 00302 QTextStream out(&tsFile); 00303 out.setCodec(codec); 00304 out << QString("<?xml version=\"1.0\" encoding=\"%1\"?>\n") 00305 .arg(QString(codec->name())); 00306 out << ts.toString(4); 00307 00308 if (!quiet) { 00309 QTextStream results(stdout); 00310 results << QString("Converted %1 strings from %2 to %3.\n").arg(n_strings) 00311 .arg(infile) 00312 .arg(outfile); 00313 } 00314 return 0; 00315 } 00316