mirror of
https://invent.kde.org/graphics/okular
synced 2024-11-05 18:34:53 +00:00
594 lines
18 KiB
C++
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;
|
|
}
|