~ubuntu-branches/ubuntu/raring/kbibtex/raring

« back to all changes in this revision

Viewing changes to src/fileexporterbibtex.cpp

  • Committer: Package Import Robot
  • Author(s): Michael Hanke
  • Date: 2011-07-18 09:29:48 UTC
  • mfrom: (1.1.6) (2.1.5 sid)
  • Revision ID: package-import@ubuntu.com-20110718092948-ksxjmg7kdfamolmg
Tags: 0.3-1
* First upstream release for KDE4 (Closes: #634255). A number of search
  engines are still missing, in comparison to the 0.2 series.
* Bumped Standards-Version to 3.9.2, no changes necessary.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/***************************************************************************
2
 
*   Copyright (C) 2004-2009 by Thomas Fischer                             *
3
 
*   fischer@unix-ag.uni-kl.de                                             *
4
 
*                                                                         *
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.                                   *
9
 
*                                                                         *
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.                          *
14
 
*                                                                         *
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
 
***************************************************************************/
20
 
#include <file.h>
21
 
#include <element.h>
22
 
#include <entry.h>
23
 
#include <macro.h>
24
 
#include <preamble.h>
25
 
#include <value.h>
26
 
#include <comment.h>
27
 
#include <encoderlatex.h>
28
 
 
29
 
#include "fileexporterbibtex.h"
30
 
 
31
 
namespace BibTeX
32
 
{
33
 
 
34
 
    FileExporterBibTeX::FileExporterBibTeX() : FileExporter(),
35
 
            m_iconvBufferSize( 16384 ), m_stringOpenDelimiter( '"' ), m_stringCloseDelimiter( '"' ), m_keywordCasing( kcCamelCase ), m_encoding( "latex" ), m_protectCasing( FALSE ), cancelFlag( FALSE )
36
 
    {
37
 
        m_iconvBuffer = new char[m_iconvBufferSize];
38
 
    }
39
 
 
40
 
    FileExporterBibTeX::~FileExporterBibTeX()
41
 
    {
42
 
        delete[] m_iconvBuffer;
43
 
    }
44
 
 
45
 
    bool FileExporterBibTeX::save( QIODevice* iodevice, const File* bibtexfile, QStringList * /*errorLog*/ )
46
 
    {
47
 
        m_mutex.lock();
48
 
        bool result = TRUE;
49
 
 
50
 
        /**
51
 
          * Categorize elements from the bib file into four groups,
52
 
          * to ensure that BibTeX finds all connected elements
53
 
          * in the correct order.
54
 
          */
55
 
 
56
 
        QValueList<Comment*> parameterCommentsList;
57
 
        QValueList<Preamble*> preambleList;
58
 
        QValueList<Macro*> macroList;
59
 
        QValueList<Entry*> crossRefingEntryList;
60
 
        QValueList<Element*> remainingList;
61
 
 
62
 
        for ( File::ElementList::const_iterator it = bibtexfile->elements.begin(); it != bibtexfile->elements.end() && result && !cancelFlag; it++ )
63
 
        {
64
 
            Preamble *preamble = dynamic_cast<Preamble*>( *it );
65
 
            if ( preamble != NULL )
66
 
                preambleList.append( preamble );
67
 
            else
68
 
            {
69
 
                Macro *macro = dynamic_cast<Macro*>( *it );
70
 
                if ( macro != NULL )
71
 
                    macroList.append( macro );
72
 
                else
73
 
                {
74
 
                    Entry *entry = dynamic_cast<Entry*>( *it );
75
 
                    if (( entry != NULL ) && ( entry->getField( EntryField::ftCrossRef ) != NULL ) )
76
 
                        crossRefingEntryList.append( entry );
77
 
                    else
78
 
                    {
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=" ) )
83
 
                        {
84
 
                            m_encoding = commentText.mid( 19 );
85
 
                            qDebug( "Switching encoding to <%s>", m_encoding.latin1() );
86
 
                            parameterCommentsList.append( comment );
87
 
                        }
88
 
                        else
89
 
                            remainingList.append( *it );
90
 
                    }
91
 
                }
92
 
            }
93
 
        }
94
 
 
95
 
        int totalElements = ( int ) bibtexfile->count();
96
 
        int currentPos = 0;
97
 
 
98
 
        const char *encodingTo = m_encoding == "latex" ? "utf-8\0" : m_encoding.append( "\0" ).ascii();
99
 
        m_iconvHandle = iconv_open( encodingTo, "utf-8" );
100
 
 
101
 
        /** before anything else, write parameter comments */
102
 
        for ( QValueList<Comment*>::iterator it = parameterCommentsList.begin(); it != parameterCommentsList.end() && result && !cancelFlag; it++ )
103
 
        {
104
 
            result &= writeComment( *iodevice, *it );
105
 
            emit progress( ++currentPos, totalElements );
106
 
        }
107
 
 
108
 
        /** first, write preambles and strings (macros) at the beginning */
109
 
        for ( QValueList<Preamble*>::iterator it = preambleList.begin(); it != preambleList.end() && result && !cancelFlag; it++ )
110
 
        {
111
 
            result &= writePreamble( *iodevice, *it );
112
 
            emit progress( ++currentPos, totalElements );
113
 
        }
114
 
 
115
 
        for ( QValueList<Macro*>::iterator it = macroList.begin(); it != macroList.end() && result && !cancelFlag; it++ )
116
 
        {
117
 
            result &= writeMacro( *iodevice, *it );
118
 
            emit progress( ++currentPos, totalElements );
119
 
        }
120
 
 
121
 
        /** second, write cross-referencing elements */
122
 
        for ( QValueList<Entry*>::iterator it = crossRefingEntryList.begin(); it != crossRefingEntryList.end() && result && !cancelFlag; it++ )
123
 
        {
124
 
            result &= writeEntry( *iodevice, *it );
125
 
            emit progress( ++currentPos, totalElements );
126
 
        }
127
 
 
128
 
        /** third, write remaining elements */
129
 
        for ( QValueList<Element*>::iterator it = remainingList.begin(); it != remainingList.end() && result && !cancelFlag; it++ )
130
 
        {
131
 
            Entry *entry = dynamic_cast<Entry*>( *it );
132
 
            if ( entry != NULL )
133
 
                result &= writeEntry( *iodevice, entry );
134
 
            else
135
 
            {
136
 
                Comment *comment = dynamic_cast<Comment*>( *it );
137
 
                if ( comment != NULL )
138
 
                    result &= writeComment( *iodevice, comment );
139
 
            }
140
 
            emit progress( ++currentPos, totalElements );
141
 
        }
142
 
 
143
 
        iconv_close( m_iconvHandle );
144
 
        m_mutex.unlock();
145
 
        return result && !cancelFlag;
146
 
    }
147
 
 
148
 
    bool FileExporterBibTeX::save( QIODevice* iodevice, const Element* element, QStringList * /*errorLog*/ )
149
 
    {
150
 
        m_mutex.lock();
151
 
        bool result = FALSE;
152
 
 
153
 
        const char *encodingTo = m_encoding == "latex" ? "utf-8\0" : m_encoding.append( "\0" ).ascii();
154
 
        m_iconvHandle = iconv_open( encodingTo, "utf-8" );
155
 
 
156
 
        const Entry *entry = dynamic_cast<const Entry*>( element );
157
 
        if ( entry != NULL )
158
 
            result |= writeEntry( *iodevice, entry );
159
 
        else
160
 
        {
161
 
            const Macro * macro = dynamic_cast<const Macro*>( element );
162
 
            if ( macro != NULL )
163
 
                result |= writeMacro( *iodevice, macro );
164
 
            else
165
 
            {
166
 
                const Comment * comment = dynamic_cast<const Comment*>( element );
167
 
                if ( comment != NULL )
168
 
                    result |= writeComment( *iodevice, comment );
169
 
                else
170
 
                {
171
 
                    const Preamble * preamble = dynamic_cast<const Preamble*>( element );
172
 
                    if ( preamble != NULL )
173
 
                        result |= writePreamble( *iodevice, preamble );
174
 
                }
175
 
            }
176
 
        }
177
 
 
178
 
        iconv_close( m_iconvHandle );
179
 
        m_mutex.unlock();
180
 
        return result && !cancelFlag;
181
 
    }
182
 
 
183
 
    void FileExporterBibTeX::cancel()
184
 
    {
185
 
        cancelFlag = TRUE;
186
 
    }
187
 
 
188
 
    bool FileExporterBibTeX::writeEntry( QIODevice &device, const Entry* entry )
189
 
    {
190
 
        writeString( device, QString( "@%1{ %2" ).arg( applyKeywordCasing( entry->entryTypeString() ) ).arg( entry->id() ) );
191
 
 
192
 
        for ( Entry::EntryFields::ConstIterator it = entry->begin(); it != entry->end(); ++it )
193
 
        {
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 ) );
199
 
        }
200
 
        writeString( device, "\n}\n\n" );
201
 
        return TRUE;
202
 
    }
203
 
 
204
 
    bool FileExporterBibTeX::writeMacro( QIODevice &device, const Macro *macro )
205
 
    {
206
 
        QString text = valueToString( macro->value() );
207
 
        if ( m_protectCasing )
208
 
            addProtectiveCasing( text );
209
 
 
210
 
        writeString( device, QString( "@%1{ %2 = %3 }\n\n" ).arg( applyKeywordCasing( "String" ) ).arg( macro->key() ).arg( text ) );
211
 
 
212
 
        return TRUE;
213
 
    }
214
 
 
215
 
    bool FileExporterBibTeX::writeComment( QIODevice &device, const Comment *comment )
216
 
    {
217
 
        if ( !comment->useCommand() )
218
 
        {
219
 
            QString text = comment->text() ;
220
 
 
221
 
            if ( m_encoding == "latex" )
222
 
                text = EncoderLaTeX::currentEncoderLaTeX() ->encode( text );
223
 
 
224
 
            QStringList commentLines = QStringList::split( '\n', text );
225
 
            for ( QStringList::Iterator it = commentLines.begin(); it != commentLines.end(); it++ )
226
 
            {
227
 
                writeString( device, ( *it ).append( "\n" ) );
228
 
            }
229
 
            writeString( device, "\n" );
230
 
        }
231
 
        else
232
 
        {
233
 
            QString text = comment->text() ;
234
 
 
235
 
            if ( m_encoding == "latex" )
236
 
                text = EncoderLaTeX::currentEncoderLaTeX() ->encode( text );
237
 
 
238
 
            writeString( device, QString( "@%1{%2}\n\n" ).arg( applyKeywordCasing( "Comment" ) ).arg( text ) );
239
 
        }
240
 
        return TRUE;
241
 
    }
242
 
 
243
 
    bool FileExporterBibTeX::writePreamble( QIODevice &device, const Preamble* preamble )
244
 
    {
245
 
        writeString( device, QString( "@%1{%2}\n\n" ).arg( applyKeywordCasing( "Preamble" ) ).arg( valueToString( preamble->value() ) ) );
246
 
 
247
 
        return TRUE;
248
 
    }
249
 
 
250
 
    bool FileExporterBibTeX::writeString( QIODevice &device, const QString& text )
251
 
    {
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;
258
 
 
259
 
        size_t result = iconv( m_iconvHandle, &utf8data, &utf8datasize, &outputdata, &outputdatasize );
260
 
        if ( result != 0 )
261
 
        {
262
 
            qWarning( "Cannot convert string using iconv" );
263
 
            return false;
264
 
        }
265
 
 
266
 
        if ( device.writeBlock( m_iconvBuffer, m_iconvBufferSize - outputdatasize ) != ( int )( m_iconvBufferSize - outputdatasize ) )
267
 
        {
268
 
            qWarning( "Cannot write string to device" );
269
 
            return false;
270
 
        }
271
 
 
272
 
        return true;
273
 
    }
274
 
 
275
 
    void FileExporterBibTeX::setStringDelimiter( const QChar& stringOpenDelimiter, const QChar& stringCloseDelimiter )
276
 
    {
277
 
        m_stringOpenDelimiter = stringOpenDelimiter;
278
 
        m_stringCloseDelimiter = stringCloseDelimiter;
279
 
    }
280
 
 
281
 
    void FileExporterBibTeX::setKeywordCasing( const KeywordCasing keywordCasing )
282
 
    {
283
 
        m_keywordCasing = keywordCasing;
284
 
    }
285
 
 
286
 
    void FileExporterBibTeX::setEncoding( const QString& encoding )
287
 
    {
288
 
        m_encoding = encoding;
289
 
    }
290
 
 
291
 
    void FileExporterBibTeX::setEnclosingCurlyBrackets( bool protectCasing )
292
 
    {
293
 
        m_protectCasing = protectCasing;
294
 
    }
295
 
 
296
 
    QString FileExporterBibTeX::valueToString( const Value *value, const EntryField::FieldType fieldType, const QString &fieldTypeName )
297
 
    {
298
 
        if ( value == NULL )
299
 
            return "";
300
 
 
301
 
        QString result;
302
 
        bool isFirst = TRUE;
303
 
        EncoderLaTeX *encoder = EncoderLaTeX::currentEncoderLaTeX();
304
 
 
305
 
        for ( QValueList<ValueItem*>::ConstIterator it = value->items.begin(); it != value->items.end(); ++it )
306
 
        {
307
 
            if ( !isFirst )
308
 
                result.append( " # " );
309
 
            else
310
 
                isFirst = FALSE;
311
 
 
312
 
            MacroKey *macroKey = dynamic_cast<MacroKey*>( *it );
313
 
            if ( macroKey != NULL )
314
 
                result.append( macroKey->text() );
315
 
            else
316
 
            {
317
 
                QString 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 );
321
 
 
322
 
                if ( plainText != NULL )
323
 
                    text = plainText->text();
324
 
                else if ( keywordContainer != NULL )
325
 
                {
326
 
                    bool first = TRUE;
327
 
                    for ( QValueList<Keyword*>::Iterator it = keywordContainer->keywords.begin(); it != keywordContainer->keywords.end(); ++it )
328
 
                    {
329
 
                        if ( !first )
330
 
                            text.append( ", " );
331
 
                        else
332
 
                            first = FALSE;
333
 
                        text.append(( *it )->text() );
334
 
                    }
335
 
                }
336
 
                else if ( personContainer != NULL )
337
 
                {
338
 
                    bool first = TRUE;
339
 
                    for ( QValueList<Person*>::Iterator it = personContainer->persons.begin(); it != personContainer->persons.end(); ++it )
340
 
                    {
341
 
                        if ( !first )
342
 
                            text.append( " and " );
343
 
                        else
344
 
                            first = FALSE;
345
 
 
346
 
                        QString v = ( *it )->firstName();
347
 
                        if ( !v.isEmpty() )
348
 
                        {
349
 
                            bool requiresQuoting = requiresPersonQuoting( v, FALSE );
350
 
                            if ( requiresQuoting ) text.append( "{" );
351
 
                            text.append( v );
352
 
                            if ( requiresQuoting ) text.append( "}" );
353
 
                            text.append( " " );
354
 
                        }
355
 
 
356
 
                        v = ( *it )->lastName();
357
 
                        if ( !v.isEmpty() )
358
 
                        {
359
 
                            /** Multi-part surnames (such as "Garcia Marquez") have to be enquoted.
360
 
                              * However, "von"-Parts (as in "von Hofmannsthal") must _not_ be enquoted.
361
 
                              * Examples:
362
 
                              * -- Robson de Souza
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.
369
 
                              */
370
 
                            QStringList list = QStringList::split( " ", v );
371
 
                            QString von;
372
 
                            for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it )
373
 
                            {
374
 
                                QString str = *it;
375
 
                                if ( str != "others" && str[0].category() == QChar::Letter_Lowercase )
376
 
                                {
377
 
                                    von += *it;
378
 
                                    von += " ";
379
 
                                }
380
 
                                else
381
 
                                    break;
382
 
                            }
383
 
                            if ( !von.isEmpty() )
384
 
                            {
385
 
                                text.append( von );
386
 
                                v = v.right( v.length() - von.length() );
387
 
                            }
388
 
                            bool requiresQuoting = requiresPersonQuoting( v, TRUE );
389
 
                            if ( requiresQuoting ) text.append( "{" );
390
 
                            text.append( v );
391
 
                            if ( requiresQuoting ) text.append( "}" );
392
 
                        }
393
 
                    }
394
 
                }
395
 
 
396
 
                if ( m_encoding == "latex" )
397
 
                    text = encoder->encodeSpecialized( text, fieldType );
398
 
 
399
 
                if ( fieldType == EntryField::ftURL || fieldType == EntryField::ftDoi || ( fieldType == EntryField::ftUnknown && fieldTypeName.lower() == "slaccitation" ) )
400
 
                    removeBackslashQuoting( text );
401
 
 
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
405
 
                  * in parser failures
406
 
                  */
407
 
                QChar stringOpenDelimiter = m_stringOpenDelimiter;
408
 
                QChar stringCloseDelimiter = m_stringCloseDelimiter;
409
 
                if ( text.contains( '"' ) && ( m_stringOpenDelimiter == '"' || m_stringCloseDelimiter == '"' ) )
410
 
                {
411
 
                    stringOpenDelimiter = '{';
412
 
                    stringCloseDelimiter = '}';
413
 
                }
414
 
 
415
 
                result.append( stringOpenDelimiter ).append( text ).append( stringCloseDelimiter );
416
 
            }
417
 
        }
418
 
 
419
 
        return result;
420
 
    }
421
 
 
422
 
    void FileExporterBibTeX::removeBackslashQuoting( QString &text )
423
 
    {
424
 
        text.replace( "\\&", "&" ).replace( "\\#", "#" ).replace( "\\_", "_" ).replace( "\\%", "%" );
425
 
    }
426
 
 
427
 
    QString FileExporterBibTeX::applyKeywordCasing( const QString &keyword )
428
 
    {
429
 
        switch ( m_keywordCasing )
430
 
        {
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;
435
 
        }
436
 
    }
437
 
 
438
 
    bool FileExporterBibTeX::requiresPersonQuoting( const QString &text, bool isLastName )
439
 
    {
440
 
        if ( isLastName && !text.contains( " " ) )
441
 
            /** Last name contains NO spaces, no quoting necessary */
442
 
            return FALSE;
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") */
445
 
            return FALSE;
446
 
        else if ( !isLastName && !text.contains( " and " ) )
447
 
            /** First name contains no " and " no quoting necessary */
448
 
            return FALSE;
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 */
451
 
            return TRUE;
452
 
 
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 )
456
 
        {
457
 
            if ( text[i] == '{' )
458
 
                ++bracketCounter;
459
 
            else if ( text[i] == '}' )
460
 
                --bracketCounter;
461
 
            if ( bracketCounter == 0 && i > 0 )
462
 
                return TRUE;
463
 
        }
464
 
        return FALSE;
465
 
    }
466
 
 
467
 
    void FileExporterBibTeX::addProtectiveCasing( QString &text )
468
 
    {
469
 
        if (( text[0] != '"' || text[text.length()-1] != '"' ) && ( text[0] != '{' || text[text.length()-1] != '}' ) )
470
 
        {
471
 
            /** nothing to protect, as this is no text string */
472
 
            return;
473
 
        }
474
 
 
475
 
        bool addBrackets = TRUE;
476
 
 
477
 
        if ( text[1] == '{' && text[text.length() - 2] == '}' )
478
 
        {
479
 
            addBrackets = FALSE;
480
 
            int count = 0;
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;
485
 
        }
486
 
 
487
 
        if ( addBrackets )
488
 
            text.insert( 1, '{' ).insert( text.length(), '}' );
489
 
    }
490
 
 
491
 
}