- 1. fb3
- 2. Open zip-archive
- 3. Conclusion
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
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.
Добрый день!
Вы не думали разместить репозиторий проекта на GitHub?
Приветствую!
Я думаю дойдёт и до этого, но пока изучать его у меня нет желания.
Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html