/*************************************************************************** * 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(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 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); 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 &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(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; }