You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kbibtex/src/fileexporterbibtex.cpp

492 lines
20 KiB

/***************************************************************************
* 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( TQIODevice* iodevice, const File* bibtexfile, TQStringList * /*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.
*/
TQValueList<Comment*> parameterCommentsList;
TQValueList<Preamble*> preambleList;
TQValueList<Macro*> macroList;
TQValueList<Entry*> crossRefingEntryList;
TQValueList<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 );
TQString commentText = TQString::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 );
tqDebug( "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 ( TQValueList<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 ( TQValueList<Preamble*>::iterator it = preambleList.begin(); it != preambleList.end() && result && !cancelFlag; it++ )
{
result &= writePreamble( *iodevice, *it );
emit progress( ++currentPos, totalElements );
}
for ( TQValueList<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 ( TQValueList<Entry*>::iterator it = crossRefingEntryList.begin(); it != crossRefingEntryList.end() && result && !cancelFlag; it++ )
{
result &= writeEntry( *iodevice, *it );
emit progress( ++currentPos, totalElements );
}
/** third, write remaining elements */
for ( TQValueList<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( TQIODevice* iodevice, const Element* element, TQStringList * /*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( TQIODevice &device, const Entry* entry )
{
writeString( device, TQString( "@%1{ %2" ).arg( applyKeywordCasing( entry->entryTypeString() ) ).arg( entry->id() ) );
for ( Entry::EntryFields::ConstIterator it = entry->begin(); it != entry->end(); ++it )
{
EntryField *field = *it;
TQString 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, TQString( ",\n\t%1 = %2" ).arg( field->fieldTypeName() ).arg( text ) );
}
writeString( device, "\n}\n\n" );
return TRUE;
}
bool FileExporterBibTeX::writeMacro( TQIODevice &device, const Macro *macro )
{
TQString text = valueToString( macro->value() );
if ( m_protectCasing )
addProtectiveCasing( text );
writeString( device, TQString( "@%1{ %2 = %3 }\n\n" ).arg( applyKeywordCasing( "String" ) ).arg( macro->key() ).arg( text ) );
return TRUE;
}
bool FileExporterBibTeX::writeComment( TQIODevice &device, const Comment *comment )
{
if ( !comment->useCommand() )
{
TQString text = comment->text() ;
if ( m_encoding == "latex" )
text = EncoderLaTeX::currentEncoderLaTeX() ->encode( text );
TQStringList commentLines = TQStringList::split( '\n', text );
for ( TQStringList::Iterator it = commentLines.begin(); it != commentLines.end(); it++ )
{
writeString( device, ( *it ).append( "\n" ) );
}
writeString( device, "\n" );
}
else
{
TQString text = comment->text() ;
if ( m_encoding == "latex" )
text = EncoderLaTeX::currentEncoderLaTeX() ->encode( text );
writeString( device, TQString( "@%1{%2}\n\n" ).arg( applyKeywordCasing( "Comment" ) ).arg( text ) );
}
return TRUE;
}
bool FileExporterBibTeX::writePreamble( TQIODevice &device, const Preamble* preamble )
{
writeString( device, TQString( "@%1{%2}\n\n" ).arg( applyKeywordCasing( "Preamble" ) ).arg( valueToString( preamble->value() ) ) );
return TRUE;
}
bool FileExporterBibTeX::writeString( TQIODevice &device, const TQString& text )
{
size_t utf8datasize = 1;
TQCString 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 )
{
tqWarning( "Cannot convert string using iconv" );
return false;
}
if ( device.writeBlock( m_iconvBuffer, m_iconvBufferSize - outputdatasize ) != ( int )( m_iconvBufferSize - outputdatasize ) )
{
tqWarning( "Cannot write string to device" );
return false;
}
return true;
}
void FileExporterBibTeX::setStringDelimiter( const TQChar& stringOpenDelimiter, const TQChar& stringCloseDelimiter )
{
m_stringOpenDelimiter = stringOpenDelimiter;
m_stringCloseDelimiter = stringCloseDelimiter;
}
void FileExporterBibTeX::setKeywordCasing( const KeywordCasing keywordCasing )
{
m_keywordCasing = keywordCasing;
}
void FileExporterBibTeX::setEncoding( const TQString& encoding )
{
m_encoding = encoding;
}
void FileExporterBibTeX::setEnclosingCurlyBrackets( bool protectCasing )
{
m_protectCasing = protectCasing;
}
TQString FileExporterBibTeX::valueToString( const Value *value, const EntryField::FieldType fieldType, const TQString &fieldTypeName )
{
if ( value == NULL )
return "";
TQString result;
bool isFirst = TRUE;
EncoderLaTeX *encoder = EncoderLaTeX::currentEncoderLaTeX();
for ( TQValueList<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
{
TQString 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 ( TQValueList<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 ( TQValueList<Person*>::Iterator it = personContainer->persons.begin(); it != personContainer->persons.end(); ++it )
{
if ( !first )
text.append( " and " );
else
first = FALSE;
TQString 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.
*/
TQStringList list = TQStringList::split( " ", v );
TQString von;
for ( TQStringList::Iterator it = list.begin(); it != list.end(); ++it )
{
TQString str = *it;
if ( str != "others" && str[0].category() == TQChar::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
*/
TQChar stringOpenDelimiter = m_stringOpenDelimiter;
TQChar stringCloseDelimiter = m_stringCloseDelimiter;
if ( text.contains( '"' ) && ( m_stringOpenDelimiter == '"' || m_stringCloseDelimiter == '"' ) )
{
stringOpenDelimiter = '{';
stringCloseDelimiter = '}';
}
result.append( stringOpenDelimiter ).append( text ).append( stringCloseDelimiter );
}
}
return result;
}
void FileExporterBibTeX::removeBackslashQuoting( TQString &text )
{
text.replace( "\\&", "&" ).replace( "\\#", "#" ).replace( "\\_", "_" ).replace( "\\%", "%" );
}
TQString FileExporterBibTeX::applyKeywordCasing( const TQString &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 TQString &text, bool isLastName )
{
if ( isLastName && !text.contains( " " ) )
/** Last name contains NO spaces, no quoting necessary */
return FALSE;
else if ( isLastName && text[0].category() == TQChar::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( TQString &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(), '}' );
}
}