/*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * 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. * ***************************************************************************/ #include "converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "document.h" #include "styleinformation.h" #include "styleparser.h" using namespace OOO; class Style { public: Style( const QTextBlockFormat &blockFormat, const QTextCharFormat &textFormat ); QTextBlockFormat blockFormat() const; QTextCharFormat textFormat() const; private: QTextBlockFormat mBlockFormat; QTextCharFormat mTextFormat; }; Style::Style( const QTextBlockFormat &blockFormat, const QTextCharFormat &textFormat ) : mBlockFormat( blockFormat ), mTextFormat( textFormat ) { } QTextBlockFormat Style::blockFormat() const { return mBlockFormat; } QTextCharFormat Style::textFormat() const { return mTextFormat; } Converter::Converter() : mTextDocument( 0 ), mCursor( 0 ), mStyleInformation( 0 ) { } Converter::~Converter() { } Okular::Document::OpenResult Converter::convertWithPassword( const QString &fileName, const QString &password ) { Document oooDocument( fileName ); if ( !oooDocument.open( password ) ) { if ( !oooDocument.anyFileEncrypted() ) emit error( oooDocument.lastErrorString(), -1 ); return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } mTextDocument = new QTextDocument; mCursor = new QTextCursor( mTextDocument ); /** * Create the dom of the content */ QXmlSimpleReader reader; QXmlInputSource source; source.setData( oooDocument.content() ); QString errorMsg; QDomDocument document; if ( !document.setContent( &source, &reader, &errorMsg ) ) { if ( !oooDocument.anyFileEncrypted() ) emit error( i18n( "Invalid XML document: %1", errorMsg ), -1 ); delete mCursor; return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } mStyleInformation = new StyleInformation(); /** * Read the style properties, so the are available when * parsing the content. */ StyleParser styleParser( &oooDocument, document, mStyleInformation ); if ( !styleParser.parse() ) { if ( !oooDocument.anyFileEncrypted() ) emit error( i18n( "Unable to read style information" ), -1 ); delete mCursor; return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } /** * Add all images of the document to resource framework */ const QMap images = oooDocument.images(); QMapIterator it( images ); while ( it.hasNext() ) { it.next(); mTextDocument->addResource( QTextDocument::ImageResource, QUrl( it.key() ), QImage::fromData( it.value() ) ); } /** * Set the correct page size */ const QString masterLayout = mStyleInformation->masterPageName(); const PageFormatProperty property = mStyleInformation->pageProperty( masterLayout ); 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 ) ); QTextFrameFormat frameFormat; frameFormat.setMargin( qRound( property.margin() ) ); QTextFrame *rootFrame = mTextDocument->rootFrame(); rootFrame->setFrameFormat( frameFormat ); /** * Parse the content of the document */ const QDomElement documentElement = document.documentElement(); QDomElement element = documentElement.firstChildElement(); while ( !element.isNull() ) { if ( element.tagName() == QLatin1String( "body" ) ) { if ( !convertBody( element ) ) { if ( !oooDocument.anyFileEncrypted() ) emit error( i18n( "Unable to convert document content" ), -1 ); delete mCursor; return oooDocument.anyFileEncrypted() ? Okular::Document::OpenNeedsPassword : Okular::Document::OpenError; } } element = element.nextSiblingElement(); } MetaInformation::List metaInformation = mStyleInformation->metaInformation(); for ( int i = 0; i < metaInformation.count(); ++i ) { emit addMetaData( metaInformation[ i ].key(), metaInformation[ i ].value(), metaInformation[ i ].title() ); } delete mCursor; delete mStyleInformation; mStyleInformation = 0; setDocument( mTextDocument ); return Okular::Document::OpenSuccess; } bool Converter::convertBody( const QDomElement &element ) { QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "text" ) ) { if ( !convertText( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertText( const QDomElement &element ) { 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" ) ) { if ( !convertList( mCursor, child ) ) return false; } else if ( child.tagName() == QLatin1String( "table" ) ) { if ( !convertTable( child ) ) return false; } child = child.nextSiblingElement(); } return true; } bool Converter::convertHeader( QTextCursor *cursor, const QDomElement &element ) { const QString styleName = element.attribute( "style-name" ); const StyleFormatProperty property = mStyleInformation->styleProperty( styleName ); QTextBlockFormat blockFormat; QTextCharFormat textFormat; property.applyBlock( &blockFormat ); property.applyText( &textFormat ); 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(); } emit addTitle( element.attribute( "outline-level", QString("0") ).toInt(), element.text(), cursor->block() ); return true; } bool Converter::convertParagraph( QTextCursor *cursor, const QDomElement &element, const QTextBlockFormat &parentFormat, bool merge ) { const QString styleName = element.attribute( "style-name" ); const StyleFormatProperty property = mStyleInformation->styleProperty( styleName ); QTextBlockFormat blockFormat( parentFormat ); QTextCharFormat textFormat; property.applyBlock( &blockFormat ); property.applyText( &textFormat ); if ( merge ) cursor->mergeBlockFormat( blockFormat ); else 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 ( 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; } else if ( childElement.tagName() == QLatin1String( "a" ) ) { if ( !convertLink( cursor, childElement, textFormat ) ) return false; } else if ( childElement.tagName() == QLatin1String( "annotation" ) ) { if ( !convertAnnotation( cursor, childElement ) ) return false; } } else if ( child.isText() ) { const QDomText childText = child.toText(); if ( !convertTextNode( cursor, childText, textFormat ) ) return false; } child = child.nextSibling(); } return true; } bool Converter::convertTextNode( QTextCursor *cursor, const QDomText &element, const QTextCharFormat &format ) { cursor->insertText( element.data(), format ); 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 ); QTextCharFormat textFormat( format ); property.applyText( &textFormat ); 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(); } return true; } bool Converter::convertList( QTextCursor *cursor, const QDomElement &element ) { const QString styleName = element.attribute( "style-name" ); const ListFormatProperty property = mStyleInformation->listProperty( styleName ); QTextListFormat format; if ( cursor->currentList() ) { // we are in a nested list format = cursor->currentList()->format(); format.setIndent( format.indent() + 1 ); } property.apply( &format, 0 ); QTextList *list = cursor->insertList( format ); QDomElement itemChild = element.firstChildElement(); int loop = 0; while ( !itemChild.isNull() ) { if ( itemChild.tagName() == QLatin1String( "list-item" ) ) { loop++; QDomElement childElement = itemChild.firstChildElement(); while ( !childElement.isNull() ) { QTextBlock prevBlock; if ( childElement.tagName() == QLatin1String( "p" ) ) { if ( loop > 1 ) cursor->insertBlock(); prevBlock = cursor->block(); if ( !convertParagraph( cursor, childElement, QTextBlockFormat(), true ) ) return false; } else if ( childElement.tagName() == QLatin1String( "list" ) ) { prevBlock = cursor->block(); if ( !convertList( cursor, childElement ) ) return false; } if( prevBlock.isValid() ) list->add( prevBlock ); childElement = childElement.nextSiblingElement(); } } itemChild = itemChild.nextSiblingElement(); } return true; } static void enqueueNodeList( QQueue &queue, const QDomNodeList &list ) { for ( int i = 0; i < list.count(); ++i ) { queue.enqueue( list.at( i ) ); } } bool Converter::convertTable( const QDomElement &element ) { /** * Find out dimension of the table */ int rowCounter = 0; int columnCounter = 0; QQueue nodeQueue; enqueueNodeList( nodeQueue, element.childNodes() ); while ( !nodeQueue.isEmpty() ) { QDomElement el = nodeQueue.dequeue().toElement(); if ( el.isNull() ) continue; if ( el.tagName() == QLatin1String( "table-row" ) ) { rowCounter++; int counter = 0; QDomElement columnElement = el.firstChildElement(); while ( !columnElement.isNull() ) { if ( columnElement.tagName() == QLatin1String( "table-cell" ) ) { counter++; } columnElement = columnElement.nextSiblingElement(); } columnCounter = qMax( columnCounter, counter ); } else if ( el.tagName() == QLatin1String( "table-header-rows" ) ) { enqueueNodeList( nodeQueue, el.childNodes() ); } } /** * Create table */ QTextTable *table = mCursor->insertTable( rowCounter, columnCounter ); mCursor->movePosition( QTextCursor::End ); /** * Fill table */ nodeQueue.clear(); enqueueNodeList( nodeQueue, element.childNodes() ); QTextTableFormat tableFormat; rowCounter = 0; while ( !nodeQueue.isEmpty() ) { QDomElement el = nodeQueue.dequeue().toElement(); if ( el.isNull() ) continue; if ( el.tagName() == QLatin1String( "table-row" ) ) { int columnCounter = 0; QDomElement columnElement = el.firstChildElement(); while ( !columnElement.isNull() ) { if ( columnElement.tagName() == QLatin1String( "table-cell" ) ) { const StyleFormatProperty property = mStyleInformation->styleProperty( columnElement.attribute( "style-name" ) ); QTextBlockFormat format; property.applyTableCell( &format ); QDomElement paragraphElement = columnElement.firstChildElement(); while ( !paragraphElement.isNull() ) { if ( paragraphElement.tagName() == QLatin1String( "p" ) ) { QTextTableCell cell = table->cellAt( rowCounter, columnCounter ); // 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 ) ) return false; } 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; } } paragraphElement = paragraphElement.nextSiblingElement(); } columnCounter++; } columnElement = columnElement.nextSiblingElement(); } rowCounter++; } else if ( el.tagName() == QLatin1String( "table-column" ) ) { const StyleFormatProperty property = mStyleInformation->styleProperty( el.attribute( "style-name" ) ); const QString tableColumnNumColumnsRepeated = el.attribute( "number-columns-repeated", "1" ); int numColumnsToApplyTo = tableColumnNumColumnsRepeated.toInt(); for (int i = 0; i < numColumnsToApplyTo; ++i) { property.applyTableColumn( &tableFormat ); } } } table->setFormat( tableFormat ); return true; } 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; } bool Converter::convertLink( QTextCursor *cursor, const QDomElement &element, const QTextCharFormat &format ) { int startPosition = cursor->position(); 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(); } int endPosition = cursor->position(); Okular::Action *action = new Okular::BrowseAction( element.attribute( "href" ) ); emit addAction( action, startPosition, endPosition ); return true; } bool Converter::convertAnnotation( QTextCursor *cursor, const QDomElement &element ) { QStringList contents; QString creator; QDateTime dateTime; int position = cursor->position(); QDomElement child = element.firstChildElement(); while ( !child.isNull() ) { if ( child.tagName() == QLatin1String( "creator" ) ) { creator = child.text(); } else if ( child.tagName() == QLatin1String( "date" ) ) { dateTime = QDateTime::fromString( child.text(), Qt::ISODate ); } else if ( child.tagName() == QLatin1String( "p" ) ) { contents.append( child.text() ); } child = child.nextSiblingElement(); } 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 ); emit addAnnotation( annotation, position, position + 3 ); return true; }