source: trunk/tools/linguist/shared/metatranslator.cpp

Last change on this file was 2, checked in by dmik, 20 years ago

Imported xplatform parts of the official release 3.3.1 from Trolltech

  • Property svn:keywords set to Id
File size: 16.2 KB
Line 
1/**********************************************************************
2** Copyright (C) 2000-2002 Trolltech AS. All rights reserved.
3**
4** This file is part of Qt Linguist.
5**
6** This file may be distributed and/or modified under the terms of the
7** GNU General Public License version 2 as published by the Free Software
8** Foundation and appearing in the file LICENSE.GPL included in the
9** packaging of this file.
10**
11** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
12** licenses may use this file in accordance with the Qt Commercial License
13** Agreement provided with the Software.
14**
15** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
17**
18** See http://www.trolltech.com/gpl/ for GPL licensing information.
19** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
20** information about Qt Commercial License Agreements.
21**
22** Contact info@trolltech.com if any conditions of this licensing are
23** not clear to you.
24**
25**********************************************************************/
26
27#include "metatranslator.h"
28
29#include <qapplication.h>
30#include <qcstring.h>
31#include <qfile.h>
32#include <qmessagebox.h>
33#include <qtextcodec.h>
34#include <qtextstream.h>
35#include <qxml.h>
36
37static bool encodingIsUtf8( const QXmlAttributes& atts )
38{
39 for ( int i = 0; i < atts.length(); i++ ) {
40 // utf8="true" is a pre-3.0 syntax
41 if ( atts.qName(i) == QString("utf8") ) {
42 return ( atts.value(i) == QString("true") );
43 } else if ( atts.qName(i) == QString("encoding") ) {
44 return ( atts.value(i) == QString("UTF-8") );
45 }
46 }
47 return FALSE;
48}
49
50class TsHandler : public QXmlDefaultHandler
51{
52public:
53 TsHandler( MetaTranslator *translator )
54 : tor( translator ), type( MetaTranslatorMessage::Finished ),
55 inMessage( FALSE ), ferrorCount( 0 ), contextIsUtf8( FALSE ),
56 messageIsUtf8( FALSE ) { }
57
58 virtual bool startElement( const QString& namespaceURI,
59 const QString& localName, const QString& qName,
60 const QXmlAttributes& atts );
61 virtual bool endElement( const QString& namespaceURI,
62 const QString& localName, const QString& qName );
63 virtual bool characters( const QString& ch );
64 virtual bool fatalError( const QXmlParseException& exception );
65
66private:
67 MetaTranslator *tor;
68 MetaTranslatorMessage::Type type;
69 bool inMessage;
70 QString context;
71 QString source;
72 QString comment;
73 QString translation;
74
75 QString accum;
76 int ferrorCount;
77 bool contextIsUtf8;
78 bool messageIsUtf8;
79};
80
81bool TsHandler::startElement( const QString& /* namespaceURI */,
82 const QString& /* localName */,
83 const QString& qName,
84 const QXmlAttributes& atts )
85{
86 if ( qName == QString("byte") ) {
87 for ( int i = 0; i < atts.length(); i++ ) {
88 if ( atts.qName(i) == QString("value") ) {
89 QString value = atts.value( i );
90 int base = 10;
91 if ( value.startsWith("x") ) {
92 base = 16;
93 value = value.mid( 1 );
94 }
95 int n = value.toUInt( 0, base );
96 if ( n != 0 )
97 accum += QChar( n );
98 }
99 }
100 } else {
101 if ( qName == QString("context") ) {
102 context.truncate( 0 );
103 source.truncate( 0 );
104 comment.truncate( 0 );
105 translation.truncate( 0 );
106 contextIsUtf8 = encodingIsUtf8( atts );
107 } else if ( qName == QString("message") ) {
108 inMessage = TRUE;
109 type = MetaTranslatorMessage::Finished;
110 source.truncate( 0 );
111 comment.truncate( 0 );
112 translation.truncate( 0 );
113 messageIsUtf8 = encodingIsUtf8( atts );
114 } else if ( qName == QString("translation") ) {
115 for ( int i = 0; i < atts.length(); i++ ) {
116 if ( atts.qName(i) == QString("type") ) {
117 if ( atts.value(i) == QString("unfinished") )
118 type = MetaTranslatorMessage::Unfinished;
119 else if ( atts.value(i) == QString("obsolete") )
120 type = MetaTranslatorMessage::Obsolete;
121 else
122 type = MetaTranslatorMessage::Finished;
123 }
124 }
125 }
126 accum.truncate( 0 );
127 }
128 return TRUE;
129}
130
131bool TsHandler::endElement( const QString& /* namespaceURI */,
132 const QString& /* localName */,
133 const QString& qName )
134{
135 if ( qName == QString("codec") || qName == QString("defaultcodec") ) {
136 // "codec" is a pre-3.0 syntax
137 tor->setCodec( accum );
138 } else if ( qName == QString("name") ) {
139 context = accum;
140 } else if ( qName == QString("source") ) {
141 source = accum;
142 } else if ( qName == QString("comment") ) {
143 if ( inMessage ) {
144 comment = accum;
145 } else {
146 if ( contextIsUtf8 )
147 tor->insert( MetaTranslatorMessage(context.utf8(),
148 ContextComment,
149 accum.utf8(), QString::null, TRUE,
150 MetaTranslatorMessage::Unfinished) );
151 else
152 tor->insert( MetaTranslatorMessage(context.ascii(),
153 ContextComment,
154 accum.ascii(), QString::null, FALSE,
155 MetaTranslatorMessage::Unfinished) );
156 }
157 } else if ( qName == QString("translation") ) {
158 translation = accum;
159 } else if ( qName == QString("message") ) {
160 if ( messageIsUtf8 )
161 tor->insert( MetaTranslatorMessage(context.utf8(), source.utf8(),
162 comment.utf8(), translation,
163 TRUE, type) );
164 else
165 tor->insert( MetaTranslatorMessage(context.ascii(), source.ascii(),
166 comment.ascii(), translation,
167 FALSE, type) );
168 inMessage = FALSE;
169 }
170 return TRUE;
171}
172
173bool TsHandler::characters( const QString& ch )
174{
175 QString t = ch;
176 t.replace( "\r", "" );
177 accum += t;
178 return TRUE;
179}
180
181bool TsHandler::fatalError( const QXmlParseException& exception )
182{
183 if ( ferrorCount++ == 0 ) {
184 QString msg;
185 msg.sprintf( "Parse error at line %d, column %d (%s).",
186 exception.lineNumber(), exception.columnNumber(),
187 exception.message().latin1() );
188 if ( qApp == 0 )
189 fprintf( stderr, "XML error: %s\n", msg.latin1() );
190 else
191 QMessageBox::information( qApp->mainWidget(),
192 QObject::tr("Qt Linguist"), msg );
193 }
194 return FALSE;
195}
196
197static QString numericEntity( int ch )
198{
199 return QString( ch <= 0x20 ? "<byte value=\"x%1\"/>" : "&#x%1;" )
200 .arg( ch, 0, 16 );
201}
202
203static QString protect( const QCString& str )
204{
205 QString result;
206 int len = (int) str.length();
207 for ( int k = 0; k < len; k++ ) {
208 switch( str[k] ) {
209 case '\"':
210 result += QString( "&quot;" );
211 break;
212 case '&':
213 result += QString( "&amp;" );
214 break;
215 case '>':
216 result += QString( "&gt;" );
217 break;
218 case '<':
219 result += QString( "&lt;" );
220 break;
221 case '\'':
222 result += QString( "&apos;" );
223 break;
224 default:
225 if ( (uchar) str[k] < 0x20 && str[k] != '\n' )
226 result += numericEntity( (uchar) str[k] );
227 else
228 result += str[k];
229 }
230 }
231 return result;
232}
233
234static QString evilBytes( const QCString& str, bool utf8 )
235{
236 if ( utf8 ) {
237 return protect( str );
238 } else {
239 QString result;
240 QCString t = protect( str ).latin1();
241 int len = (int) t.length();
242 for ( int k = 0; k < len; k++ ) {
243 if ( (uchar) t[k] >= 0x7f )
244 result += numericEntity( (uchar) t[k] );
245 else
246 result += QChar( t[k] );
247 }
248 return result;
249 }
250}
251
252MetaTranslatorMessage::MetaTranslatorMessage()
253 : utfeight( FALSE ), ty( Unfinished )
254{
255}
256
257MetaTranslatorMessage::MetaTranslatorMessage( const char *context,
258 const char *sourceText,
259 const char *comment,
260 const QString& translation,
261 bool utf8, Type type )
262 : QTranslatorMessage( context, sourceText, comment, translation ),
263 utfeight( FALSE ), ty( type )
264{
265 /*
266 Don't use UTF-8 if it makes no difference. UTF-8 should be
267 reserved for the real problematic case: non-ASCII (possibly
268 non-Latin-1) characters in .ui files.
269 */
270 if ( utf8 ) {
271 if ( sourceText != 0 ) {
272 int i = 0;
273 while ( sourceText[i] != '\0' ) {
274 if ( (uchar) sourceText[i] >= 0x80 ) {
275 utfeight = TRUE;
276 break;
277 }
278 i++;
279 }
280 }
281 if ( !utfeight && comment != 0 ) {
282 int i = 0;
283 while ( comment[i] != '\0' ) {
284 if ( (uchar) comment[i] >= 0x80 ) {
285 utfeight = TRUE;
286 break;
287 }
288 i++;
289 }
290 }
291 }
292}
293
294MetaTranslatorMessage::MetaTranslatorMessage( const MetaTranslatorMessage& m )
295 : QTranslatorMessage( m ), utfeight( m.utfeight ), ty( m.ty )
296{
297}
298
299MetaTranslatorMessage& MetaTranslatorMessage::operator=(
300 const MetaTranslatorMessage& m )
301{
302 QTranslatorMessage::operator=( m );
303 utfeight = m.utfeight;
304 ty = m.ty;
305 return *this;
306}
307
308bool MetaTranslatorMessage::operator==( const MetaTranslatorMessage& m ) const
309{
310 return qstrcmp( context(), m.context() ) == 0 &&
311 qstrcmp( sourceText(), m.sourceText() ) == 0 &&
312 qstrcmp( comment(), m.comment() ) == 0;
313}
314
315bool MetaTranslatorMessage::operator<( const MetaTranslatorMessage& m ) const
316{
317 int delta = qstrcmp( context(), m.context() );
318 if ( delta == 0 )
319 delta = qstrcmp( sourceText(), m.sourceText() );
320 if ( delta == 0 )
321 delta = qstrcmp( comment(), m.comment() );
322 return delta < 0;
323}
324
325MetaTranslator::MetaTranslator()
326{
327 clear();
328}
329
330MetaTranslator::MetaTranslator( const MetaTranslator& tor )
331 : mm( tor.mm ), codecName( tor.codecName ), codec( tor.codec )
332{
333}
334
335MetaTranslator& MetaTranslator::operator=( const MetaTranslator& tor )
336{
337 mm = tor.mm;
338 codecName = tor.codecName;
339 codec = tor.codec;
340 return *this;
341}
342
343void MetaTranslator::clear()
344{
345 mm.clear();
346 codecName = "ISO-8859-1";
347 codec = 0;
348}
349
350bool MetaTranslator::load( const QString& filename )
351{
352 QFile f( filename );
353 if ( !f.open(IO_ReadOnly) )
354 return FALSE;
355
356 QTextStream t( &f );
357 QXmlInputSource in( t );
358 QXmlSimpleReader reader;
359 reader.setFeature( "http://xml.org/sax/features/namespaces", FALSE );
360 reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", TRUE );
361 reader.setFeature( "http://trolltech.com/xml/features/report-whitespace"
362 "-only-CharData", FALSE );
363 QXmlDefaultHandler *hand = new TsHandler( this );
364 reader.setContentHandler( hand );
365 reader.setErrorHandler( hand );
366
367 bool ok = reader.parse( in );
368 reader.setContentHandler( 0 );
369 reader.setErrorHandler( 0 );
370 delete hand;
371 f.close();
372 return ok;
373}
374
375bool MetaTranslator::save( const QString& filename ) const
376{
377 QFile f( filename );
378 if ( !f.open(IO_WriteOnly) )
379 return FALSE;
380
381 QTextStream t( &f );
382 t.setCodec( QTextCodec::codecForName("ISO-8859-1") );
383
384 t << "<!DOCTYPE TS><TS>\n";
385 if ( codecName != "ISO-8859-1" )
386 t << "<defaultcodec>" << codecName << "</defaultcodec>\n";
387 TMM::ConstIterator m = mm.begin();
388 while ( m != mm.end() ) {
389 TMMInv inv;
390 TMMInv::Iterator i;
391 bool contextIsUtf8 = m.key().utf8();
392 QCString context = m.key().context();
393 QCString comment = "";
394
395 do {
396 if ( QCString(m.key().sourceText()) == ContextComment ) {
397 if ( m.key().type() != MetaTranslatorMessage::Obsolete ) {
398 contextIsUtf8 = m.key().utf8();
399 comment = QCString( m.key().comment() );
400 }
401 } else {
402 inv.insert( *m, m.key() );
403 }
404 } while ( ++m != mm.end() && QCString(m.key().context()) == context );
405
406 t << "<context";
407 if ( contextIsUtf8 )
408 t << " encoding=\"UTF-8\"";
409 t << ">\n";
410 t << " <name>" << evilBytes( context, contextIsUtf8 )
411 << "</name>\n";
412 if ( !comment.isEmpty() )
413 t << " <comment>" << evilBytes( comment, contextIsUtf8 )
414 << "</comment>\n";
415
416 for ( i = inv.begin(); i != inv.end(); ++i ) {
417 // no need for such noise
418 if ( (*i).type() == MetaTranslatorMessage::Obsolete &&
419 (*i).translation().isEmpty() )
420 continue;
421
422 t << " <message";
423 if ( (*i).utf8() )
424 t << " encoding=\"UTF-8\"";
425 t << ">\n"
426 << " <source>" << evilBytes( (*i).sourceText(),
427 (*i).utf8() )
428 << "</source>\n";
429 if ( !QCString((*i).comment()).isEmpty() )
430 t << " <comment>" << evilBytes( (*i).comment(),
431 (*i).utf8() )
432 << "</comment>\n";
433 t << " <translation";
434 if ( (*i).type() == MetaTranslatorMessage::Unfinished )
435 t << " type=\"unfinished\"";
436 else if ( (*i).type() == MetaTranslatorMessage::Obsolete )
437 t << " type=\"obsolete\"";
438 t << ">" << protect( (*i).translation().utf8() )
439 << "</translation>\n";
440 t << " </message>\n";
441 }
442 t << "</context>\n";
443 }
444 t << "</TS>\n";
445 f.close();
446 return TRUE;
447}
448
449bool MetaTranslator::release( const QString& filename, bool verbose,
450 QTranslator::SaveMode mode ) const
451{
452 QTranslator tor( 0 );
453 int finished = 0;
454 int unfinished = 0;
455 int untranslated = 0;
456 TMM::ConstIterator m;
457
458 for ( m = mm.begin(); m != mm.end(); ++m ) {
459 if ( m.key().type() != MetaTranslatorMessage::Obsolete ) {
460 if ( m.key().translation().isEmpty() ) {
461 untranslated++;
462 } else {
463 if ( m.key().type() == MetaTranslatorMessage::Unfinished )
464 unfinished++;
465 else
466 finished++;
467
468 QCString context = m.key().context();
469 QCString sourceText = m.key().sourceText();
470 QCString comment = m.key().comment();
471 QString translation = m.key().translation();
472
473 /*
474 Drop the comment in (context, sourceText, comment),
475 unless (context, sourceText, "") already exists, or
476 unless we already dropped the comment of (context,
477 sourceText, comment0).
478 */
479 if ( comment.isEmpty()
480 || contains(context, sourceText, "")
481 || !tor.findMessage(context, sourceText, "").translation()
482 .isNull() ) {
483 tor.insert( m.key() );
484 } else {
485 tor.insert( QTranslatorMessage(context, sourceText, "",
486 translation) );
487 }
488 }
489 }
490 }
491
492 bool saved = tor.save( filename, mode );
493 if ( saved && verbose )
494 fprintf( stderr,
495 " %d finished, %d unfinished and %d untranslated messages\n",
496 finished, unfinished, untranslated );
497
498 return saved;
499}
500
501bool MetaTranslator::contains( const char *context, const char *sourceText,
502 const char *comment ) const
503{
504 return mm.find( MetaTranslatorMessage(context, sourceText, comment) ) !=
505 mm.end();
506}
507
508void MetaTranslator::insert( const MetaTranslatorMessage& m )
509{
510 int pos = mm.count();
511 TMM::Iterator n = mm.find( m );
512 if ( n != mm.end() )
513 pos = *n;
514 mm.replace( m, pos );
515}
516
517void MetaTranslator::stripObsoleteMessages()
518{
519 TMM newmm;
520
521 TMM::Iterator m = mm.begin();
522 while ( m != mm.end() ) {
523 if ( m.key().type() != MetaTranslatorMessage::Obsolete )
524 newmm.insert( m.key(), *m );
525 ++m;
526 }
527 mm = newmm;
528}
529
530void MetaTranslator::stripEmptyContexts()
531{
532 TMM newmm;
533
534 TMM::Iterator m = mm.begin();
535 while ( m != mm.end() ) {
536 if ( QCString(m.key().sourceText()) == ContextComment ) {
537 TMM::Iterator n = m;
538 ++n;
539 // the context comment is followed by other messages
540 if ( n != newmm.end() &&
541 qstrcmp(m.key().context(), n.key().context()) == 0 )
542 newmm.insert( m.key(), *m );
543 } else {
544 newmm.insert( m.key(), *m );
545 }
546 ++m;
547 }
548 mm = newmm;
549}
550
551void MetaTranslator::setCodec( const char *name )
552{
553 const int latin1 = 4;
554
555 codecName = name;
556 codec = QTextCodec::codecForName( name );
557 if ( codec == 0 || codec->mibEnum() == latin1 )
558 codec = 0;
559}
560
561QString MetaTranslator::toUnicode( const char *str, bool utf8 ) const
562{
563 if ( utf8 )
564 return QString::fromUtf8( str );
565 else if ( codec == 0 )
566 return QString( str );
567 else
568 return codec->toUnicode( str );
569}
570
571QValueList<MetaTranslatorMessage> MetaTranslator::messages() const
572{
573 int n = mm.count();
574 TMM::ConstIterator *t = new TMM::ConstIterator[n + 1];
575 TMM::ConstIterator m;
576 for ( m = mm.begin(); m != mm.end(); ++m )
577 t[*m] = m;
578
579 QValueList<MetaTranslatorMessage> val;
580 for ( int i = 0; i < n; i++ )
581 val.append( t[i].key() );
582
583 delete[] t;
584 return val;
585}
586
587QValueList<MetaTranslatorMessage> MetaTranslator::translatedMessages() const
588{
589 QValueList<MetaTranslatorMessage> val;
590 TMM::ConstIterator m;
591 for ( m = mm.begin(); m != mm.end(); ++m ) {
592 if ( m.key().type() == MetaTranslatorMessage::Finished )
593 val.append( m.key() );
594 }
595 return val;
596}
Note: See TracBrowser for help on using the repository browser.