/*************************************************************************** * Copyright (C) 2007 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 "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> 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(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(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; }