Дмитрий
ДмитрийMay 18, 2019, 3:29 p.m.

Fb3 file reader on Qt Creator

Some time ago, I published a fb2 reader project, in the process of working on which I found out that this format is obsolete. Therefore, I began to master new formats and settled on fb3, the fb2 receiver format. In the course of work, I encountered some problems that have not been fully resolved. In addition, despite the fact that books are already appearing in fb3, the format has not been finally approved. Although books already appear. Therefore, I decided to publish the program in its current form and talk about my achievements and failures. Subsequently, I plan a new version with support for the epub format.


fb3

The fb3 file is a zip container that contains book elements. Therefore, I implemented the following algorithm. Unpack all files into a temporary folder and read all the necessary elements from it.

if(name.endsWith(".fb3"))
{
    QString s = thisName;
    QString nameUn = QStandardPaths::standardLocations(QStandardPaths::TempLocation).at(0)
                    + "/dmreader/" + s;

    if(!openerTextFiles::UnZip(name, nameUn)) // распаковка архива
        qDebug() << "файл " << name << " не открыт";
    else
    {
        // извлекаем содержимое
        openerTextFiles::openFBFile(nameUn + "/fb3/body.xml", &text, &content); 
        // помещаем содержимое в окно textBrowser
        ui->textBrowser->setText(text);
        ui->textBrowser->verticalScrollBar()->setValue(0);
        ui->comboBoxContent->insertItems(0, content);
        ui->comboBoxContent->setCurrentIndex(-1);
        this->setWindowTitle(content.first());
    }
}

The sample file contains:

The fb3 folder contains basic information, in particular, two key body.xml elements - the text of the book and description.xml - the annotation of the book, which correspond to the main blocks of the fb2 file. The img folder contains the book's illustrations.
The body.xml structure after some amendments to the fb2 structure. The amendments are aimed at expanding functionality and simplifying the structure. Added tags ol, ul, li to create lists. blockquote - quote, em - text accents (italics), pre - block of pre-formatted text identical to html. The underline (underlined) and spacing (sparse) tags can be implemented with the following substitutions:

<span style=\"text-decoration:underline;\">
<span style=\"letter-spacing:5px;\">

Also, the notes system has been slightly modified, I will not dwell on this.
Significantly changed the system of illustrations. In text with tag there are links to the numbers of the pictures. To display images, you must first read the links to them from the file "/_rels/body.xml.rels". To do this, I create an array img_fb3 of type QHash , where the first line is a link to the picture in the text, the second is the path to the picture in the img directory.
The img tag is processed like this

if( sr.name().toString() == "img" ) // расположение рисунков fb3
{
    if(sr.attributes().count() > 0)
    {
        if( sr.attributes().at(0).name().toString() == "src" )
            book->append("<p align=\"center\"><img src=\""
                         + filerels + "/"
                         + img_fb3.take( sr.attributes().at(0).value().toString() )
                         + "\" alt=\"рисунок\"" + "/></p>");   
        else
            qDebug() << "img src ошибка";
    }
    break;
}

img_fb3 is filled like this

if(sr.name().toString() == "fb3-body") // ссылки на картинки fb3
{
    QFile fr(filerels + "/_rels/body.xml.rels" );
    if (!fr.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        qDebug() << "файл body.xml.rels не открыт";
        break;
    }

    QXmlStreamReader srr(&fr);
    while( !srr.atEnd() )
    {
        switch( srr.readNext() )
        {
        case QXmlStreamReader::StartElement:
            if(srr.name().toString() == "Relationship")
            {
                QString f, s;
                for(int i = 0; i < srr.attributes().count(); i++)
                {
                    if( srr.attributes().at(i).name() == "Id" )
                        s = srr.attributes().at(i).value().toString();
                    if( srr.attributes().at(i).name() == "Target" )
                        f = srr.attributes().at(i).value().toString();
                }
                if(f.indexOf("cover") != -1)
                {
                    book->append("<p align=\"center\"><img src=\""
                                 + filerels + "/" + f
                                 + "\" alt=\"рисунок\"" + "/></p>");
                }
                else
                    img_fb3.insert(s, f);
                //qDebug() << f;
            }
            break;
        default: ;
        }
    }
    fr.close();
    break;
}

In addition, there are some tags whose purpose is not completely clear to me: , .

Open zip-archive

Initially, I planned to use the 7z program for unzipping. But this option disappeared when I found out that you can implement file unpacking using the zlib library and interfaces from the zipreader_p.h, zipwriter_p.h and zip.cpp libraries that you can download along with other Qt sources. In the end, I found out that these libraries are also included in the Qt redistributables (gui-private module). By the way, each main module has such a private twin module. And as the warning in each of the private header files says:
(This file is not part of the Qt API. It exists purely as an implementation detail. This header file may change from version to version without notice, or even be removed).
So, the UnZip(QString name, QString path) function puts the contents of the name archive into the path folder. As temporary storage, I suggest using the "dmreader" folder created in the directory for storing temporary files QStandardPaths::standardLocations(QStandardPaths::TempLocation).at(0).
Implementation of archive unpacking

bool openerTextFiles::UnZip(QString zfile, QString path)
{
    QZipReader cZip(zfile);
    QDir dir(path);
    if(!dir.exists())
        dir.mkpath( path );

    //bool b = cZip.extractAll( path );
    bool b = extractFiles( cZip , path );
    cZip.close();
    return b;
}

If we were dealing with a zip archive, then it would be enough to use the extractAll(QString path) function of the QZipReader class. However, the fb3 archive has one feature that I will not dwell on here. I added this algorithm on my knee in the extractFiles(QZipReader zip ,QString path) function.

bool openerTextFiles::extractFiles(const QZipReader &zip, const QString &destinationDir)
{
    QDir baseDir(destinationDir);
    QVector<QZipReader::FileInfo> allFiles = zip.fileInfoList();
    // create directories first
    foreach (QZipReader::FileInfo fi, allFiles) {
        const QString absPath = destinationDir + QDir::separator() + fi.filePath;
        if (fi.isDir) {
            if (!baseDir.mkpath(absPath))
                return false;
            if (!QFile::setPermissions(absPath, fi.permissions))
                return false;
        }
    }
    //  ------------------------
    foreach (QZipReader::FileInfo fi, allFiles) {
        const QString absPath = destinationDir + "/" + fi.filePath;
        QString d; // для fb3
        if(absPath.indexOf("/") != -1)
        {
            d = absPath.left(absPath.lastIndexOf("/"));
            QDir dir(d);
            if(!dir.exists()){
                dir.mkdir(dir.path());
            }
        }
    }
    foreach (QZipReader::FileInfo fi, allFiles) {
        const QString absPath = destinationDir + "/" + fi.filePath;
        if(absPath.endsWith("/"))
        {
            if( !baseDir.exists(fi.filePath) )
            {
                if (!baseDir.mkdir(fi.filePath))
                    return false;
                if (!QFile::setPermissions(absPath, fi.permissions))
                    return false;
            }
        }
        else
        {
            QString d; // для fb3
            if(absPath.indexOf("/") != -1)
            {
                d = absPath.left(absPath.lastIndexOf("/"));
                QDir dir(d);
                if(!dir.exists()){
                    dir.mkdir(dir.path());
                }
            }
            QFile f(absPath);
            if (!f.open(QIODevice::WriteOnly))
                return false;
            f.write(zip.fileData(fi.filePath));
            f.setPermissions(fi.permissions);
            f.close();
        }
    }
    return true;
    //  ------------------------
    // set up symlinks
    foreach (QZipReader::FileInfo fi, allFiles) {
        const QString absPath = destinationDir + QDir::separator() + fi.filePath;
        if (fi.isSymLink) {
            QString destination = QFile::decodeName(zip.fileData(fi.filePath));
            if (destination.isEmpty())
                return false;
            QFileInfo linkFi(absPath);
            if (!QFile::exists(linkFi.absolutePath()))
                QDir::root().mkpath(linkFi.absolutePath());
            if (!QFile::link(destination, absPath))
                return false;
        }
    }
}

The source code of the program can be downloaded here .

Conclusion

The written program does not claim to be fully functional when reading fb3 files. But in order to open the text, view it and save it in html, which can be processed in a large number of editors, it is great. Enjoy reading.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

Evgenii Legotckoi
  • May 20, 2019, 3:20 p.m.

Добрый день!

Вы не думали разместить репозиторий проекта на GitHub?

Дмитрий
  • May 21, 2019, 4:10 p.m.

Приветствую!

Я думаю дойдёт и до этого, но пока изучать его у меня нет желания.

A
  • Oct. 19, 2024, 8:19 a.m.

Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html

Comments

Only authorized users can post comments.
Please, Log in or Sign up
AD

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:20points,
  • Rating points-10
Last comments
Evgenii Legotckoi
Evgenii LegotckoiOct. 31, 2024, 2:37 p.m.
Django - Lesson 064. How to write a Python Markdown extension Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEOct. 19, 2024, 8:19 a.m.
Fb3 file reader on Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовOct. 5, 2024, 7:51 a.m.
Django - Lesson 064. How to write a Python Markdown extension Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5July 5, 2024, 11:02 a.m.
QML - Lesson 016. SQLite database and the working with it in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrFeb. 8, 2024, 6:43 p.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiJune 24, 2024, 3:11 p.m.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Nov. 15, 2024, 6:04 a.m.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectJune 4, 2022, 3:49 a.m.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9AnonimOct. 25, 2024, 9:10 a.m.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Follow us in social networks