summaryrefslogtreecommitdiffstats
path: root/src/fileexporterbibtex.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/fileexporterbibtex.cpp')
-rw-r--r--src/fileexporterbibtex.cpp491
1 files changed, 491 insertions, 0 deletions
diff --git a/src/fileexporterbibtex.cpp b/src/fileexporterbibtex.cpp
new file mode 100644
index 0000000..240754d
--- /dev/null
+++ b/src/fileexporterbibtex.cpp
@@ -0,0 +1,491 @@
+/***************************************************************************
+* Copyright (C) 2004-2009 by Thomas Fischer *
+* fischer@unix-ag.uni-kl.de *
+* *
+* 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., *
+* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+***************************************************************************/
+#include <file.h>
+#include <element.h>
+#include <entry.h>
+#include <macro.h>
+#include <preamble.h>
+#include <value.h>
+#include <comment.h>
+#include <encoderlatex.h>
+
+#include "fileexporterbibtex.h"
+
+namespace BibTeX
+{
+
+ FileExporterBibTeX::FileExporterBibTeX() : FileExporter(),
+ m_iconvBufferSize( 16384 ), m_stringOpenDelimiter( '"' ), m_stringCloseDelimiter( '"' ), m_keywordCasing( kcCamelCase ), m_encoding( "latex" ), m_protectCasing( FALSE ), cancelFlag( FALSE )
+ {
+ m_iconvBuffer = new char[m_iconvBufferSize];
+ }
+
+ FileExporterBibTeX::~FileExporterBibTeX()
+ {
+ delete[] m_iconvBuffer;
+ }
+
+ bool FileExporterBibTeX::save( QIODevice* iodevice, const File* bibtexfile, QStringList * /*errorLog*/ )
+ {
+ m_mutex.lock();
+ bool result = TRUE;
+
+ /**
+ * Categorize elements from the bib file into four groups,
+ * to ensure that BibTeX finds all connected elements
+ * in the correct order.
+ */
+
+ QValueList<Comment*> parameterCommentsList;
+ QValueList<Preamble*> preambleList;
+ QValueList<Macro*> macroList;
+ QValueList<Entry*> crossRefingEntryList;
+ QValueList<Element*> remainingList;
+
+ for ( File::ElementList::const_iterator it = bibtexfile->elements.begin(); it != bibtexfile->elements.end() && result && !cancelFlag; it++ )
+ {
+ Preamble *preamble = dynamic_cast<Preamble*>( *it );
+ if ( preamble != NULL )
+ preambleList.append( preamble );
+ else
+ {
+ Macro *macro = dynamic_cast<Macro*>( *it );
+ if ( macro != NULL )
+ macroList.append( macro );
+ else
+ {
+ Entry *entry = dynamic_cast<Entry*>( *it );
+ if (( entry != NULL ) && ( entry->getField( EntryField::ftCrossRef ) != NULL ) )
+ crossRefingEntryList.append( entry );
+ else
+ {
+ Comment *comment = dynamic_cast<Comment*>( *it );
+ QString commentText = QString::null;
+ /** check if this file requests a special encoding */
+ if ( comment != NULL && comment->useCommand() && (( commentText = comment->text().lower() ) ).startsWith( "x-kbibtex-encoding=" ) )
+ {
+ m_encoding = commentText.mid( 19 );
+ qDebug( "Switching encoding to <%s>", m_encoding.latin1() );
+ parameterCommentsList.append( comment );
+ }
+ else
+ remainingList.append( *it );
+ }
+ }
+ }
+ }
+
+ int totalElements = ( int ) bibtexfile->count();
+ int currentPos = 0;
+
+ const char *encodingTo = m_encoding == "latex" ? "utf-8\0" : m_encoding.append( "\0" ).ascii();
+ m_iconvHandle = iconv_open( encodingTo, "utf-8" );
+
+ /** before anything else, write parameter comments */
+ for ( QValueList<Comment*>::iterator it = parameterCommentsList.begin(); it != parameterCommentsList.end() && result && !cancelFlag; it++ )
+ {
+ result &= writeComment( *iodevice, *it );
+ emit progress( ++currentPos, totalElements );
+ }
+
+ /** first, write preambles and strings (macros) at the beginning */
+ for ( QValueList<Preamble*>::iterator it = preambleList.begin(); it != preambleList.end() && result && !cancelFlag; it++ )
+ {
+ result &= writePreamble( *iodevice, *it );
+ emit progress( ++currentPos, totalElements );
+ }
+
+ for ( QValueList<Macro*>::iterator it = macroList.begin(); it != macroList.end() && result && !cancelFlag; it++ )
+ {
+ result &= writeMacro( *iodevice, *it );
+ emit progress( ++currentPos, totalElements );
+ }
+
+ /** second, write cross-referencing elements */
+ for ( QValueList<Entry*>::iterator it = crossRefingEntryList.begin(); it != crossRefingEntryList.end() && result && !cancelFlag; it++ )
+ {
+ result &= writeEntry( *iodevice, *it );
+ emit progress( ++currentPos, totalElements );
+ }
+
+ /** third, write remaining elements */
+ for ( QValueList<Element*>::iterator it = remainingList.begin(); it != remainingList.end() && result && !cancelFlag; it++ )
+ {
+ Entry *entry = dynamic_cast<Entry*>( *it );
+ if ( entry != NULL )
+ result &= writeEntry( *iodevice, entry );
+ else
+ {
+ Comment *comment = dynamic_cast<Comment*>( *it );
+ if ( comment != NULL )
+ result &= writeComment( *iodevice, comment );
+ }
+ emit progress( ++currentPos, totalElements );
+ }
+
+ iconv_close( m_iconvHandle );
+ m_mutex.unlock();
+ return result && !cancelFlag;
+ }
+
+ bool FileExporterBibTeX::save( QIODevice* iodevice, const Element* element, QStringList * /*errorLog*/ )
+ {
+ m_mutex.lock();
+ bool result = FALSE;
+
+ const char *encodingTo = m_encoding == "latex" ? "utf-8\0" : m_encoding.append( "\0" ).ascii();
+ m_iconvHandle = iconv_open( encodingTo, "utf-8" );
+
+ const Entry *entry = dynamic_cast<const Entry*>( element );
+ if ( entry != NULL )
+ result |= writeEntry( *iodevice, entry );
+ else
+ {
+ const Macro * macro = dynamic_cast<const Macro*>( element );
+ if ( macro != NULL )
+ result |= writeMacro( *iodevice, macro );
+ else
+ {
+ const Comment * comment = dynamic_cast<const Comment*>( element );
+ if ( comment != NULL )
+ result |= writeComment( *iodevice, comment );
+ else
+ {
+ const Preamble * preamble = dynamic_cast<const Preamble*>( element );
+ if ( preamble != NULL )
+ result |= writePreamble( *iodevice, preamble );
+ }
+ }
+ }
+
+ iconv_close( m_iconvHandle );
+ m_mutex.unlock();
+ return result && !cancelFlag;
+ }
+
+ void FileExporterBibTeX::cancel()
+ {
+ cancelFlag = TRUE;
+ }
+
+ bool FileExporterBibTeX::writeEntry( QIODevice &device, const Entry* entry )
+ {
+ writeString( device, QString( "@%1{ %2" ).arg( applyKeywordCasing( entry->entryTypeString() ) ).arg( entry->id() ) );
+
+ for ( Entry::EntryFields::ConstIterator it = entry->begin(); it != entry->end(); ++it )
+ {
+ EntryField *field = *it;
+ QString text = valueToString( field->value(), field->fieldType(), field->fieldTypeName() );
+ if ( m_protectCasing && dynamic_cast<BibTeX::PlainText*>( field->value()->items.first() ) != NULL && ( field->fieldType() == EntryField::ftTitle || field->fieldType() == EntryField::ftBookTitle || field->fieldType() == EntryField::ftSeries ) )
+ addProtectiveCasing( text );
+ writeString( device, QString( ",\n\t%1 = %2" ).arg( field->fieldTypeName() ).arg( text ) );
+ }
+ writeString( device, "\n}\n\n" );
+ return TRUE;
+ }
+
+ bool FileExporterBibTeX::writeMacro( QIODevice &device, const Macro *macro )
+ {
+ QString text = valueToString( macro->value() );
+ if ( m_protectCasing )
+ addProtectiveCasing( text );
+
+ writeString( device, QString( "@%1{ %2 = %3 }\n\n" ).arg( applyKeywordCasing( "String" ) ).arg( macro->key() ).arg( text ) );
+
+ return TRUE;
+ }
+
+ bool FileExporterBibTeX::writeComment( QIODevice &device, const Comment *comment )
+ {
+ if ( !comment->useCommand() )
+ {
+ QString text = comment->text() ;
+
+ if ( m_encoding == "latex" )
+ text = EncoderLaTeX::currentEncoderLaTeX() ->encode( text );
+
+ QStringList commentLines = QStringList::split( '\n', text );
+ for ( QStringList::Iterator it = commentLines.begin(); it != commentLines.end(); it++ )
+ {
+ writeString( device, ( *it ).append( "\n" ) );
+ }
+ writeString( device, "\n" );
+ }
+ else
+ {
+ QString text = comment->text() ;
+
+ if ( m_encoding == "latex" )
+ text = EncoderLaTeX::currentEncoderLaTeX() ->encode( text );
+
+ writeString( device, QString( "@%1{%2}\n\n" ).arg( applyKeywordCasing( "Comment" ) ).arg( text ) );
+ }
+ return TRUE;
+ }
+
+ bool FileExporterBibTeX::writePreamble( QIODevice &device, const Preamble* preamble )
+ {
+ writeString( device, QString( "@%1{%2}\n\n" ).arg( applyKeywordCasing( "Preamble" ) ).arg( valueToString( preamble->value() ) ) );
+
+ return TRUE;
+ }
+
+ bool FileExporterBibTeX::writeString( QIODevice &device, const QString& text )
+ {
+ size_t utf8datasize = 1;
+ QCString utf8 = text.utf8();
+ char *utf8data = utf8.data();
+ utf8datasize = utf8.length();
+ char *outputdata = m_iconvBuffer;
+ size_t outputdatasize = m_iconvBufferSize;
+
+ size_t result = iconv( m_iconvHandle, &utf8data, &utf8datasize, &outputdata, &outputdatasize );
+ if ( result != 0 )
+ {
+ qWarning( "Cannot convert string using iconv" );
+ return false;
+ }
+
+ if ( device.writeBlock( m_iconvBuffer, m_iconvBufferSize - outputdatasize ) != ( int )( m_iconvBufferSize - outputdatasize ) )
+ {
+ qWarning( "Cannot write string to device" );
+ return false;
+ }
+
+ return true;
+ }
+
+ void FileExporterBibTeX::setStringDelimiter( const QChar& stringOpenDelimiter, const QChar& stringCloseDelimiter )
+ {
+ m_stringOpenDelimiter = stringOpenDelimiter;
+ m_stringCloseDelimiter = stringCloseDelimiter;
+ }
+
+ void FileExporterBibTeX::setKeywordCasing( const KeywordCasing keywordCasing )
+ {
+ m_keywordCasing = keywordCasing;
+ }
+
+ void FileExporterBibTeX::setEncoding( const QString& encoding )
+ {
+ m_encoding = encoding;
+ }
+
+ void FileExporterBibTeX::setEnclosingCurlyBrackets( bool protectCasing )
+ {
+ m_protectCasing = protectCasing;
+ }
+
+ QString FileExporterBibTeX::valueToString( const Value *value, const EntryField::FieldType fieldType, const QString &fieldTypeName )
+ {
+ if ( value == NULL )
+ return "";
+
+ QString result;
+ bool isFirst = TRUE;
+ EncoderLaTeX *encoder = EncoderLaTeX::currentEncoderLaTeX();
+
+ for ( QValueList<ValueItem*>::ConstIterator it = value->items.begin(); it != value->items.end(); ++it )
+ {
+ if ( !isFirst )
+ result.append( " # " );
+ else
+ isFirst = FALSE;
+
+ MacroKey *macroKey = dynamic_cast<MacroKey*>( *it );
+ if ( macroKey != NULL )
+ result.append( macroKey->text() );
+ else
+ {
+ QString text;
+ BibTeX::PersonContainer *personContainer = dynamic_cast<BibTeX::PersonContainer*>( *it );
+ BibTeX::PlainText *plainText = dynamic_cast<BibTeX::PlainText*>( *it );
+ BibTeX::KeywordContainer *keywordContainer = dynamic_cast<BibTeX::KeywordContainer*>( *it );
+
+ if ( plainText != NULL )
+ text = plainText->text();
+ else if ( keywordContainer != NULL )
+ {
+ bool first = TRUE;
+ for ( QValueList<Keyword*>::Iterator it = keywordContainer->keywords.begin(); it != keywordContainer->keywords.end(); ++it )
+ {
+ if ( !first )
+ text.append( ", " );
+ else
+ first = FALSE;
+ text.append(( *it )->text() );
+ }
+ }
+ else if ( personContainer != NULL )
+ {
+ bool first = TRUE;
+ for ( QValueList<Person*>::Iterator it = personContainer->persons.begin(); it != personContainer->persons.end(); ++it )
+ {
+ if ( !first )
+ text.append( " and " );
+ else
+ first = FALSE;
+
+ QString v = ( *it )->firstName();
+ if ( !v.isEmpty() )
+ {
+ bool requiresQuoting = requiresPersonQuoting( v, FALSE );
+ if ( requiresQuoting ) text.append( "{" );
+ text.append( v );
+ if ( requiresQuoting ) text.append( "}" );
+ text.append( " " );
+ }
+
+ v = ( *it )->lastName();
+ if ( !v.isEmpty() )
+ {
+ /** Multi-part surnames (such as "Garcia Marquez") have to be enquoted.
+ * However, "von"-Parts (as in "von Hofmannsthal") must _not_ be enquoted.
+ * Examples:
+ * -- Robson de Souza
+ * -- Hartmann von der Tann
+ * -- Ronaldo de {Assis Moreira} ("Ronaldo de Assis Moreira" works as well)
+ * -- Ailton {Goncalves da Silva}
+ * -- Gloria von {Thurn und Taxis}
+ * Thus we split the von-Parts from the surname (= everything after the first upcase char).
+ * FIXME: Make the personContainer aware of von-Parts and jr-Parts, instead.
+ */
+ QStringList list = QStringList::split( " ", v );
+ QString von;
+ for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it )
+ {
+ QString str = *it;
+ if ( str != "others" && str[0].category() == QChar::Letter_Lowercase )
+ {
+ von += *it;
+ von += " ";
+ }
+ else
+ break;
+ }
+ if ( !von.isEmpty() )
+ {
+ text.append( von );
+ v = v.right( v.length() - von.length() );
+ }
+ bool requiresQuoting = requiresPersonQuoting( v, TRUE );
+ if ( requiresQuoting ) text.append( "{" );
+ text.append( v );
+ if ( requiresQuoting ) text.append( "}" );
+ }
+ }
+ }
+
+ if ( m_encoding == "latex" )
+ text = encoder->encodeSpecialized( text, fieldType );
+
+ if ( fieldType == EntryField::ftURL || fieldType == EntryField::ftDoi || ( fieldType == EntryField::ftUnknown && fieldTypeName.lower() == "slaccitation" ) )
+ removeBackslashQuoting( text );
+
+ /** if the text to save contains a quote char ("),
+ * force string delimiters to be curly brackets,
+ * as quote chars as string delimiters would result
+ * in parser failures
+ */
+ QChar stringOpenDelimiter = m_stringOpenDelimiter;
+ QChar stringCloseDelimiter = m_stringCloseDelimiter;
+ if ( text.contains( '"' ) && ( m_stringOpenDelimiter == '"' || m_stringCloseDelimiter == '"' ) )
+ {
+ stringOpenDelimiter = '{';
+ stringCloseDelimiter = '}';
+ }
+
+ result.append( stringOpenDelimiter ).append( text ).append( stringCloseDelimiter );
+ }
+ }
+
+ return result;
+ }
+
+ void FileExporterBibTeX::removeBackslashQuoting( QString &text )
+ {
+ text.replace( "\\&", "&" ).replace( "\\#", "#" ).replace( "\\_", "_" ).replace( "\\%", "%" );
+ }
+
+ QString FileExporterBibTeX::applyKeywordCasing( const QString &keyword )
+ {
+ switch ( m_keywordCasing )
+ {
+ case kcLowerCase: return keyword.lower();
+ case kcInitialCapital: return keyword.at( 0 ) + keyword.lower().mid( 1 );
+ case kcCapital: return keyword.upper();
+ default: return keyword;
+ }
+ }
+
+ bool FileExporterBibTeX::requiresPersonQuoting( const QString &text, bool isLastName )
+ {
+ if ( isLastName && !text.contains( " " ) )
+ /** Last name contains NO spaces, no quoting necessary */
+ return FALSE;
+ else if ( isLastName && text[0].category() == QChar::Letter_Lowercase )
+ /** Last name starts with lower case character (e.g. as in "van der Linden") */
+ return FALSE;
+ else if ( !isLastName && !text.contains( " and " ) )
+ /** First name contains no " and " no quoting necessary */
+ return FALSE;
+ else if ( text[0] != '{' || text[text.length()-1] != '}' )
+ /** as either last name contains spaces or first name contains " and " and there is no protective quoting yet, there must be a protective quoting added */
+ return TRUE;
+
+ /** check for cases like "{..}..{..}", which must be surrounded with a protective quoting, too */
+ int bracketCounter = 0;
+ for ( int i = text.length() - 1; i >= 0; --i )
+ {
+ if ( text[i] == '{' )
+ ++bracketCounter;
+ else if ( text[i] == '}' )
+ --bracketCounter;
+ if ( bracketCounter == 0 && i > 0 )
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ void FileExporterBibTeX::addProtectiveCasing( QString &text )
+ {
+ if (( text[0] != '"' || text[text.length()-1] != '"' ) && ( text[0] != '{' || text[text.length()-1] != '}' ) )
+ {
+ /** nothing to protect, as this is no text string */
+ return;
+ }
+
+ bool addBrackets = TRUE;
+
+ if ( text[1] == '{' && text[text.length() - 2] == '}' )
+ {
+ addBrackets = FALSE;
+ int count = 0;
+ for ( int i = text.length() - 2; !addBrackets && i >= 1; --i )
+ if ( text[i] == '{' )++count;
+ else if ( text[i] == '}' )--count;
+ else if ( count == 0 ) addBrackets = TRUE;
+ }
+
+ if ( addBrackets )
+ text.insert( 1, '{' ).insert( text.length(), '}' );
+ }
+
+}