converter.cpp 17.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
/***************************************************************************
 *   Copyright (C) 2006 by Tobias Koenig <tokoe@kde.org>                   *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

Albert Astals Cid's avatar
Albert Astals Cid committed
10
11
#include "converter.h"

12
#include <QtCore/QQueue>
13
#include <QtCore/QUrl>
14
15
#include <QtGui/QTextCursor>
#include <QtGui/QTextDocument>
16
17
#include <QtGui/QTextFrame>
#include <QtGui/QTextList>
18
#include <QtGui/QTextTableCell>
19
20
#include <QtXml/QDomElement>
#include <QtXml/QDomText>
21
#include <QtXml/QXmlSimpleReader>
22

23
24
25
26
#include <core/action.h>
#include <core/annotations.h>
#include <core/document.h>
#include <core/utils.h>
27

Tobias Koenig's avatar
Tobias Koenig committed
28
29
#include <klocale.h>

30
31
32
33
34
35
#include "document.h"
#include "styleinformation.h"
#include "styleparser.h"

using namespace OOO;

36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Style
{
  public:
    Style( const QTextBlockFormat &blockFormat, const QTextCharFormat &textFormat );

    QTextBlockFormat blockFormat() const;
    QTextCharFormat textFormat() const;

  private:
    QTextBlockFormat mBlockFormat;
    QTextCharFormat mTextFormat;
};


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
Style::Style( const QTextBlockFormat &blockFormat, const QTextCharFormat &textFormat )
  : mBlockFormat( blockFormat ), mTextFormat( textFormat )
{
}

QTextBlockFormat Style::blockFormat() const
{
  return mBlockFormat;
}

QTextCharFormat Style::textFormat() const
{
  return mTextFormat;
}

65
66
Converter::Converter()
  : mTextDocument( 0 ), mCursor( 0 ),
67
    mStyleInformation( 0 )
68
69
70
71
72
73
74
{
}

Converter::~Converter()
{
}

75
QTextDocument* Converter::convert( const QString &fileName )
76
{
77
  Document oooDocument( fileName );
Tobias Koenig's avatar
Tobias Koenig committed
78
79
  if ( !oooDocument.open() ) {
    emit error( oooDocument.lastErrorString(), -1 );
80
    return 0;
Tobias Koenig's avatar
Tobias Koenig committed
81
  }
82

83
84
85
  mTextDocument = new QTextDocument;
  mCursor = new QTextCursor( mTextDocument );

86
87
88
89
90
91
  /**
   * Create the dom of the content
   */
  QXmlSimpleReader reader;

  QXmlInputSource source;
92
  source.setData( oooDocument.content() );
93
94
95

  QString errorMsg;
  QDomDocument document;
Tobias Koenig's avatar
Tobias Koenig committed
96
97
  if ( !document.setContent( &source, &reader, &errorMsg ) ) {
    emit error( i18n( "Invalid XML document: %1", errorMsg ), -1 );
98
    delete mCursor;
99
    return 0;
100
101
  }

102
103
  mStyleInformation = new StyleInformation();

104
105
106
107
  /**
   * Read the style properties, so the are available when
   * parsing the content.
   */
108
  StyleParser styleParser( &oooDocument, document, mStyleInformation );
Tobias Koenig's avatar
Tobias Koenig committed
109
110
  if ( !styleParser.parse() ) {
    emit error( i18n( "Unable to read style information" ), -1 );
111
    delete mCursor;
112
    return 0;
Tobias Koenig's avatar
Tobias Koenig committed
113
  }
114

115
116
117
  /**
   * Add all images of the document to resource framework
   */
118
  const QMap<QString, QByteArray> images = oooDocument.images();
119
120
121
122
123
124
125
  QMapIterator<QString, QByteArray> it( images );
  while ( it.hasNext() ) {
    it.next();

    mTextDocument->addResource( QTextDocument::ImageResource, QUrl( it.key() ), QImage::fromData( it.value() ) );
  }

126
127
128
  /**
   * Set the correct page size
   */
129
  const QString masterLayout = mStyleInformation->masterPageName();
130
  const PageFormatProperty property = mStyleInformation->pageProperty( masterLayout );
131
132
133
134
135
136
137
138
139
140

  int pageWidth = qRound(property.width() / 72.0 * Okular::Utils::dpiX());
  int pageHeight = qRound(property.height() / 72.0 * Okular::Utils::dpiY());

  if ( pageWidth == 0 )
      pageWidth = 600;
  if ( pageHeight == 0 )
      pageHeight = 800;

  mTextDocument->setPageSize( QSize( pageWidth, pageHeight ) );
141

142
143
144
145
146
147
  QTextFrameFormat frameFormat;
  frameFormat.setMargin( qRound( property.margin() ) );

  QTextFrame *rootFrame = mTextDocument->rootFrame();
  rootFrame->setFrameFormat( frameFormat );

148
149
150
  /**
   * Parse the content of the document
   */
151
152
153
154
155
  const QDomElement documentElement = document.documentElement();

  QDomElement element = documentElement.firstChildElement();
  while ( !element.isNull() ) {
    if ( element.tagName() == QLatin1String( "body" ) ) {
Tobias Koenig's avatar
Tobias Koenig committed
156
157
      if ( !convertBody( element ) ) {
        emit error( i18n( "Unable to convert document content" ), -1 );
158
        delete mCursor;
159
        return 0;
Tobias Koenig's avatar
Tobias Koenig committed
160
      }
161
162
163
164
165
    }

    element = element.nextSiblingElement();
  }

166
167
168
169
170
171
  MetaInformation::List metaInformation = mStyleInformation->metaInformation();
  for ( int i = 0; i < metaInformation.count(); ++i ) {
    emit addMetaData( metaInformation[ i ].key(),
                      metaInformation[ i ].value(),
                      metaInformation[ i ].title() );
  }
Tobias Koenig's avatar
Tobias Koenig committed
172

173
  delete mCursor;
174
175
  delete mStyleInformation;
  mStyleInformation = 0;
176

177
  return mTextDocument;
178
179
}

180
bool Converter::convertBody( const QDomElement &element )
181
{
182
183
184
185
186
187
188
189
190
191
192
  QDomElement child = element.firstChildElement();
  while ( !child.isNull() ) {
    if ( child.tagName() == QLatin1String( "text" ) ) {
      if ( !convertText( child ) )
        return false;
    }

    child = child.nextSiblingElement();
  }

  return true;
193
194
}

195
bool Converter::convertText( const QDomElement &element )
196
{
197
198
199
200
201
202
203
204
205
206
207
  QDomElement child = element.firstChildElement();
  while ( !child.isNull() ) {
    if ( child.tagName() == QLatin1String( "p" ) ) {
      mCursor->insertBlock();
      if ( !convertParagraph( mCursor, child ) )
        return false;
    } else if ( child.tagName() == QLatin1String( "h" ) ) {
      mCursor->insertBlock();
      if ( !convertHeader( mCursor, child ) )
        return false;
    } else if ( child.tagName() == QLatin1String( "list" ) ) {
208
      if ( !convertList( mCursor, child ) )
209
        return false;
210
211
212
    } else if ( child.tagName() == QLatin1String( "table" ) ) {
      if ( !convertTable( child ) )
        return false;
213
214
215
216
217
218
    }

    child = child.nextSiblingElement();
  }

  return true;
219
220
}

221
bool Converter::convertHeader( QTextCursor *cursor, const QDomElement &element )
222
{
223
224
225
226
227
  const QString styleName = element.attribute( "style-name" );
  const StyleFormatProperty property = mStyleInformation->styleProperty( styleName );

  QTextBlockFormat blockFormat;
  QTextCharFormat textFormat;
228
229
  property.applyBlock( &blockFormat );
  property.applyText( &textFormat );
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248

  cursor->setBlockFormat( blockFormat );

  QDomNode child = element.firstChild();
  while ( !child.isNull() ) {
    if ( child.isElement() ) {
      const QDomElement childElement = child.toElement();
      if ( childElement.tagName() == QLatin1String( "span" ) ) {
        if ( !convertSpan( cursor, childElement, textFormat ) )
          return false;
      }
    } else if ( child.isText() ) {
      const QDomText childText = child.toText();
      if ( !convertTextNode( cursor, childText, textFormat ) )
        return false;
    }

    child = child.nextSibling();
  }
249

250
  emit addTitle( element.attribute( "outline-level", 0 ).toInt(), element.text(), cursor->block() );
251

252
253
254
  return true;
}

255
bool Converter::convertParagraph( QTextCursor *cursor, const QDomElement &element, const QTextBlockFormat &parentFormat, bool merge )
256
{
257
258
259
  const QString styleName = element.attribute( "style-name" );
  const StyleFormatProperty property = mStyleInformation->styleProperty( styleName );

260
  QTextBlockFormat blockFormat( parentFormat );
261
  QTextCharFormat textFormat;
262
263
  property.applyBlock( &blockFormat );
  property.applyText( &textFormat );
264

265
266
267
268
  if ( merge )
    cursor->mergeBlockFormat( blockFormat );
  else
    cursor->setBlockFormat( blockFormat );
269
270
271
272
273
274
275
276

  QDomNode child = element.firstChild();
  while ( !child.isNull() ) {
    if ( child.isElement() ) {
      const QDomElement childElement = child.toElement();
      if ( childElement.tagName() == QLatin1String( "span" ) ) {
        if ( !convertSpan( cursor, childElement, textFormat ) )
          return false;
277
278
279
280
281
282
283
284
285
      } else if ( childElement.tagName() == QLatin1String( "tab" ) ) {
        mCursor->insertText( "    " );
      } else if ( childElement.tagName() == QLatin1String( "s" ) ) {
        QString spaces;
        spaces.fill( ' ', childElement.attribute( "c" ).toInt() );
        mCursor->insertText( spaces );
      } else if ( childElement.tagName() == QLatin1String( "frame" ) ) {
        if ( !convertFrame( childElement ) )
          return false;
286
287
288
      } else if ( childElement.tagName() == QLatin1String( "a" ) ) {
        if ( !convertLink( cursor, childElement, textFormat ) )
          return false;
Tobias Koenig's avatar
Tobias Koenig committed
289
290
291
      } else if ( childElement.tagName() == QLatin1String( "annotation" ) ) {
        if ( !convertAnnotation( cursor, childElement ) )
          return false;
292
293
294
295
296
297
298
299
300
      }
    } else if ( child.isText() ) {
      const QDomText childText = child.toText();
      if ( !convertTextNode( cursor, childText, textFormat ) )
        return false;
    }

    child = child.nextSibling();
  }
301

302
303
  return true;
}
304

305
306
307
bool Converter::convertTextNode( QTextCursor *cursor, const QDomText &element, const QTextCharFormat &format )
{
  cursor->insertText( element.data(), format );
308

309
310
311
312
313
314
315
316
  return true;
}

bool Converter::convertSpan( QTextCursor *cursor, const QDomElement &element, const QTextCharFormat &format )
{
  const QString styleName = element.attribute( "style-name" );
  const StyleFormatProperty property = mStyleInformation->styleProperty( styleName );

317
318
  QTextCharFormat textFormat( format );
  property.applyText( &textFormat );
319
320
321
322
323
324
325
326
327
328

  QDomNode child = element.firstChild();
  while ( !child.isNull() ) {
    if ( child.isText() ) {
      const QDomText childText = child.toText();
      if ( !convertTextNode( cursor, childText, textFormat ) )
        return false;
    }

    child = child.nextSibling();
329
330
  }

331
332
  return true;
}
333

334
bool Converter::convertList( QTextCursor *cursor, const QDomElement &element )
335
336
337
{
  const QString styleName = element.attribute( "style-name" );
  const ListFormatProperty property = mStyleInformation->listProperty( styleName );
338

339
  QTextListFormat format;
340

341
342
  if ( cursor->currentList() ) { // we are in a nested list
    format = cursor->currentList()->format();
343
344
345
    format.setIndent( format.indent() + 1 );
  }

346
  property.apply( &format, 0 );
347

348
  QTextList *list = cursor->insertList( format );
349

350
  QDomElement itemChild = element.firstChildElement();
351
  int loop = 0;
352
353
  while ( !itemChild.isNull() ) {
    if ( itemChild.tagName() == QLatin1String( "list-item" ) ) {
354
      loop++;
355

356
357
      QDomElement childElement = itemChild.firstChildElement();
      while ( !childElement.isNull() ) {
358

359
        QTextBlock prevBlock;
360

361
362
        if ( childElement.tagName() == QLatin1String( "p" ) ) {
          if ( loop > 1 )
363
            cursor->insertBlock();
364

365
          prevBlock = cursor->block();
366

367
          if ( !convertParagraph( cursor, childElement, QTextBlockFormat(), true ) )
368
369
370
            return false;

        } else if ( childElement.tagName() == QLatin1String( "list" ) ) {
371
          prevBlock = cursor->block();
372

373
          if ( !convertList( cursor, childElement ) )
374
375
376
            return false;
        }

377
378
        if( prevBlock.isValid() )
            list->add( prevBlock );
379
380
381

        childElement = childElement.nextSiblingElement();
      }
382
383
    }

384
    itemChild = itemChild.nextSiblingElement();
385
386
387
388
389
  }

  return true;
}

390
391
392
393
394
395
396
static void enqueueNodeList( QQueue<QDomNode> &queue, const QDomNodeList &list )
{
  for ( int i = 0; i < list.count(); ++i ) {
    queue.enqueue( list.at( i ) );
  }
}

397
bool Converter::convertTable( const QDomElement &element )
398
{
399
400
401
402
403
  /**
   * Find out dimension of the table
   */
  int rowCounter = 0;
  int columnCounter = 0;
404

405
406
407
408
409
410
411
412
  QQueue<QDomNode> nodeQueue;
  enqueueNodeList( nodeQueue, element.childNodes() );
  while ( !nodeQueue.isEmpty() ) {
    QDomElement el = nodeQueue.dequeue().toElement();
    if ( el.isNull() )
      continue;

    if ( el.tagName() == QLatin1String( "table-row" ) ) {
413
414
415
      rowCounter++;

      int counter = 0;
416
      QDomElement columnElement = el.firstChildElement();
417
418
419
420
421
422
423
424
      while ( !columnElement.isNull() ) {
        if ( columnElement.tagName() == QLatin1String( "table-cell" ) ) {
          counter++;
        }
        columnElement = columnElement.nextSiblingElement();
      }

      columnCounter = qMax( columnCounter, counter );
425
426
    } else if ( el.tagName() == QLatin1String( "table-header-rows" ) ) {
      enqueueNodeList( nodeQueue, el.childNodes() );
427
428
429
430
431
432
433
    }
  }

  /**
   * Create table
   */
  QTextTable *table = mCursor->insertTable( rowCounter, columnCounter );
434
  mCursor->movePosition( QTextCursor::End );
435
436
437
438

  /**
   * Fill table
   */
439
440
  nodeQueue.clear();
  enqueueNodeList( nodeQueue, element.childNodes() );
441

442
443
  QTextTableFormat tableFormat;

444
  rowCounter = 0;
445
446
447
448
449
450
  while ( !nodeQueue.isEmpty() ) {
    QDomElement el = nodeQueue.dequeue().toElement();
    if ( el.isNull() )
      continue;

    if ( el.tagName() == QLatin1String( "table-row" ) ) {
451
452

      int columnCounter = 0;
453
      QDomElement columnElement = el.firstChildElement();
454
455
      while ( !columnElement.isNull() ) {
        if ( columnElement.tagName() == QLatin1String( "table-cell" ) ) {
456
457
458
459
          const StyleFormatProperty property = mStyleInformation->styleProperty( columnElement.attribute( "style-name" ) );

          QTextBlockFormat format;
          property.applyTableCell( &format );
460
461
462
463
464

          QDomElement paragraphElement = columnElement.firstChildElement();
          while ( !paragraphElement.isNull() ) {
            if ( paragraphElement.tagName() == QLatin1String( "p" ) ) {
              QTextTableCell cell = table->cellAt( rowCounter, columnCounter );
465
466
467
468
469
470
471
472
473
474
              // Insert a frame into the cell and work on that, so we can handle
              // different parts of the cell having different block formatting
              QTextCursor cellCursor = cell.lastCursorPosition();
              QTextFrameFormat frameFormat;
              frameFormat.setMargin( 1 ); // TODO: this shouldn't be hard coded
              QTextFrame *frame = cellCursor.insertFrame( frameFormat );
              QTextCursor frameCursor = frame->firstCursorPosition();
              frameCursor.setBlockFormat( format );

              if ( !convertParagraph( &frameCursor, paragraphElement, format ) )
475
                return false;
476
477
478
479
480
481
482
            } else if ( paragraphElement.tagName() == QLatin1String( "list" ) ) {
              QTextTableCell cell = table->cellAt( rowCounter, columnCounter );
              // insert a list into the cell
              QTextCursor cellCursor = cell.lastCursorPosition();
              if ( !convertList( &cellCursor, paragraphElement ) ) {
                return false;
              }
483
484
485
486
487
488
489
490
491
492
            }

            paragraphElement = paragraphElement.nextSiblingElement();
          }
          columnCounter++;
        }
        columnElement = columnElement.nextSiblingElement();
      }

      rowCounter++;
493
494
    } else if ( el.tagName() == QLatin1String( "table-column" ) ) {
      const StyleFormatProperty property = mStyleInformation->styleProperty( el.attribute( "style-name" ) );
495
496
497
498
499
      const QString tableColumnNumColumnsRepeated = el.attribute( "number-columns-repeated", "1" );
      int numColumnsToApplyTo = tableColumnNumColumnsRepeated.toInt();
      for (int i = 0; i < numColumnsToApplyTo; ++i) {
        property.applyTableColumn( &tableFormat );
      }
500
    }
501
502
  }

503
504
  table->setFormat( tableFormat );

505
506
  return true;
}
507

508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
bool Converter::convertFrame( const QDomElement &element )
{
  QDomElement child = element.firstChildElement();
  while ( !child.isNull() ) {
    if ( child.tagName() == QLatin1String( "image" ) ) {
      const QString href = child.attribute( "href" );
      QTextImageFormat format;
      format.setWidth( StyleParser::convertUnit( element.attribute( "width" ) ) );
      format.setHeight( StyleParser::convertUnit( element.attribute( "height" ) ) );
      format.setName( href );

      mCursor->insertImage( format );
    }

    child = child.nextSiblingElement();
  }

  return true;
}
527

528
529
bool Converter::convertLink( QTextCursor *cursor, const QDomElement &element, const QTextCharFormat &format )
{
530
  int startPosition = cursor->position();
531

532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
  QDomNode child = element.firstChild();
  while ( !child.isNull() ) {
    if ( child.isElement() ) {
      const QDomElement childElement = child.toElement();
      if ( childElement.tagName() == QLatin1String( "span" ) ) {
        if ( !convertSpan( cursor, childElement, format ) )
          return false;
      }
    } else if ( child.isText() ) {
      const QDomText childText = child.toText();
      if ( !convertTextNode( cursor, childText, format ) )
        return false;
    }

    child = child.nextSibling();
  }

549
  int endPosition = cursor->position();
550

551
  Okular::Action *action = new Okular::BrowseAction( element.attribute( "href" ) );
Pino Toscano's avatar
Pino Toscano committed
552
  emit addAction( action, startPosition, endPosition );
553
554
555
556

  return true;
}

Tobias Koenig's avatar
Tobias Koenig committed
557
558
559
bool Converter::convertAnnotation( QTextCursor *cursor, const QDomElement &element )
{
  QStringList contents;
560
561
  QString creator;
  QDateTime dateTime;
Tobias Koenig's avatar
Tobias Koenig committed
562

563
  int position = cursor->position();
Tobias Koenig's avatar
Tobias Koenig committed
564
565
566
567

  QDomElement child = element.firstChildElement();
  while ( !child.isNull() ) {
    if ( child.tagName() == QLatin1String( "creator" ) ) {
568
      creator = child.text();
Tobias Koenig's avatar
Tobias Koenig committed
569
    } else if ( child.tagName() == QLatin1String( "date" ) ) {
570
      dateTime = QDateTime::fromString( child.text(), Qt::ISODate );
Tobias Koenig's avatar
Tobias Koenig committed
571
572
573
574
575
576
577
    } else if ( child.tagName() == QLatin1String( "p" ) ) {
        contents.append( child.text() );
    }

    child = child.nextSiblingElement();
  }

578
579
580
581
582
583
  Okular::TextAnnotation *annotation = new Okular::TextAnnotation;
  annotation->setAuthor( creator );
  annotation->setContents( contents.join( "\n" ) );
  annotation->setCreationDate( dateTime );
  annotation->style().setColor( QColor( "#ffff00" ) );
  annotation->style().setOpacity( 0.5 );
Tobias Koenig's avatar
Tobias Koenig committed
584

585
  emit addAnnotation( annotation, position, position + 3 );
Tobias Koenig's avatar
Tobias Koenig committed
586
587
588

  return true;
}