From 998f21e02a725cd553d7c278819f67cd81295af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sl=C3=A1vek=20Banko?= Date: Mon, 24 Jun 2013 02:08:15 +0200 Subject: Initial import --- src/fileexporterbibtex.cpp | 491 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 491 insertions(+) create mode 100644 src/fileexporterbibtex.cpp (limited to 'src/fileexporterbibtex.cpp') 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 +#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(), '}' ); + } + +} -- cgit v1.2.3