summaryrefslogtreecommitdiffstats
path: root/kbabel/filters/gettext/gettextexport.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kbabel/filters/gettext/gettextexport.cpp')
-rw-r--r--kbabel/filters/gettext/gettextexport.cpp352
1 files changed, 352 insertions, 0 deletions
diff --git a/kbabel/filters/gettext/gettextexport.cpp b/kbabel/filters/gettext/gettextexport.cpp
new file mode 100644
index 00000000..e951847b
--- /dev/null
+++ b/kbabel/filters/gettext/gettextexport.cpp
@@ -0,0 +1,352 @@
+/* ****************************************************************************
+ This file is part of KBabel
+
+ Copyright (C) 1999-2000 by Matthias Kiefer <matthias.kiefer@gmx.de>
+ 2001-2002 by Stanislav Visnovsky <visnovsky@kde.org>
+ Copyright (C) 2005,2006 by Nicolas GOUTTE <goutte@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+
+**************************************************************************** */
+
+#include "gettextexport.h"
+
+#include <resources.h>
+#include "catalog.h"
+#include "catalogitem.h"
+#include "catalogsettings.h"
+#include "kbprojectsettings.h"
+
+#include <qfile.h>
+#include <qtextcodec.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kgenericfactory.h>
+
+K_EXPORT_COMPONENT_FACTORY( kbabel_gettextexport, KGenericFactory<GettextExportPlugin> ( "kbabelgettextexportfilter" ) )
+
+using namespace KBabel;
+
+GettextExportPlugin::GettextExportPlugin(QObject* parent, const char* name, const QStringList &) :
+ CatalogExportPlugin(parent,name), m_wrapWidth( -1 )
+{
+}
+
+ConversionStatus GettextExportPlugin::save(const QString& localFile , const QString& mimetype, const Catalog* catalog)
+{
+ // check, whether we know how to handle the extra data
+ if( catalog->importPluginID() != "GNU gettext")
+ return UNSUPPORTED_TYPE;
+
+ // we support on the application/x-gettext MIME type
+ if( mimetype != "application/x-gettext")
+ return UNSUPPORTED_TYPE;
+
+ QFile file(localFile);
+
+ if(file.open(IO_WriteOnly))
+ {
+ int progressRatio = QMAX(100/ QMAX(catalog->numberOfEntries(),1), 1);
+ emit signalResetProgressBar(i18n("saving file"),100);
+
+ QTextStream stream(&file);
+
+ SaveSettings _saveSettings = catalog->saveSettings();
+
+ if(_saveSettings.useOldEncoding && catalog->fileCodec())
+ {
+ stream.setCodec(catalog->fileCodec());
+ }
+ else
+ {
+ switch(_saveSettings.encoding)
+ {
+ case ProjectSettingsBase::UTF8:
+ stream.setCodec(QTextCodec::codecForName("utf-8"));
+ break;
+ case ProjectSettingsBase::UTF16:
+ stream.setCodec(QTextCodec::codecForName("utf-16"));
+ break;
+ default:
+ stream.setCodec(QTextCodec::codecForLocale());
+ break;
+ }
+ }
+
+ // only save header if it is not empty
+ const QString headerComment( catalog->header().comment() );
+ // ### TODO: why is this useful to have a header with an empty msgstr?
+ if( !headerComment.isEmpty() || !catalog->header().msgstr().isEmpty() )
+ {
+ // write header
+ writeComment( stream, headerComment );
+
+ const QString headerMsgid = catalog->header().msgid().first();
+
+ // Gettext PO files should have an empty msgid as header
+ if ( !headerMsgid.isEmpty() )
+ {
+ // ### TODO: perhaps it is grave enough for a user message
+ kdWarning() << "Non-empty msgid for the header, assuming empty msgid!" << endl << headerMsgid << "---" << endl;
+ }
+
+ // ### FIXME: if it is the header, then the msgid should be empty! (Even if KBabel has made something out of a non-header first entry!)
+ stream << "msgid \"\"\n";
+
+ writeKeyword( stream, "msgstr", catalog->header().msgstr().first() );
+
+ stream << "\n";
+ }
+
+ QStringList list;
+ for( uint counter = 0; counter < catalog->numberOfEntries() ; counter++ )
+ {
+ if(counter%10==0) {
+ emit signalProgress(counter/progressRatio);
+ }
+
+ // write entry
+ writeComment( stream, catalog->comment(counter) );
+
+ const QString msgctxt = catalog->msgctxt(counter);
+ if (! msgctxt.isEmpty() )
+ {
+ writeKeyword( stream, "msgctxt", msgctxt );
+ }
+
+ writeKeyword( stream, "msgid", catalog->msgid( counter ).first() );
+ if( catalog->pluralForm( counter ) == Gettext )
+ {
+ writeKeyword( stream, "msgid_plural", catalog->msgid( counter ).last() );
+ }
+
+ if( catalog->pluralForm(counter) != Gettext)
+ {
+ writeKeyword( stream, "msgstr", catalog->msgstr( counter ).first() );
+ }
+ else
+ {
+ kdDebug(KBABEL) << "Saving gettext plural form" << endl;
+ const int forms = catalog->msgstr( counter ).count();
+ for ( int i = 0; i < forms; ++i )
+ {
+ QString keyword ( "msgstr[" );
+ keyword += QString::number( i );
+ keyword += ']';
+
+ writeKeyword( stream, keyword, *( catalog->msgstr( counter ).at( i ) ) );
+ }
+ }
+
+ stream << "\n";
+
+ kapp->processEvents(10);
+ if( isStopped() )
+ {
+ return STOPPED;
+ }
+ }
+
+ if( _saveSettings.saveObsolete )
+ {
+ QValueList<QString>::ConstIterator oit;
+
+ QStringList _obsolete = catalog->catalogExtraData();
+
+ for( oit = _obsolete.begin(); oit != _obsolete.end(); ++oit )
+ {
+ stream << (*oit) << "\n\n";
+
+ kapp->processEvents(10);
+ if( isStopped() )
+ {
+ return STOPPED;
+ }
+ }
+ }
+
+ emit signalProgress(100);
+ file.close();
+
+ emit signalClearProgressBar();
+ }
+ else
+ {
+ //emit signalError(i18n("Wasn't able to open file %1").arg(filename.ascii()));
+ return OS_ERROR;
+ }
+
+ return OK;
+}
+
+void GettextExportPlugin::writeComment( QTextStream& stream, const QString& comment ) const
+{
+ if( !comment.isEmpty() )
+ {
+ // We must check that each comment line really starts with a #, to avoid syntax errors
+ int pos = 0;
+ for(;;)
+ {
+ const int newpos = comment.find( '\n', pos, false );
+ if ( newpos == pos )
+ {
+ ++pos;
+ stream << "\n";
+ continue;
+ }
+ const QString span ( ( newpos == -1 ) ? comment.mid( pos ) : comment.mid( pos, newpos-pos ) );
+
+ const int len = span.length();
+ QString spaces; // Stored leading spaces
+ for ( int i = 0 ; i < len ; ++i )
+ {
+ const QChar& ch = span[ i ];
+ if ( ch == '#' )
+ {
+ stream << spaces << span.mid( i );
+ break;
+ }
+ else if ( ch == ' ' || ch == '\t' )
+ {
+ // We have a leading white space character, so store it temporary
+ spaces += ch;
+ }
+ else
+ {
+ // Not leading white space and not a # character. so consider that the # character was missing at first position.
+ stream << "# " << spaces << span.mid( i );
+ break;
+ }
+ }
+ stream << "\n";
+
+ if ( newpos == -1 )
+ break;
+ else
+ pos = newpos + 1;
+ }
+ }
+}
+
+void GettextExportPlugin::writeKeyword( QTextStream& stream, const QString& keyword, const QString& text ) const
+{
+ if ( text.isEmpty() )
+ {
+ // Whatever the wrapping mode, an empty line is an empty line
+ stream << keyword << " \"\"\n";
+ return;
+ }
+ else if ( m_wrapWidth == -1 )
+ {
+ // Traditional KBabel wrapping
+ QStringList list = QStringList::split( '\n', text );
+
+ if ( text.startsWith( "\n" ) )
+ list.prepend( QString() );
+
+ if(list.isEmpty())
+ list.append( QString() );
+
+ if( list.count() > 1 )
+ list.prepend( QString() );
+
+ stream << keyword << " ";
+
+ QStringList::const_iterator it;
+ for( it = list.constBegin(); it != list.constEnd(); ++it )
+ {
+ stream << "\"" << (*it) << "\"\n";
+ }
+ return;
+ }
+ else if ( ( !m_wrapWidth )
+ || ( m_wrapWidth < 0 ) // Unknown special wrapping, so assume "no wrap" instead
+ )
+ {
+ // No wrapping (like Gettext's --no.wrap or -w0 )
+
+ // we need to remove the \n characters, as they are extra characters
+ QString realText( text );
+ realText.remove( '\n' );
+ stream << keyword << " \"" << realText << "\"\n";
+ return;
+ }
+
+ // ### TODO: test!
+ // Normal wrapping like Gettext's -w parameter with a value bigger than 0
+ // From here on, we assume that we have an non-empty text and a positive non-null m_wrapWidth
+
+ // we need to remove the \n characters, as they are extra characters
+ QString realText( text );
+ realText.remove( '\n' );
+
+ bool needFirstEmptyLine = false;
+ if ( realText.find( "\\n" ) != -1 )
+ {
+ // We have more than one (logical) line, so write the extra empty line
+ needFirstEmptyLine = true;
+ }
+ else
+ {
+ // We must see if the text would fit in one line, including the keyword, a space and two quote characters.
+ const int rest = text.length() + keyword.length() + 3 - m_wrapWidth;
+ if ( rest > 0 )
+ {
+ needFirstEmptyLine = true;
+ }
+ }
+ int availableWidth = m_wrapWidth;
+ if ( needFirstEmptyLine )
+ {
+ stream << keyword << " \"\"\n";
+ }
+ else
+ {
+ stream << keyword << " ";
+ availableWidth -= keyword.length();
+ availableWidth--; // The space after the keyword
+ }
+
+ const int spanLength = realText.length();
+ for ( int pos = 0; pos < spanLength; )
+ {
+ availableWidth -= 2; // Count the quote characters
+ if ( availableWidth < 2 )
+ {
+ // Be sure that at least two useful characters are written, even if the wrap width is too small
+ availableWidth = 2;
+ }
+ const int newlinePos = realText.find ( "\\n", pos );
+ if ( ( newlinePos >= 0 ) && ( newlinePos - pos + 2 < availableWidth ) )
+ {
+ // The newline is near than the maximum available numbers of characters
+ availableWidth = newlinePos - pos + 2;
+ }
+ stream << '\"' << realText.mid( pos, availableWidth ) << "\"\n";
+ pos += availableWidth;
+ }
+}