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.
492 lines
20 KiB
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(), '}' );
|
|
}
|
|
|
|
}
|