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.
591 lines
20 KiB
591 lines
20 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 <QDomElement> |
|
#include <QDomText> |
|
#include <QQueue> |
|
#include <QTextCursor> |
|
#include <QTextDocument> |
|
#include <QTextFrame> |
|
#include <QTextList> |
|
#include <QTextTableCell> |
|
#include <QUrl> |
|
#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; |
|
}
|
|
|