You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
594 lines
18 KiB
594 lines
18 KiB
/*************************************************************************** |
|
* 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( 0xff, 0xff, 0x00 ) ); |
|
annotation->style().setOpacity( 0.5 ); |
|
|
|
emit addAnnotation( annotation, position, position + 3 ); |
|
|
|
return true; |
|
}
|
|
|