File indexing completed on 2024-05-12 16:06:43

0001 /*
0002     SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "converter.h"
0008 
0009 #include <QAbstractTextDocumentLayout>
0010 #include <QDate>
0011 #include <QDomElement>
0012 #include <QDomText>
0013 #include <QTextCursor>
0014 #include <QTextDocument>
0015 #include <QTextFrame>
0016 #include <QTextTable>
0017 #include <QUrl>
0018 
0019 #include <KLocalizedString>
0020 
0021 #include <core/action.h>
0022 #include <core/document.h>
0023 
0024 #include "document.h"
0025 
0026 using namespace FictionBook;
0027 
0028 class Converter::TitleInfo
0029 {
0030 public:
0031     QStringList mGenres;
0032     QString mAuthor;
0033     QString mTitle;
0034     QString mAnnotation;
0035     QString mKeywords;
0036     QDate mDate;
0037     QDomElement mCoverPage;
0038     QString mLanguage;
0039 };
0040 
0041 class Converter::DocumentInfo
0042 {
0043 public:
0044     QString mAuthor;
0045     QString mProducer;
0046     QDate mDate;
0047     QString mId;
0048     QString mVersion;
0049 };
0050 
0051 Converter::Converter()
0052     : mTextDocument(nullptr)
0053     , mCursor(nullptr)
0054     , mTitleInfo(nullptr)
0055     , mDocumentInfo(nullptr)
0056 {
0057 }
0058 
0059 Converter::~Converter()
0060 {
0061     delete mTitleInfo;
0062     delete mDocumentInfo;
0063 }
0064 
0065 QTextDocument *Converter::convert(const QString &fileName)
0066 {
0067     Document fbDocument(fileName);
0068     if (!fbDocument.open()) {
0069         Q_EMIT error(fbDocument.lastErrorString(), -1);
0070         return nullptr;
0071     }
0072 
0073     mTextDocument = new QTextDocument;
0074     mCursor = new QTextCursor(mTextDocument);
0075     mSectionCounter = 0;
0076     mLocalLinks.clear();
0077     mSectionMap.clear();
0078 
0079     const QDomDocument document = fbDocument.content();
0080 
0081     /**
0082      * Set the correct page size
0083      */
0084     mTextDocument->setPageSize(QSizeF(600, 800));
0085 
0086     QTextFrameFormat frameFormat;
0087     frameFormat.setMargin(20);
0088 
0089     QTextFrame *rootFrame = mTextDocument->rootFrame();
0090     rootFrame->setFrameFormat(frameFormat);
0091 
0092     /**
0093      * Parse the content of the document
0094      */
0095     const QDomElement documentElement = document.documentElement();
0096 
0097     if (documentElement.tagName() != QLatin1String("FictionBook")) {
0098         Q_EMIT error(i18n("Document is not a valid FictionBook"), -1);
0099         delete mCursor;
0100         return nullptr;
0101     }
0102 
0103     /**
0104      * First we read all images, so we can calculate the size later.
0105      */
0106     QDomElement element = documentElement.firstChildElement();
0107     while (!element.isNull()) {
0108         if (element.tagName() == QLatin1String("binary")) {
0109             if (!convertBinary(element)) {
0110                 delete mCursor;
0111                 return nullptr;
0112             }
0113         }
0114 
0115         element = element.nextSiblingElement();
0116     }
0117 
0118     /**
0119      * Read the rest: description (could be only one) and bodies (one or more)
0120      */
0121     element = documentElement.firstChildElement();
0122     while (!element.isNull()) {
0123         if (element.tagName() == QLatin1String("description")) {
0124             if (!convertDescription(element)) {
0125                 delete mCursor;
0126                 return nullptr;
0127             }
0128 
0129             if (mTitleInfo && !mTitleInfo->mCoverPage.isNull()) {
0130                 convertCover(mTitleInfo->mCoverPage);
0131                 mCursor->insertBlock();
0132             }
0133 
0134             QTextFrame *topFrame = mCursor->currentFrame();
0135 
0136             QTextFrameFormat frameFormat;
0137             frameFormat.setBorder(2);
0138             frameFormat.setPadding(8);
0139             frameFormat.setBackground(Qt::lightGray);
0140 
0141             if (mTitleInfo && !mTitleInfo->mTitle.isEmpty()) {
0142                 mCursor->insertFrame(frameFormat);
0143 
0144                 QTextCharFormat charFormat;
0145                 charFormat.setFontPointSize(22);
0146                 charFormat.setFontWeight(QFont::Bold);
0147                 mCursor->insertText(mTitleInfo->mTitle, charFormat);
0148 
0149                 mCursor->setPosition(topFrame->lastPosition());
0150             }
0151 
0152             if (mTitleInfo && !mTitleInfo->mAuthor.isEmpty()) {
0153                 frameFormat.setBorder(1);
0154                 mCursor->insertFrame(frameFormat);
0155 
0156                 QTextCharFormat charFormat;
0157                 charFormat.setFontPointSize(10);
0158                 mCursor->insertText(mTitleInfo->mAuthor, charFormat);
0159 
0160                 mCursor->setPosition(topFrame->lastPosition());
0161                 mCursor->insertBlock();
0162             }
0163 
0164             if (mTitleInfo && !mTitleInfo->mAnnotation.isEmpty()) {
0165                 frameFormat.setBorder(0);
0166                 mCursor->insertFrame(frameFormat);
0167 
0168                 QTextCharFormat charFormat;
0169                 charFormat.setFontPointSize(10);
0170                 charFormat.setFontItalic(true);
0171                 mCursor->insertText(mTitleInfo->mAnnotation, charFormat);
0172 
0173                 mCursor->setPosition(topFrame->lastPosition());
0174                 mCursor->insertBlock();
0175             }
0176         } else if (element.tagName() == QLatin1String("body")) {
0177             mCursor->insertBlock();
0178 
0179             if (!convertBody(element)) {
0180                 delete mCursor;
0181                 return nullptr;
0182             }
0183         }
0184 
0185         element = element.nextSiblingElement();
0186     }
0187 
0188     /**
0189      * Add document info.
0190      */
0191     if (mTitleInfo) {
0192         if (!mTitleInfo->mTitle.isEmpty()) {
0193             Q_EMIT addMetaData(Okular::DocumentInfo::Title, mTitleInfo->mTitle);
0194         }
0195 
0196         if (!mTitleInfo->mAuthor.isEmpty()) {
0197             Q_EMIT addMetaData(Okular::DocumentInfo::Author, mTitleInfo->mAuthor);
0198         }
0199 
0200         if (!mTitleInfo->mKeywords.isEmpty()) {
0201             Q_EMIT addMetaData(Okular::DocumentInfo::Keywords, mTitleInfo->mKeywords);
0202         }
0203     }
0204 
0205     if (mDocumentInfo) {
0206         if (!mDocumentInfo->mProducer.isEmpty()) {
0207             Q_EMIT addMetaData(Okular::DocumentInfo::Producer, mDocumentInfo->mProducer);
0208         }
0209 
0210         if (mDocumentInfo->mDate.isValid()) {
0211             Q_EMIT addMetaData(Okular::DocumentInfo::CreationDate, QLocale().toString(mDocumentInfo->mDate, QLocale::ShortFormat));
0212         }
0213     }
0214 
0215     QMapIterator<QString, QPair<int, int>> it(mLocalLinks);
0216     while (it.hasNext()) {
0217         it.next();
0218 
0219         const QTextBlock block = mSectionMap[it.key()];
0220         if (!block.isValid()) { // local link without existing target
0221             continue;
0222         }
0223 
0224         Okular::DocumentViewport viewport = calculateViewport(mTextDocument, block);
0225 
0226         Okular::GotoAction *action = new Okular::GotoAction(QString(), viewport);
0227 
0228         Q_EMIT addAction(action, it.value().first, it.value().second);
0229     }
0230 
0231     delete mCursor;
0232 
0233     return mTextDocument;
0234 }
0235 
0236 bool Converter::convertBody(const QDomElement &element)
0237 {
0238     QDomElement child = element.firstChildElement();
0239     while (!child.isNull()) {
0240         if (child.tagName() == QLatin1String("section")) {
0241             mCursor->insertBlock();
0242             if (!convertSection(child)) {
0243                 return false;
0244             }
0245         } else if (child.tagName() == QLatin1String("image")) {
0246             if (!convertImage(child)) {
0247                 return false;
0248             }
0249         } else if (child.tagName() == QLatin1String("title")) {
0250             if (!convertTitle(child)) {
0251                 return false;
0252             }
0253         } else if (child.tagName() == QLatin1String("epigraph")) {
0254             if (!convertEpigraph(child)) {
0255                 return false;
0256             }
0257         }
0258 
0259         child = child.nextSiblingElement();
0260     }
0261 
0262     return true;
0263 }
0264 
0265 bool Converter::convertDescription(const QDomElement &element)
0266 {
0267     QDomElement child = element.firstChildElement();
0268     while (!child.isNull()) {
0269         if (child.tagName() == QLatin1String("title-info")) {
0270             if (!convertTitleInfo(child)) {
0271                 return false;
0272             }
0273         }
0274         if (child.tagName() == QLatin1String("document-info")) {
0275             if (!convertDocumentInfo(child)) {
0276                 return false;
0277             }
0278         }
0279 
0280         child = child.nextSiblingElement();
0281     }
0282 
0283     return true;
0284 }
0285 
0286 bool Converter::convertTitleInfo(const QDomElement &element)
0287 {
0288     delete mTitleInfo;
0289     mTitleInfo = new TitleInfo;
0290 
0291     QDomElement child = element.firstChildElement();
0292     while (!child.isNull()) {
0293         if (child.tagName() == QLatin1String("genre")) {
0294             QString genre;
0295             if (!convertTextNode(child, genre)) {
0296                 return false;
0297             }
0298 
0299             if (!genre.isEmpty()) {
0300                 mTitleInfo->mGenres.append(genre);
0301             }
0302         } else if (child.tagName() == QLatin1String("author")) {
0303             QString firstName, middleName, lastName, dummy;
0304 
0305             if (!convertAuthor(child, firstName, middleName, lastName, dummy, dummy)) {
0306                 return false;
0307             }
0308 
0309             if (mTitleInfo->mAuthor.isEmpty()) {
0310                 mTitleInfo->mAuthor = QStringLiteral("%1 %2 %3").arg(firstName, middleName, lastName).simplified();
0311             } else {
0312                 mTitleInfo->mAuthor += QStringLiteral(", %1 %2 %3").arg(firstName, middleName, lastName).simplified();
0313             }
0314         } else if (child.tagName() == QLatin1String("book-title")) {
0315             if (!convertTextNode(child, mTitleInfo->mTitle)) {
0316                 return false;
0317             }
0318         } else if (child.tagName() == QLatin1String("keywords")) {
0319             QString keywords;
0320             if (!convertTextNode(child, keywords)) {
0321                 return false;
0322             }
0323 
0324             mTitleInfo->mKeywords = keywords;
0325         } else if (child.tagName() == QLatin1String("annotation")) {
0326             if (!convertAnnotation(child, mTitleInfo->mAnnotation)) {
0327                 return false;
0328             }
0329         } else if (child.tagName() == QLatin1String("date")) {
0330             if (!convertDate(child, mTitleInfo->mDate)) {
0331                 return false;
0332             }
0333         } else if (child.tagName() == QLatin1String("coverpage")) {
0334             mTitleInfo->mCoverPage = child;
0335         } else if (child.tagName() == QLatin1String("lang")) {
0336             if (!convertTextNode(child, mTitleInfo->mLanguage)) {
0337                 return false;
0338             }
0339         }
0340         child = child.nextSiblingElement();
0341     }
0342 
0343     return true;
0344 }
0345 
0346 bool Converter::convertDocumentInfo(const QDomElement &element)
0347 {
0348     delete mDocumentInfo;
0349     mDocumentInfo = new DocumentInfo;
0350 
0351     QDomElement child = element.firstChildElement();
0352     while (!child.isNull()) {
0353         if (child.tagName() == QLatin1String("author")) {
0354             QString firstName, middleName, lastName, email, nickname;
0355 
0356             if (!convertAuthor(child, firstName, middleName, lastName, email, nickname)) {
0357                 return false;
0358             }
0359 
0360             mDocumentInfo->mAuthor = QStringLiteral("%1 %2 %3 <%4> (%5)").arg(firstName, middleName, lastName, email, nickname);
0361         } else if (child.tagName() == QLatin1String("program-used")) {
0362             if (!convertTextNode(child, mDocumentInfo->mProducer)) {
0363                 return false;
0364             }
0365         } else if (child.tagName() == QLatin1String("date")) {
0366             if (!convertDate(child, mDocumentInfo->mDate)) {
0367                 return false;
0368             }
0369         } else if (child.tagName() == QLatin1String("id")) {
0370             if (!convertTextNode(child, mDocumentInfo->mId)) {
0371                 return false;
0372             }
0373         } else if (child.tagName() == QLatin1String("version")) {
0374             if (!convertTextNode(child, mDocumentInfo->mVersion)) {
0375                 return false;
0376             }
0377         }
0378 
0379         child = child.nextSiblingElement();
0380     }
0381 
0382     return true;
0383 }
0384 bool Converter::convertAuthor(const QDomElement &element, QString &firstName, QString &middleName, QString &lastName, QString &email, QString &nickname)
0385 {
0386     QDomElement child = element.firstChildElement();
0387     while (!child.isNull()) {
0388         if (child.tagName() == QLatin1String("first-name")) {
0389             if (!convertTextNode(child, firstName)) {
0390                 return false;
0391             }
0392         } else if (child.tagName() == QLatin1String("middle-name")) {
0393             if (!convertTextNode(child, middleName)) {
0394                 return false;
0395             }
0396         } else if (child.tagName() == QLatin1String("last-name")) {
0397             if (!convertTextNode(child, lastName)) {
0398                 return false;
0399             }
0400         } else if (child.tagName() == QLatin1String("email")) {
0401             if (!convertTextNode(child, email)) {
0402                 return false;
0403             }
0404         } else if (child.tagName() == QLatin1String("nickname")) {
0405             if (!convertTextNode(child, nickname)) {
0406                 return false;
0407             }
0408         }
0409 
0410         child = child.nextSiblingElement();
0411     }
0412 
0413     return true;
0414 }
0415 
0416 bool Converter::convertTextNode(const QDomElement &element, QString &data)
0417 {
0418     QDomNode child = element.firstChild();
0419     while (!child.isNull()) {
0420         QDomText text = child.toText();
0421         if (!text.isNull()) {
0422             data = text.data();
0423         }
0424 
0425         child = child.nextSibling();
0426     }
0427 
0428     return true;
0429 }
0430 
0431 bool Converter::convertDate(const QDomElement &element, QDate &date)
0432 {
0433     if (element.hasAttribute(QStringLiteral("value"))) {
0434         date = QDate::fromString(element.attribute(QStringLiteral("value")), Qt::ISODate);
0435     }
0436 
0437     return true;
0438 }
0439 
0440 bool Converter::convertAnnotation(const QDomElement &element, QString &data)
0441 {
0442     QDomElement child = element.firstChildElement();
0443     while (!child.isNull()) {
0444         QString text = child.text();
0445         if (!text.isNull()) {
0446             data = child.text();
0447         }
0448 
0449         child = child.nextSiblingElement();
0450     }
0451 
0452     return true;
0453 }
0454 
0455 bool Converter::convertSection(const QDomElement &element)
0456 {
0457     if (element.hasAttribute(QStringLiteral("id"))) {
0458         mSectionMap.insert(element.attribute(QStringLiteral("id")), mCursor->block());
0459     }
0460 
0461     mSectionCounter++;
0462 
0463     QDomElement child = element.firstChildElement();
0464     while (!child.isNull()) {
0465         if (child.tagName() == QLatin1String("title")) {
0466             if (!convertTitle(child)) {
0467                 return false;
0468             }
0469         } else if (child.tagName() == QLatin1String("epigraph")) {
0470             if (!convertEpigraph(child)) {
0471                 return false;
0472             }
0473         } else if (child.tagName() == QLatin1String("image")) {
0474             if (!convertImage(child)) {
0475                 return false;
0476             }
0477         } else if (child.tagName() == QLatin1String("section")) {
0478             if (!convertSection(child)) {
0479                 return false;
0480             }
0481         } else if (child.tagName() == QLatin1String("p")) {
0482             QTextBlockFormat format;
0483             format.setTextIndent(10);
0484             mCursor->insertBlock(format);
0485             if (!convertParagraph(child)) {
0486                 return false;
0487             }
0488         } else if (child.tagName() == QLatin1String("poem")) {
0489             if (!convertPoem(child)) {
0490                 return false;
0491             }
0492         } else if (child.tagName() == QLatin1String("subtitle")) {
0493             if (!convertSubTitle(child)) {
0494                 return false;
0495             }
0496         } else if (child.tagName() == QLatin1String("cite")) {
0497             if (!convertCite(child)) {
0498                 return false;
0499             }
0500         } else if (child.tagName() == QLatin1String("empty-line")) {
0501             if (!convertEmptyLine(child)) {
0502                 return false;
0503             }
0504         } else if (child.tagName() == QLatin1String("code")) {
0505             if (!convertCode(child)) {
0506                 return false;
0507             }
0508         } else if (child.tagName() == QLatin1String("table")) {
0509             if (!convertTable(child)) {
0510                 return false;
0511             }
0512         }
0513 
0514         child = child.nextSiblingElement();
0515     }
0516 
0517     mSectionCounter--;
0518 
0519     return true;
0520 }
0521 
0522 bool Converter::convertTitle(const QDomElement &element)
0523 {
0524     QTextFrame *topFrame = mCursor->currentFrame();
0525 
0526     QTextFrameFormat frameFormat;
0527     frameFormat.setBorder(1);
0528     frameFormat.setPadding(8);
0529     frameFormat.setBackground(Qt::lightGray);
0530 
0531     mCursor->insertFrame(frameFormat);
0532 
0533     QDomElement child = element.firstChildElement();
0534 
0535     bool firstParagraph = true;
0536     while (!child.isNull()) {
0537         if (child.tagName() == QLatin1String("p")) {
0538             if (firstParagraph) {
0539                 firstParagraph = false;
0540             } else {
0541                 mCursor->insertBlock();
0542             }
0543 
0544             QTextCharFormat origFormat = mCursor->charFormat();
0545 
0546             QTextCharFormat titleFormat(origFormat);
0547             titleFormat.setFontPointSize(22 - (mSectionCounter * 2));
0548             titleFormat.setFontWeight(QFont::Bold);
0549             mCursor->setCharFormat(titleFormat);
0550 
0551             if (!convertParagraph(child)) {
0552                 return false;
0553             }
0554 
0555             mCursor->setCharFormat(origFormat);
0556 
0557             Q_EMIT addTitle(mSectionCounter, child.text(), mCursor->block());
0558 
0559         } else if (child.tagName() == QLatin1String("empty-line")) {
0560             if (!convertEmptyLine(child)) {
0561                 return false;
0562             }
0563         }
0564 
0565         child = child.nextSiblingElement();
0566     }
0567 
0568     mCursor->setPosition(topFrame->lastPosition());
0569 
0570     return true;
0571 }
0572 
0573 bool Converter::convertParagraph(const QDomElement &element)
0574 {
0575     QDomNode child = element.firstChild();
0576     while (!child.isNull()) {
0577         if (child.isElement()) {
0578             const QDomElement childElement = child.toElement();
0579             if (childElement.tagName() == QLatin1String("emphasis")) {
0580                 if (!convertEmphasis(childElement)) {
0581                     return false;
0582                 }
0583             } else if (childElement.tagName() == QLatin1String("strong")) {
0584                 if (!convertStrong(childElement)) {
0585                     return false;
0586                 }
0587             } else if (childElement.tagName() == QLatin1String("style")) {
0588                 if (!convertStyle(childElement)) {
0589                     return false;
0590                 }
0591             } else if (childElement.tagName() == QLatin1String("a")) {
0592                 if (!convertLink(childElement)) {
0593                     return false;
0594                 }
0595             } else if (childElement.tagName() == QLatin1String("image")) {
0596                 if (!convertImage(childElement)) {
0597                     return false;
0598                 }
0599             } else if (childElement.tagName() == QLatin1String("strikethrough")) {
0600                 if (!convertStrikethrough(childElement)) {
0601                     return false;
0602                 }
0603             } else if (childElement.tagName() == QLatin1String("code")) {
0604                 if (!convertCode(childElement)) {
0605                     return false;
0606                 }
0607             } else if (childElement.tagName() == QLatin1String("sup")) {
0608                 if (!convertSuperScript(childElement)) {
0609                     return false;
0610                 }
0611             } else if (childElement.tagName() == QLatin1String("sub")) {
0612                 if (!convertSubScript(childElement)) {
0613                     return false;
0614                 }
0615             }
0616         } else if (child.isText()) {
0617             const QDomText childText = child.toText();
0618             mCursor->insertText(childText.data().simplified());
0619         }
0620 
0621         child = child.nextSibling();
0622     }
0623 
0624     return true;
0625 }
0626 
0627 bool Converter::convertEmphasis(const QDomElement &element)
0628 {
0629     QTextCharFormat origFormat = mCursor->charFormat();
0630 
0631     QTextCharFormat italicFormat(origFormat);
0632     italicFormat.setFontItalic(true);
0633     mCursor->setCharFormat(italicFormat);
0634 
0635     if (!convertParagraph(element)) {
0636         return false;
0637     }
0638 
0639     mCursor->setCharFormat(origFormat);
0640 
0641     return true;
0642 }
0643 
0644 bool Converter::convertStrikethrough(const QDomElement &element)
0645 {
0646     QTextCharFormat origFormat = mCursor->charFormat();
0647 
0648     QTextCharFormat strikeoutFormat(origFormat);
0649     strikeoutFormat.setFontStrikeOut(true);
0650     mCursor->setCharFormat(strikeoutFormat);
0651 
0652     if (!convertParagraph(element)) {
0653         return false;
0654     }
0655 
0656     mCursor->setCharFormat(origFormat);
0657 
0658     return true;
0659 }
0660 
0661 bool Converter::convertStrong(const QDomElement &element)
0662 {
0663     QTextCharFormat origFormat = mCursor->charFormat();
0664 
0665     QTextCharFormat boldFormat(origFormat);
0666     boldFormat.setFontWeight(QFont::Bold);
0667     mCursor->setCharFormat(boldFormat);
0668 
0669     if (!convertParagraph(element)) {
0670         return false;
0671     }
0672 
0673     mCursor->setCharFormat(origFormat);
0674 
0675     return true;
0676 }
0677 
0678 bool Converter::convertStyle(const QDomElement &element)
0679 {
0680     if (!convertParagraph(element)) {
0681         return false;
0682     }
0683 
0684     return true;
0685 }
0686 
0687 bool Converter::convertBinary(const QDomElement &element)
0688 {
0689     const QString id = element.attribute(QStringLiteral("id"));
0690 
0691     const QDomText textNode = element.firstChild().toText();
0692     QByteArray data = textNode.data().toLatin1();
0693     data = QByteArray::fromBase64(data);
0694 
0695     mTextDocument->addResource(QTextDocument::ImageResource, QUrl(id), QImage::fromData(data));
0696 
0697     return true;
0698 }
0699 
0700 bool Converter::convertCover(const QDomElement &element)
0701 {
0702     QDomElement child = element.firstChildElement();
0703     while (!child.isNull()) {
0704         if (child.tagName() == QLatin1String("image")) {
0705             if (!convertImage(child)) {
0706                 return false;
0707             }
0708         }
0709 
0710         child = child.nextSiblingElement();
0711     }
0712 
0713     return true;
0714 }
0715 
0716 bool Converter::convertImage(const QDomElement &element)
0717 {
0718     QString href = element.attributeNS(QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("href"));
0719 
0720     if (href.startsWith(QLatin1Char('#'))) {
0721         href = href.mid(1);
0722     }
0723 
0724     const QImage img = qvariant_cast<QImage>(mTextDocument->resource(QTextDocument::ImageResource, QUrl(href)));
0725 
0726     QTextImageFormat format;
0727     format.setName(href);
0728 
0729     if (img.width() > 560) {
0730         format.setWidth(560);
0731     }
0732 
0733     format.setHeight(img.height());
0734 
0735     mCursor->insertImage(format);
0736 
0737     return true;
0738 }
0739 
0740 bool Converter::convertEpigraph(const QDomElement &element)
0741 {
0742     QDomElement child = element.firstChildElement();
0743     while (!child.isNull()) {
0744         if (child.tagName() == QLatin1String("p")) {
0745             QTextBlockFormat format;
0746             format.setTextIndent(10);
0747             mCursor->insertBlock(format);
0748             if (!convertParagraph(child)) {
0749                 return false;
0750             }
0751         } else if (child.tagName() == QLatin1String("poem")) {
0752             if (!convertPoem(child)) {
0753                 return false;
0754             }
0755         } else if (child.tagName() == QLatin1String("cite")) {
0756             if (!convertCite(child)) {
0757                 return false;
0758             }
0759         } else if (child.tagName() == QLatin1String("empty-line")) {
0760             if (!convertEmptyLine(child)) {
0761                 return false;
0762             }
0763         } else if (child.tagName() == QLatin1String("text-author")) {
0764             QTextBlockFormat format;
0765             format.setTextIndent(10);
0766             mCursor->insertBlock(format);
0767             if (!convertParagraph(child)) {
0768                 return false;
0769             }
0770         }
0771 
0772         child = child.nextSiblingElement();
0773     }
0774 
0775     return true;
0776 }
0777 
0778 bool Converter::convertPoem(const QDomElement &element)
0779 {
0780     QDomElement child = element.firstChildElement();
0781     while (!child.isNull()) {
0782         if (child.tagName() == QLatin1String("title")) {
0783             if (!convertTitle(child)) {
0784                 return false;
0785             }
0786         } else if (child.tagName() == QLatin1String("epigraph")) {
0787             if (!convertEpigraph(child)) {
0788                 return false;
0789             }
0790         } else if (child.tagName() == QLatin1String("empty-line")) {
0791             if (!convertEmptyLine(child)) {
0792                 return false;
0793             }
0794         } else if (child.tagName() == QLatin1String("stanza")) {
0795             if (!convertStanza(child)) {
0796                 return false;
0797             }
0798         } else if (child.tagName() == QLatin1String("text-author")) {
0799             QTextBlockFormat format;
0800             format.setTextIndent(10);
0801             mCursor->insertBlock(format);
0802             if (!convertParagraph(child)) {
0803                 return false;
0804             }
0805         }
0806 
0807         child = child.nextSiblingElement();
0808     }
0809 
0810     return true;
0811 }
0812 
0813 bool Converter::convertSubTitle(const QDomElement &element)
0814 {
0815     QTextFrame *topFrame = mCursor->currentFrame();
0816 
0817     QTextFrameFormat frameFormat;
0818     frameFormat.setBorder(1);
0819     frameFormat.setPadding(8);
0820     frameFormat.setBackground(Qt::lightGray);
0821     frameFormat.setTopMargin(16);
0822 
0823     mCursor->insertFrame(frameFormat);
0824 
0825     if (!convertParagraph(element)) {
0826         return false;
0827     }
0828 
0829     mCursor->setPosition(topFrame->lastPosition());
0830 
0831     return true;
0832 }
0833 
0834 bool Converter::convertCite(const QDomElement &element)
0835 {
0836     QDomElement child = element.firstChildElement();
0837     while (!child.isNull()) {
0838         if (child.tagName() == QLatin1String("p")) {
0839             QTextBlockFormat format;
0840             format.setTextIndent(10);
0841             mCursor->insertBlock(format);
0842             if (!convertParagraph(child)) {
0843                 return false;
0844             }
0845         } else if (child.tagName() == QLatin1String("poem")) {
0846             if (!convertParagraph(child)) {
0847                 return false;
0848             }
0849         } else if (child.tagName() == QLatin1String("text-author")) {
0850             QTextBlockFormat format;
0851             format.setTextIndent(10);
0852             mCursor->insertBlock(format);
0853             if (!convertParagraph(child)) {
0854                 return false;
0855             }
0856         } else if (child.tagName() == QLatin1String("empty-line")) {
0857             if (!convertEmptyLine(child)) {
0858                 return false;
0859             }
0860         } else if (child.tagName() == QLatin1String("subtitle")) {
0861             if (!convertSubTitle(child)) {
0862                 return false;
0863             }
0864         } else if (child.tagName() == QLatin1String("table")) {
0865             if (!convertTable(child)) {
0866                 return false;
0867             }
0868         }
0869 
0870         child = child.nextSiblingElement();
0871     }
0872 
0873     return true;
0874 }
0875 
0876 bool Converter::convertEmptyLine(const QDomElement &)
0877 {
0878     mCursor->insertText(QStringLiteral("\n\n"));
0879     return true;
0880 }
0881 
0882 bool Converter::convertLink(const QDomElement &element)
0883 {
0884     QString href = element.attributeNS(QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("href"));
0885     QString type = element.attributeNS(QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("type"));
0886 
0887     if (type == QLatin1String("note")) {
0888         mCursor->insertText(QStringLiteral("["));
0889     }
0890 
0891     int startPosition = mCursor->position();
0892 
0893     QTextCharFormat origFormat(mCursor->charFormat());
0894 
0895     QTextCharFormat format(mCursor->charFormat());
0896     format.setForeground(Qt::blue);
0897     format.setFontUnderline(true);
0898     mCursor->setCharFormat(format);
0899 
0900     QDomNode child = element.firstChild();
0901     while (!child.isNull()) {
0902         if (child.isElement()) {
0903             const QDomElement childElement = child.toElement();
0904             if (childElement.tagName() == QLatin1String("emphasis")) {
0905                 if (!convertEmphasis(childElement)) {
0906                     return false;
0907                 }
0908             } else if (childElement.tagName() == QLatin1String("strong")) {
0909                 if (!convertStrong(childElement)) {
0910                     return false;
0911                 }
0912             } else if (childElement.tagName() == QLatin1String("style")) {
0913                 if (!convertStyle(childElement)) {
0914                     return false;
0915                 }
0916             }
0917         } else if (child.isText()) {
0918             const QDomText text = child.toText();
0919             if (!text.isNull()) {
0920                 mCursor->insertText(text.data());
0921             }
0922         }
0923 
0924         child = child.nextSibling();
0925     }
0926     mCursor->setCharFormat(origFormat);
0927 
0928     int endPosition = mCursor->position();
0929 
0930     if (type == QLatin1String("note")) {
0931         mCursor->insertText(QStringLiteral("]"));
0932     }
0933 
0934     if (href.startsWith(QLatin1Char('#'))) { // local link
0935         mLocalLinks.insert(href.mid(1), QPair<int, int>(startPosition, endPosition));
0936     } else {
0937         // external link
0938         Okular::BrowseAction *action = new Okular::BrowseAction(QUrl(href));
0939         Q_EMIT addAction(action, startPosition, endPosition);
0940     }
0941 
0942     return true;
0943 }
0944 
0945 bool Converter::convertStanza(const QDomElement &element)
0946 {
0947     QDomElement child = element.firstChildElement();
0948     while (!child.isNull()) {
0949         if (child.tagName() == QLatin1String("title")) {
0950             if (!convertTitle(child)) {
0951                 return false;
0952             }
0953         } else if (child.tagName() == QLatin1String("subtitle")) {
0954             if (!convertSubTitle(child)) {
0955                 return false;
0956             }
0957         } else if (child.tagName() == QLatin1String("v")) {
0958             QTextBlockFormat format;
0959             format.setTextIndent(50);
0960             mCursor->insertBlock(format);
0961             if (!convertParagraph(child)) {
0962                 return false;
0963             }
0964         }
0965 
0966         child = child.nextSiblingElement();
0967     }
0968 
0969     return true;
0970 }
0971 
0972 bool Converter::convertCode(const QDomElement &element)
0973 {
0974     QTextCharFormat origFormat = mCursor->charFormat();
0975 
0976     QTextCharFormat codeFormat(origFormat);
0977     codeFormat.setFontFamilies({QStringLiteral("monospace")});
0978     mCursor->setCharFormat(codeFormat);
0979 
0980     if (!convertParagraph(element)) {
0981         return false;
0982     }
0983 
0984     mCursor->setCharFormat(origFormat);
0985 
0986     return true;
0987 }
0988 
0989 bool Converter::convertSuperScript(const QDomElement &element)
0990 {
0991     QTextCharFormat origFormat = mCursor->charFormat();
0992 
0993     QTextCharFormat superScriptFormat(origFormat);
0994     superScriptFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
0995     mCursor->setCharFormat(superScriptFormat);
0996 
0997     if (!convertParagraph(element)) {
0998         return false;
0999     }
1000 
1001     mCursor->setCharFormat(origFormat);
1002 
1003     return true;
1004 }
1005 
1006 bool Converter::convertSubScript(const QDomElement &element)
1007 {
1008     QTextCharFormat origFormat = mCursor->charFormat();
1009 
1010     QTextCharFormat subScriptFormat(origFormat);
1011     subScriptFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
1012     mCursor->setCharFormat(subScriptFormat);
1013 
1014     if (!convertParagraph(element)) {
1015         return false;
1016     }
1017 
1018     mCursor->setCharFormat(origFormat);
1019 
1020     return true;
1021 }
1022 
1023 bool Converter::convertTable(const QDomElement &element)
1024 {
1025     QTextFrame *topFrame = mCursor->currentFrame();
1026 
1027     QTextTable *table = nullptr;
1028 
1029     QDomElement child = element.firstChildElement();
1030     while (!child.isNull()) {
1031         if (child.tagName() == QLatin1String("tr")) {
1032             if (table) {
1033                 table->appendRows(1);
1034             } else {
1035                 QTextTableFormat tableFormat;
1036                 tableFormat.setBorderStyle(QTextFrameFormat::BorderStyle_None);
1037                 table = mCursor->insertTable(1, 1, tableFormat);
1038             }
1039 
1040             if (!convertTableRow(child, *table)) {
1041                 return false;
1042             }
1043         }
1044 
1045         child = child.nextSiblingElement();
1046     }
1047 
1048     mCursor->setPosition(topFrame->lastPosition());
1049 
1050     return true;
1051 }
1052 
1053 bool Converter::convertTableRow(const QDomElement &element, QTextTable &table)
1054 {
1055     QDomElement child = element.firstChildElement();
1056     int column = 0;
1057     while (!child.isNull()) {
1058         if (child.tagName() == QLatin1String("th")) {
1059             if (!convertTableHeaderCell(child, table, column)) {
1060                 return false;
1061             }
1062         } else if (child.tagName() == QLatin1String("td")) {
1063             if (!convertTableCell(child, table, column)) {
1064                 return false;
1065             }
1066         }
1067 
1068         child = child.nextSiblingElement();
1069     }
1070 
1071     return true;
1072 }
1073 
1074 bool Converter::convertTableHeaderCell(const QDomElement &element, QTextTable &table, int &column)
1075 {
1076     QTextCharFormat charFormat;
1077     charFormat.setFontWeight(QFont::Bold);
1078     return convertTableCellHelper(element, table, column, charFormat);
1079 }
1080 
1081 bool Converter::convertTableCell(const QDomElement &element, QTextTable &table, int &column)
1082 {
1083     QTextCharFormat charFormat;
1084     return convertTableCellHelper(element, table, column, charFormat);
1085 }
1086 
1087 bool Converter::convertTableCellHelper(const QDomElement &element, QTextTable &table, int &column, const QTextCharFormat &charFormat)
1088 {
1089     int row = table.rows() - 1;
1090 
1091     int colspan = qMax(element.attribute(QStringLiteral("colspan")).toInt(), 1);
1092     // TODO: rowspan
1093     // int rowspan = qMax(element.attribute(QStringLiteral("rowspan")).toInt(), 1);
1094 
1095     int columnsToAppend = column + colspan - table.columns();
1096     if (columnsToAppend > 0) {
1097         table.appendColumns(columnsToAppend);
1098     }
1099 
1100     table.mergeCells(row, column, 1, colspan);
1101 
1102     int cellCursorPosition = table.cellAt(row, column).firstPosition();
1103     mCursor->setPosition(cellCursorPosition);
1104 
1105     Qt::Alignment alignment;
1106 
1107     QString halign = element.attribute(QStringLiteral("halign"));
1108     if (halign == QStringLiteral("center")) {
1109         alignment |= Qt::AlignHCenter;
1110     } else if (halign == QStringLiteral("right")) {
1111         alignment |= Qt::AlignRight;
1112     } else {
1113         alignment |= Qt::AlignLeft;
1114     }
1115 
1116     QString valign = element.attribute(QStringLiteral("valign"));
1117     if (valign == QStringLiteral("center")) {
1118         alignment |= Qt::AlignVCenter;
1119     } else if (valign == QStringLiteral("bottom")) {
1120         alignment |= Qt::AlignBottom;
1121     } else {
1122         alignment |= Qt::AlignTop;
1123     }
1124 
1125     QTextBlockFormat format;
1126     format.setAlignment(alignment);
1127     mCursor->insertBlock(format, charFormat);
1128 
1129     if (!convertParagraph(element)) {
1130         return false;
1131     }
1132 
1133     column += colspan;
1134     return true;
1135 }