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.
 
 
 
 
 

852 lines
26 KiB

/***************************************************************************
* Copyright (C) 2007 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 <QAbstractTextDocumentLayout>
#include <QDate>
#include <QDomElement>
#include <QDomText>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextFrame>
#include <QUrl>
#include <KLocalizedString>
#include <core/action.h>
#include <core/document.h>
#include "document.h"
using namespace FictionBook;
class Converter::TitleInfo
{
public:
QStringList mGenres;
QString mAuthor;
QString mTitle;
QStringList mKeywords;
QDate mDate;
QDomElement mCoverPage;
QString mLanguage;
};
class Converter::DocumentInfo
{
public:
QString mAuthor;
QString mProducer;
QDate mDate;
QString mId;
QString mVersion;
};
Converter::Converter()
: mTextDocument(nullptr)
, mCursor(nullptr)
, mTitleInfo(nullptr)
, mDocumentInfo(nullptr)
{
}
Converter::~Converter()
{
delete mTitleInfo;
delete mDocumentInfo;
}
QTextDocument *Converter::convert(const QString &fileName)
{
Document fbDocument(fileName);
if (!fbDocument.open()) {
emit error(fbDocument.lastErrorString(), -1);
return nullptr;
}
mTextDocument = new QTextDocument;
mCursor = new QTextCursor(mTextDocument);
mSectionCounter = 0;
mLocalLinks.clear();
mSectionMap.clear();
const QDomDocument document = fbDocument.content();
/**
* Set the correct page size
*/
mTextDocument->setPageSize(QSizeF(600, 800));
QTextFrameFormat frameFormat;
frameFormat.setMargin(20);
QTextFrame *rootFrame = mTextDocument->rootFrame();
rootFrame->setFrameFormat(frameFormat);
/**
* Parse the content of the document
*/
const QDomElement documentElement = document.documentElement();
if (documentElement.tagName() != QLatin1String("FictionBook")) {
emit error(i18n("Document is not a valid FictionBook"), -1);
delete mCursor;
return nullptr;
}
/**
* First we read all images, so we can calculate the size later.
*/
QDomElement element = documentElement.firstChildElement();
while (!element.isNull()) {
if (element.tagName() == QLatin1String("binary")) {
if (!convertBinary(element)) {
delete mCursor;
return nullptr;
}
}
element = element.nextSiblingElement();
}
/**
* Read the rest...
*/
element = documentElement.firstChildElement();
while (!element.isNull()) {
if (element.tagName() == QLatin1String("description")) {
if (!convertDescription(element)) {
delete mCursor;
return nullptr;
}
} else if (element.tagName() == QLatin1String("body")) {
if (mTitleInfo && !mTitleInfo->mCoverPage.isNull()) {
convertCover(mTitleInfo->mCoverPage);
mCursor->insertBlock();
}
QTextFrame *topFrame = mCursor->currentFrame();
QTextFrameFormat frameFormat;
frameFormat.setBorder(2);
frameFormat.setPadding(8);
frameFormat.setBackground(Qt::lightGray);
if (mTitleInfo && !mTitleInfo->mTitle.isEmpty()) {
mCursor->insertFrame(frameFormat);
QTextCharFormat charFormat;
charFormat.setFontPointSize(22);
charFormat.setFontWeight(QFont::Bold);
mCursor->insertText(mTitleInfo->mTitle, charFormat);
mCursor->setPosition(topFrame->lastPosition());
}
if (mTitleInfo && !mTitleInfo->mAuthor.isEmpty()) {
frameFormat.setBorder(1);
mCursor->insertFrame(frameFormat);
QTextCharFormat charFormat;
charFormat.setFontPointSize(10);
mCursor->insertText(mTitleInfo->mAuthor, charFormat);
mCursor->setPosition(topFrame->lastPosition());
mCursor->insertBlock();
}
mCursor->insertBlock();
if (!convertBody(element)) {
delete mCursor;
return nullptr;
}
}
element = element.nextSiblingElement();
}
/**
* Add document info.
*/
if (mTitleInfo) {
if (!mTitleInfo->mTitle.isEmpty())
emit addMetaData(Okular::DocumentInfo::Title, mTitleInfo->mTitle);
if (!mTitleInfo->mAuthor.isEmpty())
emit addMetaData(Okular::DocumentInfo::Author, mTitleInfo->mAuthor);
}
if (mDocumentInfo) {
if (!mDocumentInfo->mProducer.isEmpty())
emit addMetaData(Okular::DocumentInfo::Producer, mDocumentInfo->mProducer);
if (mDocumentInfo->mDate.isValid())
emit addMetaData(Okular::DocumentInfo::CreationDate, QLocale().toString(mDocumentInfo->mDate, QLocale::ShortFormat));
}
QMapIterator<QString, QPair<int, int>> it(mLocalLinks);
while (it.hasNext()) {
it.next();
const QTextBlock block = mSectionMap[it.key()];
if (!block.isValid()) // local link without existing target
continue;
Okular::DocumentViewport viewport = calculateViewport(mTextDocument, block);
Okular::GotoAction *action = new Okular::GotoAction(QString(), viewport);
emit addAction(action, it.value().first, it.value().second);
}
delete mCursor;
return mTextDocument;
}
bool Converter::convertBody(const QDomElement &element)
{
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("section")) {
mCursor->insertBlock();
if (!convertSection(child))
return false;
} else if (child.tagName() == QLatin1String("image")) {
if (!convertImage(child))
return false;
} else if (child.tagName() == QLatin1String("title")) {
if (!convertTitle(child))
return false;
} else if (child.tagName() == QLatin1String("epigraph")) {
if (!convertEpigraph(child))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertDescription(const QDomElement &element)
{
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("title-info")) {
if (!convertTitleInfo(child))
return false;
}
if (child.tagName() == QLatin1String("document-info")) {
if (!convertDocumentInfo(child))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertTitleInfo(const QDomElement &element)
{
delete mTitleInfo;
mTitleInfo = new TitleInfo;
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("genre")) {
QString genre;
if (!convertTextNode(child, genre))
return false;
if (!genre.isEmpty())
mTitleInfo->mGenres.append(genre);
} else if (child.tagName() == QLatin1String("author")) {
QString firstName, middleName, lastName, dummy;
if (!convertAuthor(child, firstName, middleName, lastName, dummy, dummy))
return false;
mTitleInfo->mAuthor = QStringLiteral("%1 %2 %3").arg(firstName, middleName, lastName);
} else if (child.tagName() == QLatin1String("book-title")) {
if (!convertTextNode(child, mTitleInfo->mTitle))
return false;
} else if (child.tagName() == QLatin1String("keywords")) {
QString keywords;
if (!convertTextNode(child, keywords))
return false;
mTitleInfo->mKeywords = keywords.split(QLatin1Char(' '), QString::SkipEmptyParts);
} else if (child.tagName() == QLatin1String("date")) {
if (!convertDate(child, mTitleInfo->mDate))
return false;
} else if (child.tagName() == QLatin1String("coverpage")) {
mTitleInfo->mCoverPage = child;
} else if (child.tagName() == QLatin1String("lang")) {
if (!convertTextNode(child, mTitleInfo->mLanguage))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertDocumentInfo(const QDomElement &element)
{
delete mDocumentInfo;
mDocumentInfo = new DocumentInfo;
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("author")) {
QString firstName, middleName, lastName, email, nickname;
if (!convertAuthor(child, firstName, middleName, lastName, email, nickname))
return false;
mDocumentInfo->mAuthor = QStringLiteral("%1 %2 %3 <%4> (%5)").arg(firstName, middleName, lastName, email, nickname);
} else if (child.tagName() == QLatin1String("program-used")) {
if (!convertTextNode(child, mDocumentInfo->mProducer))
return false;
} else if (child.tagName() == QLatin1String("date")) {
if (!convertDate(child, mDocumentInfo->mDate))
return false;
} else if (child.tagName() == QLatin1String("id")) {
if (!convertTextNode(child, mDocumentInfo->mId))
return false;
} else if (child.tagName() == QLatin1String("version")) {
if (!convertTextNode(child, mDocumentInfo->mVersion))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertAuthor(const QDomElement &element, QString &firstName, QString &middleName, QString &lastName, QString &email, QString &nickname)
{
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("first-name")) {
if (!convertTextNode(child, firstName))
return false;
} else if (child.tagName() == QLatin1String("middle-name")) {
if (!convertTextNode(child, middleName))
return false;
} else if (child.tagName() == QLatin1String("last-name")) {
if (!convertTextNode(child, lastName))
return false;
} else if (child.tagName() == QLatin1String("email")) {
if (!convertTextNode(child, email))
return false;
} else if (child.tagName() == QLatin1String("nickname")) {
if (!convertTextNode(child, nickname))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertTextNode(const QDomElement &element, QString &data)
{
QDomNode child = element.firstChild();
while (!child.isNull()) {
QDomText text = child.toText();
if (!text.isNull())
data = text.data();
child = child.nextSibling();
}
return true;
}
bool Converter::convertDate(const QDomElement &element, QDate &date)
{
if (element.hasAttribute(QStringLiteral("value")))
date = QDate::fromString(element.attribute(QStringLiteral("value")), Qt::ISODate);
return true;
}
bool Converter::convertSection(const QDomElement &element)
{
if (element.hasAttribute(QStringLiteral("id")))
mSectionMap.insert(element.attribute(QStringLiteral("id")), mCursor->block());
mSectionCounter++;
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("title")) {
if (!convertTitle(child))
return false;
} else if (child.tagName() == QLatin1String("epigraph")) {
if (!convertEpigraph(child))
return false;
} else if (child.tagName() == QLatin1String("image")) {
if (!convertImage(child))
return false;
} else if (child.tagName() == QLatin1String("section")) {
if (!convertSection(child))
return false;
} else if (child.tagName() == QLatin1String("p")) {
QTextBlockFormat format;
format.setTextIndent(10);
mCursor->insertBlock(format);
if (!convertParagraph(child))
return false;
} else if (child.tagName() == QLatin1String("poem")) {
if (!convertPoem(child))
return false;
} else if (child.tagName() == QLatin1String("subtitle")) {
if (!convertSubTitle(child))
return false;
} else if (child.tagName() == QLatin1String("cite")) {
if (!convertCite(child))
return false;
} else if (child.tagName() == QLatin1String("empty-line")) {
if (!convertEmptyLine(child))
return false;
} else if (child.tagName() == QLatin1String("code")) {
if (!convertCode(child))
return false;
}
child = child.nextSiblingElement();
}
mSectionCounter--;
return true;
}
bool Converter::convertTitle(const QDomElement &element)
{
QTextFrame *topFrame = mCursor->currentFrame();
QTextFrameFormat frameFormat;
frameFormat.setBorder(1);
frameFormat.setPadding(8);
frameFormat.setBackground(Qt::lightGray);
mCursor->insertFrame(frameFormat);
QDomElement child = element.firstChildElement();
bool firstParagraph = true;
while (!child.isNull()) {
if (child.tagName() == QLatin1String("p")) {
if (firstParagraph)
firstParagraph = false;
else
mCursor->insertBlock();
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat titleFormat(origFormat);
titleFormat.setFontPointSize(22 - (mSectionCounter * 2));
titleFormat.setFontWeight(QFont::Bold);
mCursor->setCharFormat(titleFormat);
if (!convertParagraph(child))
return false;
mCursor->setCharFormat(origFormat);
emit addTitle(mSectionCounter, child.text(), mCursor->block());
} else if (child.tagName() == QLatin1String("empty-line")) {
if (!convertEmptyLine(child))
return false;
}
child = child.nextSiblingElement();
}
mCursor->setPosition(topFrame->lastPosition());
return true;
}
bool Converter::convertParagraph(const QDomElement &element)
{
QDomNode child = element.firstChild();
while (!child.isNull()) {
if (child.isElement()) {
const QDomElement childElement = child.toElement();
if (childElement.tagName() == QLatin1String("emphasis")) {
if (!convertEmphasis(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("strong")) {
if (!convertStrong(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("style")) {
if (!convertStyle(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("a")) {
if (!convertLink(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("image")) {
if (!convertImage(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("strikethrough")) {
if (!convertStrikethrough(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("code")) {
if (!convertCode(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("sup")) {
if (!convertSuperScript(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("sub")) {
if (!convertSubScript(childElement))
return false;
}
} else if (child.isText()) {
const QDomText childText = child.toText();
mCursor->insertText(childText.data());
}
child = child.nextSibling();
}
return true;
}
bool Converter::convertEmphasis(const QDomElement &element)
{
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat italicFormat(origFormat);
italicFormat.setFontItalic(true);
mCursor->setCharFormat(italicFormat);
if (!convertParagraph(element))
return false;
mCursor->setCharFormat(origFormat);
return true;
}
bool Converter::convertStrikethrough(const QDomElement &element)
{
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat strikeoutFormat(origFormat);
strikeoutFormat.setFontStrikeOut(true);
mCursor->setCharFormat(strikeoutFormat);
if (!convertParagraph(element))
return false;
mCursor->setCharFormat(origFormat);
return true;
}
bool Converter::convertStrong(const QDomElement &element)
{
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat boldFormat(origFormat);
boldFormat.setFontWeight(QFont::Bold);
mCursor->setCharFormat(boldFormat);
if (!convertParagraph(element))
return false;
mCursor->setCharFormat(origFormat);
return true;
}
bool Converter::convertStyle(const QDomElement &element)
{
if (!convertParagraph(element))
return false;
return true;
}
bool Converter::convertBinary(const QDomElement &element)
{
const QString id = element.attribute(QStringLiteral("id"));
const QDomText textNode = element.firstChild().toText();
QByteArray data = textNode.data().toLatin1();
data = QByteArray::fromBase64(data);
mTextDocument->addResource(QTextDocument::ImageResource, QUrl(id), QImage::fromData(data));
return true;
}
bool Converter::convertCover(const QDomElement &element)
{
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("image")) {
if (!convertImage(child))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertImage(const QDomElement &element)
{
QString href = element.attributeNS(QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("href"));
if (href.startsWith(QLatin1Char('#')))
href = href.mid(1);
const QImage img = qvariant_cast<QImage>(mTextDocument->resource(QTextDocument::ImageResource, QUrl(href)));
QTextImageFormat format;
format.setName(href);
if (img.width() > 560)
format.setWidth(560);
format.setHeight(img.height());
mCursor->insertImage(format);
return true;
}
bool Converter::convertEpigraph(const QDomElement &element)
{
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("p")) {
QTextBlockFormat format;
format.setTextIndent(10);
mCursor->insertBlock(format);
if (!convertParagraph(child))
return false;
} else if (child.tagName() == QLatin1String("poem")) {
if (!convertPoem(child))
return false;
} else if (child.tagName() == QLatin1String("cite")) {
if (!convertCite(child))
return false;
} else if (child.tagName() == QLatin1String("empty-line")) {
if (!convertEmptyLine(child))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertPoem(const QDomElement &element)
{
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("title")) {
if (!convertTitle(child))
return false;
} else if (child.tagName() == QLatin1String("epigraph")) {
if (!convertEpigraph(child))
return false;
} else if (child.tagName() == QLatin1String("empty-line")) {
if (!convertEmptyLine(child))
return false;
} else if (child.tagName() == QLatin1String("stanza")) {
if (!convertStanza(child))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertSubTitle(const QDomElement &element)
{
Q_UNUSED(element)
return true;
}
bool Converter::convertCite(const QDomElement &element)
{
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("p")) {
QTextBlockFormat format;
format.setTextIndent(10);
mCursor->insertBlock(format);
if (!convertParagraph(child))
return false;
} else if (child.tagName() == QLatin1String("poem")) {
if (!convertParagraph(child))
return false;
} else if (child.tagName() == QLatin1String("empty-line")) {
if (!convertEmptyLine(child))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertEmptyLine(const QDomElement &)
{
mCursor->insertText(QStringLiteral("\n\n"));
return true;
}
bool Converter::convertLink(const QDomElement &element)
{
QString href = element.attributeNS(QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("href"));
QString type = element.attributeNS(QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("type"));
if (type == QLatin1String("note"))
mCursor->insertText(QStringLiteral("["));
int startPosition = mCursor->position();
QTextCharFormat origFormat(mCursor->charFormat());
QTextCharFormat format(mCursor->charFormat());
format.setForeground(Qt::blue);
format.setFontUnderline(true);
mCursor->setCharFormat(format);
QDomNode child = element.firstChild();
while (!child.isNull()) {
if (child.isElement()) {
const QDomElement childElement = child.toElement();
if (childElement.tagName() == QLatin1String("emphasis")) {
if (!convertEmphasis(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("strong")) {
if (!convertStrong(childElement))
return false;
} else if (childElement.tagName() == QLatin1String("style")) {
if (!convertStyle(childElement))
return false;
}
} else if (child.isText()) {
const QDomText text = child.toText();
if (!text.isNull()) {
mCursor->insertText(text.data());
}
}
child = child.nextSibling();
}
mCursor->setCharFormat(origFormat);
int endPosition = mCursor->position();
if (type == QLatin1String("note"))
mCursor->insertText(QStringLiteral("]"));
if (href.startsWith(QLatin1Char('#'))) { // local link
mLocalLinks.insert(href.mid(1), QPair<int, int>(startPosition, endPosition));
} else {
// external link
Okular::BrowseAction *action = new Okular::BrowseAction(QUrl(href));
emit addAction(action, startPosition, endPosition);
}
return true;
}
bool Converter::convertStanza(const QDomElement &element)
{
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == QLatin1String("title")) {
if (!convertTitle(child))
return false;
} else if (child.tagName() == QLatin1String("subtitle")) {
if (!convertSubTitle(child))
return false;
} else if (child.tagName() == QLatin1String("v")) {
QTextBlockFormat format;
format.setTextIndent(50);
mCursor->insertBlock(format);
if (!convertParagraph(child))
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertCode(const QDomElement &element)
{
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat codeFormat(origFormat);
codeFormat.setFontFamily(QStringLiteral("monospace"));
mCursor->setCharFormat(codeFormat);
if (!convertParagraph(element))
return false;
mCursor->setCharFormat(origFormat);
return true;
}
bool Converter::convertSuperScript(const QDomElement &element)
{
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat superScriptFormat(origFormat);
superScriptFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
mCursor->setCharFormat(superScriptFormat);
if (!convertParagraph(element))
return false;
mCursor->setCharFormat(origFormat);
return true;
}
bool Converter::convertSubScript(const QDomElement &element)
{
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat subScriptFormat(origFormat);
subScriptFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
mCursor->setCharFormat(subScriptFormat);
if (!convertParagraph(element))
return false;
mCursor->setCharFormat(origFormat);
return true;
}