/*************************************************************************** * 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 #include #include #include #include #include #include #include #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 parameterCommentsList; QValueList preambleList; QValueList macroList; QValueList crossRefingEntryList; QValueList remainingList; for ( File::ElementList::const_iterator it = bibtexfile->elements.begin(); it != bibtexfile->elements.end() && result && !cancelFlag; it++ ) { Preamble *preamble = dynamic_cast( *it ); if ( preamble != NULL ) preambleList.append( preamble ); else { Macro *macro = dynamic_cast( *it ); if ( macro != NULL ) macroList.append( macro ); else { Entry *entry = dynamic_cast( *it ); if (( entry != NULL ) && ( entry->getField( EntryField::ftCrossRef ) != NULL ) ) crossRefingEntryList.append( entry ); else { Comment *comment = dynamic_cast( *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::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::iterator it = preambleList.begin(); it != preambleList.end() && result && !cancelFlag; it++ ) { result &= writePreamble( *iodevice, *it ); emit progress( ++currentPos, totalElements ); } for ( QValueList::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::iterator it = crossRefingEntryList.begin(); it != crossRefingEntryList.end() && result && !cancelFlag; it++ ) { result &= writeEntry( *iodevice, *it ); emit progress( ++currentPos, totalElements ); } /** third, write remaining elements */ for ( QValueList::iterator it = remainingList.begin(); it != remainingList.end() && result && !cancelFlag; it++ ) { Entry *entry = dynamic_cast( *it ); if ( entry != NULL ) result &= writeEntry( *iodevice, entry ); else { Comment *comment = dynamic_cast( *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( element ); if ( entry != NULL ) result |= writeEntry( *iodevice, entry ); else { const Macro * macro = dynamic_cast( element ); if ( macro != NULL ) result |= writeMacro( *iodevice, macro ); else { const Comment * comment = dynamic_cast( element ); if ( comment != NULL ) result |= writeComment( *iodevice, comment ); else { const Preamble * preamble = dynamic_cast( 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( 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::ConstIterator it = value->items.begin(); it != value->items.end(); ++it ) { if ( !isFirst ) result.append( " # " ); else isFirst = FALSE; MacroKey *macroKey = dynamic_cast( *it ); if ( macroKey != NULL ) result.append( macroKey->text() ); else { QString text; BibTeX::PersonContainer *personContainer = dynamic_cast( *it ); BibTeX::PlainText *plainText = dynamic_cast( *it ); BibTeX::KeywordContainer *keywordContainer = dynamic_cast( *it ); if ( plainText != NULL ) text = plainText->text(); else if ( keywordContainer != NULL ) { bool first = TRUE; for ( QValueList::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::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(), '}' ); } }