okular/generators/ooo/converter.cpp
2018-08-31 12:23:45 +03:00

594 lines
18 KiB
C++

/***************************************************************************
* 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. *
***************************************************************************/
#include "converter.h"
#include <QQueue>
#include <QUrl>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextFrame>
#include <QTextList>
#include <QTextTableCell>
#include <QDomElement>
#include <QDomText>
#include <QXmlSimpleReader>
#include <core/action.h>
#include <core/annotations.h>
#include <core/document.h>
#include <core/utils.h>
#include <KLocalizedString>
#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( nullptr ), mCursor( nullptr ),
mStyleInformation( nullptr )
{
}
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<QString, QByteArray> images = oooDocument.images();
QMapIterator<QString, QByteArray> 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 );
const QSizeF dpi = Okular::Utils::realDpi(nullptr);
int pageWidth = qRound(property.width() / 72.0 * dpi.width());
int pageHeight = qRound(property.height() / 72.0 * dpi.height());
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 = nullptr;
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( QStringLiteral("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( QStringLiteral("outline-level"), QStringLiteral("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( QStringLiteral("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( QStringLiteral(" ") );
} else if ( childElement.tagName() == QLatin1String( "s" ) ) {
QString spaces;
spaces.fill( QLatin1Char(' '), childElement.attribute( QStringLiteral("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( QStringLiteral("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( QStringLiteral("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<QDomNode> &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<QDomNode> 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( QStringLiteral("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( QStringLiteral("style-name") ) );
const QString tableColumnNumColumnsRepeated = el.attribute( QStringLiteral("number-columns-repeated"), QStringLiteral("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( QStringLiteral("href") );
QTextImageFormat format;
format.setWidth( StyleParser::convertUnit( element.attribute( QStringLiteral("width") ) ) );
format.setHeight( StyleParser::convertUnit( element.attribute( QStringLiteral("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( QUrl(element.attribute( QStringLiteral("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( QStringLiteral("\n") ) );
annotation->setCreationDate( dateTime );
annotation->style().setColor( QColor( "#ffff00" ) );
annotation->style().setOpacity( 0.5 );
emit addAnnotation( annotation, position, position + 3 );
return true;
}