1
/***************************************************************************
2
* Copyright (C) 2004-2009 by Thomas Fischer *
3
* fischer@unix-ag.uni-kl.de *
5
* This program is free software; you can redistribute it and/or modify *
6
* it under the terms of the GNU General Public License as published by *
7
* the Free Software Foundation; either version 2 of the License, or *
8
* (at your option) any later version. *
10
* This program is distributed in the hope that it will be useful, *
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13
* GNU General Public License for more details. *
15
* You should have received a copy of the GNU General Public License *
16
* along with this program; if not, write to the *
17
* Free Software Foundation, Inc., *
18
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19
***************************************************************************/
27
#include <encoderlatex.h>
29
#include "fileexporterbibtex.h"
34
FileExporterBibTeX::FileExporterBibTeX() : FileExporter(),
35
m_iconvBufferSize( 16384 ), m_stringOpenDelimiter( '"' ), m_stringCloseDelimiter( '"' ), m_keywordCasing( kcCamelCase ), m_encoding( "latex" ), m_protectCasing( FALSE ), cancelFlag( FALSE )
37
m_iconvBuffer = new char[m_iconvBufferSize];
40
FileExporterBibTeX::~FileExporterBibTeX()
42
delete[] m_iconvBuffer;
45
bool FileExporterBibTeX::save( QIODevice* iodevice, const File* bibtexfile, QStringList * /*errorLog*/ )
51
* Categorize elements from the bib file into four groups,
52
* to ensure that BibTeX finds all connected elements
53
* in the correct order.
56
QValueList<Comment*> parameterCommentsList;
57
QValueList<Preamble*> preambleList;
58
QValueList<Macro*> macroList;
59
QValueList<Entry*> crossRefingEntryList;
60
QValueList<Element*> remainingList;
62
for ( File::ElementList::const_iterator it = bibtexfile->elements.begin(); it != bibtexfile->elements.end() && result && !cancelFlag; it++ )
64
Preamble *preamble = dynamic_cast<Preamble*>( *it );
65
if ( preamble != NULL )
66
preambleList.append( preamble );
69
Macro *macro = dynamic_cast<Macro*>( *it );
71
macroList.append( macro );
74
Entry *entry = dynamic_cast<Entry*>( *it );
75
if (( entry != NULL ) && ( entry->getField( EntryField::ftCrossRef ) != NULL ) )
76
crossRefingEntryList.append( entry );
79
Comment *comment = dynamic_cast<Comment*>( *it );
80
QString commentText = QString::null;
81
/** check if this file requests a special encoding */
82
if ( comment != NULL && comment->useCommand() && (( commentText = comment->text().lower() ) ).startsWith( "x-kbibtex-encoding=" ) )
84
m_encoding = commentText.mid( 19 );
85
qDebug( "Switching encoding to <%s>", m_encoding.latin1() );
86
parameterCommentsList.append( comment );
89
remainingList.append( *it );
95
int totalElements = ( int ) bibtexfile->count();
98
const char *encodingTo = m_encoding == "latex" ? "utf-8\0" : m_encoding.append( "\0" ).ascii();
99
m_iconvHandle = iconv_open( encodingTo, "utf-8" );
101
/** before anything else, write parameter comments */
102
for ( QValueList<Comment*>::iterator it = parameterCommentsList.begin(); it != parameterCommentsList.end() && result && !cancelFlag; it++ )
104
result &= writeComment( *iodevice, *it );
105
emit progress( ++currentPos, totalElements );
108
/** first, write preambles and strings (macros) at the beginning */
109
for ( QValueList<Preamble*>::iterator it = preambleList.begin(); it != preambleList.end() && result && !cancelFlag; it++ )
111
result &= writePreamble( *iodevice, *it );
112
emit progress( ++currentPos, totalElements );
115
for ( QValueList<Macro*>::iterator it = macroList.begin(); it != macroList.end() && result && !cancelFlag; it++ )
117
result &= writeMacro( *iodevice, *it );
118
emit progress( ++currentPos, totalElements );
121
/** second, write cross-referencing elements */
122
for ( QValueList<Entry*>::iterator it = crossRefingEntryList.begin(); it != crossRefingEntryList.end() && result && !cancelFlag; it++ )
124
result &= writeEntry( *iodevice, *it );
125
emit progress( ++currentPos, totalElements );
128
/** third, write remaining elements */
129
for ( QValueList<Element*>::iterator it = remainingList.begin(); it != remainingList.end() && result && !cancelFlag; it++ )
131
Entry *entry = dynamic_cast<Entry*>( *it );
133
result &= writeEntry( *iodevice, entry );
136
Comment *comment = dynamic_cast<Comment*>( *it );
137
if ( comment != NULL )
138
result &= writeComment( *iodevice, comment );
140
emit progress( ++currentPos, totalElements );
143
iconv_close( m_iconvHandle );
145
return result && !cancelFlag;
148
bool FileExporterBibTeX::save( QIODevice* iodevice, const Element* element, QStringList * /*errorLog*/ )
153
const char *encodingTo = m_encoding == "latex" ? "utf-8\0" : m_encoding.append( "\0" ).ascii();
154
m_iconvHandle = iconv_open( encodingTo, "utf-8" );
156
const Entry *entry = dynamic_cast<const Entry*>( element );
158
result |= writeEntry( *iodevice, entry );
161
const Macro * macro = dynamic_cast<const Macro*>( element );
163
result |= writeMacro( *iodevice, macro );
166
const Comment * comment = dynamic_cast<const Comment*>( element );
167
if ( comment != NULL )
168
result |= writeComment( *iodevice, comment );
171
const Preamble * preamble = dynamic_cast<const Preamble*>( element );
172
if ( preamble != NULL )
173
result |= writePreamble( *iodevice, preamble );
178
iconv_close( m_iconvHandle );
180
return result && !cancelFlag;
183
void FileExporterBibTeX::cancel()
188
bool FileExporterBibTeX::writeEntry( QIODevice &device, const Entry* entry )
190
writeString( device, QString( "@%1{ %2" ).arg( applyKeywordCasing( entry->entryTypeString() ) ).arg( entry->id() ) );
192
for ( Entry::EntryFields::ConstIterator it = entry->begin(); it != entry->end(); ++it )
194
EntryField *field = *it;
195
QString text = valueToString( field->value(), field->fieldType(), field->fieldTypeName() );
196
if ( m_protectCasing && dynamic_cast<BibTeX::PlainText*>( field->value()->items.first() ) != NULL && ( field->fieldType() == EntryField::ftTitle || field->fieldType() == EntryField::ftBookTitle || field->fieldType() == EntryField::ftSeries ) )
197
addProtectiveCasing( text );
198
writeString( device, QString( ",\n\t%1 = %2" ).arg( field->fieldTypeName() ).arg( text ) );
200
writeString( device, "\n}\n\n" );
204
bool FileExporterBibTeX::writeMacro( QIODevice &device, const Macro *macro )
206
QString text = valueToString( macro->value() );
207
if ( m_protectCasing )
208
addProtectiveCasing( text );
210
writeString( device, QString( "@%1{ %2 = %3 }\n\n" ).arg( applyKeywordCasing( "String" ) ).arg( macro->key() ).arg( text ) );
215
bool FileExporterBibTeX::writeComment( QIODevice &device, const Comment *comment )
217
if ( !comment->useCommand() )
219
QString text = comment->text() ;
221
if ( m_encoding == "latex" )
222
text = EncoderLaTeX::currentEncoderLaTeX() ->encode( text );
224
QStringList commentLines = QStringList::split( '\n', text );
225
for ( QStringList::Iterator it = commentLines.begin(); it != commentLines.end(); it++ )
227
writeString( device, ( *it ).append( "\n" ) );
229
writeString( device, "\n" );
233
QString text = comment->text() ;
235
if ( m_encoding == "latex" )
236
text = EncoderLaTeX::currentEncoderLaTeX() ->encode( text );
238
writeString( device, QString( "@%1{%2}\n\n" ).arg( applyKeywordCasing( "Comment" ) ).arg( text ) );
243
bool FileExporterBibTeX::writePreamble( QIODevice &device, const Preamble* preamble )
245
writeString( device, QString( "@%1{%2}\n\n" ).arg( applyKeywordCasing( "Preamble" ) ).arg( valueToString( preamble->value() ) ) );
250
bool FileExporterBibTeX::writeString( QIODevice &device, const QString& text )
252
size_t utf8datasize = 1;
253
QCString utf8 = text.utf8();
254
char *utf8data = utf8.data();
255
utf8datasize = utf8.length();
256
char *outputdata = m_iconvBuffer;
257
size_t outputdatasize = m_iconvBufferSize;
259
size_t result = iconv( m_iconvHandle, &utf8data, &utf8datasize, &outputdata, &outputdatasize );
262
qWarning( "Cannot convert string using iconv" );
266
if ( device.writeBlock( m_iconvBuffer, m_iconvBufferSize - outputdatasize ) != ( int )( m_iconvBufferSize - outputdatasize ) )
268
qWarning( "Cannot write string to device" );
275
void FileExporterBibTeX::setStringDelimiter( const QChar& stringOpenDelimiter, const QChar& stringCloseDelimiter )
277
m_stringOpenDelimiter = stringOpenDelimiter;
278
m_stringCloseDelimiter = stringCloseDelimiter;
281
void FileExporterBibTeX::setKeywordCasing( const KeywordCasing keywordCasing )
283
m_keywordCasing = keywordCasing;
286
void FileExporterBibTeX::setEncoding( const QString& encoding )
288
m_encoding = encoding;
291
void FileExporterBibTeX::setEnclosingCurlyBrackets( bool protectCasing )
293
m_protectCasing = protectCasing;
296
QString FileExporterBibTeX::valueToString( const Value *value, const EntryField::FieldType fieldType, const QString &fieldTypeName )
303
EncoderLaTeX *encoder = EncoderLaTeX::currentEncoderLaTeX();
305
for ( QValueList<ValueItem*>::ConstIterator it = value->items.begin(); it != value->items.end(); ++it )
308
result.append( " # " );
312
MacroKey *macroKey = dynamic_cast<MacroKey*>( *it );
313
if ( macroKey != NULL )
314
result.append( macroKey->text() );
318
BibTeX::PersonContainer *personContainer = dynamic_cast<BibTeX::PersonContainer*>( *it );
319
BibTeX::PlainText *plainText = dynamic_cast<BibTeX::PlainText*>( *it );
320
BibTeX::KeywordContainer *keywordContainer = dynamic_cast<BibTeX::KeywordContainer*>( *it );
322
if ( plainText != NULL )
323
text = plainText->text();
324
else if ( keywordContainer != NULL )
327
for ( QValueList<Keyword*>::Iterator it = keywordContainer->keywords.begin(); it != keywordContainer->keywords.end(); ++it )
333
text.append(( *it )->text() );
336
else if ( personContainer != NULL )
339
for ( QValueList<Person*>::Iterator it = personContainer->persons.begin(); it != personContainer->persons.end(); ++it )
342
text.append( " and " );
346
QString v = ( *it )->firstName();
349
bool requiresQuoting = requiresPersonQuoting( v, FALSE );
350
if ( requiresQuoting ) text.append( "{" );
352
if ( requiresQuoting ) text.append( "}" );
356
v = ( *it )->lastName();
359
/** Multi-part surnames (such as "Garcia Marquez") have to be enquoted.
360
* However, "von"-Parts (as in "von Hofmannsthal") must _not_ be enquoted.
363
* -- Hartmann von der Tann
364
* -- Ronaldo de {Assis Moreira} ("Ronaldo de Assis Moreira" works as well)
365
* -- Ailton {Goncalves da Silva}
366
* -- Gloria von {Thurn und Taxis}
367
* Thus we split the von-Parts from the surname (= everything after the first upcase char).
368
* FIXME: Make the personContainer aware of von-Parts and jr-Parts, instead.
370
QStringList list = QStringList::split( " ", v );
372
for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it )
375
if ( str != "others" && str[0].category() == QChar::Letter_Lowercase )
383
if ( !von.isEmpty() )
386
v = v.right( v.length() - von.length() );
388
bool requiresQuoting = requiresPersonQuoting( v, TRUE );
389
if ( requiresQuoting ) text.append( "{" );
391
if ( requiresQuoting ) text.append( "}" );
396
if ( m_encoding == "latex" )
397
text = encoder->encodeSpecialized( text, fieldType );
399
if ( fieldType == EntryField::ftURL || fieldType == EntryField::ftDoi || ( fieldType == EntryField::ftUnknown && fieldTypeName.lower() == "slaccitation" ) )
400
removeBackslashQuoting( text );
402
/** if the text to save contains a quote char ("),
403
* force string delimiters to be curly brackets,
404
* as quote chars as string delimiters would result
407
QChar stringOpenDelimiter = m_stringOpenDelimiter;
408
QChar stringCloseDelimiter = m_stringCloseDelimiter;
409
if ( text.contains( '"' ) && ( m_stringOpenDelimiter == '"' || m_stringCloseDelimiter == '"' ) )
411
stringOpenDelimiter = '{';
412
stringCloseDelimiter = '}';
415
result.append( stringOpenDelimiter ).append( text ).append( stringCloseDelimiter );
422
void FileExporterBibTeX::removeBackslashQuoting( QString &text )
424
text.replace( "\\&", "&" ).replace( "\\#", "#" ).replace( "\\_", "_" ).replace( "\\%", "%" );
427
QString FileExporterBibTeX::applyKeywordCasing( const QString &keyword )
429
switch ( m_keywordCasing )
431
case kcLowerCase: return keyword.lower();
432
case kcInitialCapital: return keyword.at( 0 ) + keyword.lower().mid( 1 );
433
case kcCapital: return keyword.upper();
434
default: return keyword;
438
bool FileExporterBibTeX::requiresPersonQuoting( const QString &text, bool isLastName )
440
if ( isLastName && !text.contains( " " ) )
441
/** Last name contains NO spaces, no quoting necessary */
443
else if ( isLastName && text[0].category() == QChar::Letter_Lowercase )
444
/** Last name starts with lower case character (e.g. as in "van der Linden") */
446
else if ( !isLastName && !text.contains( " and " ) )
447
/** First name contains no " and " no quoting necessary */
449
else if ( text[0] != '{' || text[text.length()-1] != '}' )
450
/** 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 */
453
/** check for cases like "{..}..{..}", which must be surrounded with a protective quoting, too */
454
int bracketCounter = 0;
455
for ( int i = text.length() - 1; i >= 0; --i )
457
if ( text[i] == '{' )
459
else if ( text[i] == '}' )
461
if ( bracketCounter == 0 && i > 0 )
467
void FileExporterBibTeX::addProtectiveCasing( QString &text )
469
if (( text[0] != '"' || text[text.length()-1] != '"' ) && ( text[0] != '{' || text[text.length()-1] != '}' ) )
471
/** nothing to protect, as this is no text string */
475
bool addBrackets = TRUE;
477
if ( text[1] == '{' && text[text.length() - 2] == '}' )
481
for ( int i = text.length() - 2; !addBrackets && i >= 1; --i )
482
if ( text[i] == '{' )++count;
483
else if ( text[i] == '}' )--count;
484
else if ( count == 0 ) addBrackets = TRUE;
488
text.insert( 1, '{' ).insert( text.length(), '}' );