Дмитрий
Дмитрий29 серпня 2017 р. 15:26

Відкриваємо fb2-файли засобами Qt

В даний час fb2 є популярним форматом для зберігання книг. Файл fb2 - окремий випадок xml. Основним елементом його структури, як і для html, є тег (керуючі слова). У цій статті я розповім, як створити найпростіший переглядач fb2-файлів. Проект з вихідним текстом можна скачати по посиланням .

Загальні відомості

Теги діляться блокові і малі. Блокові теги групуються в пари з відкриваючого тега, який закриває тега між якими розташовується вміст. Наприклад, абзац тексту записується як

<p>Абзац текст</p>

Всередину такої блокової пари можна помістити інші теги. Рядкові теги використовуються для об'єктів, в які вкласти нічого не можна. Наприклад, покажчик на рисунок

<image l:href = “#_0.jpg”/>

містить інформацію: 1) про те, що в дану точку документа потрібно вставити малюнок, 2) посилання на цей малюнок. Нижче розібраний алгоритм вставки малюнка в текст. Розрізнити 3 типи тегів просто за допомогою слеша. У сатиричного тега слеш перед закривається дужкою, у який закриває блочного після відкривається, у відкриває блочного він відсутній.

Якщо хочете повністю розібратися, вивчайте html. Між html і fb2 є деяка різниця, хоча багато в чому вони ідентичні. На такі елементи я буду вказувати по ходу розповіді. Також зазначу, що xml на відміну від html не використовує мову CSS, в нашому випадку це означає те, що в fb2 файлі немає вказівок на те, як відформатований текст (розмір і колір шрифту, розташування абзаців і т.п.). Все це ми повинні (при бажанні) реалізувати самостійно.

Структура fb2-файлу

У першому тезі <? Xml > файлу міститься технічна інформація про формат, його версії і використовуваного кодування. Другий тег охоплює саму книгу цілком. Як правило, в будь-якій книзі є 2 частини: опис і основна частина (як в html). Опис містить ім'я автора, назва книги, анотацію, і т.д. Основна частина містить заголовки (всієї книги або окремих глав), глави / частини / розділи <section> і текст <p > (як в html). </section>

<?xml …>
  <FictionBook …>
    <description>
    …
    </description>
    <body>
    …
    </body>
  …
  </FictionBook>

Крім того, можна зустріти теги епіграф , посилання (як в html), малюнок <image / > і порожній рядок <empty-line / > (в html <br/>). Посилання можуть бути зовнішні і внутрішні. Зовнішні посилання в якості параметра містять URL джерела, внутрішні - містять посилання на елементи в тексті файлу (див. Вище тег image). Малюнки містять аналогічні внутрішні посилання.

Після розділу <body > можуть розташовуватися додаткові елементи. Так в окремих тегах <binary > розміщуються малюнки, перетворені до текстовій формі.

Створення програми-читалки

Нашу програму побудуємо таким чином: будемо зчитувати дані з файлу і перетворювати їх в html, потім сформований рядок направимо в текстове поле за допомогою функції setHtml (QString). Один маленький лайфхак для тих, хто хоче вивчити html: об'єкт класу QTextEdit / QTextBrowser може відобразити форматований документ у вигляді вихідного тексту. Для цього відкриваємо редактор форм, клацаємо по об'єкту 2 рази і переключиться на закладку «Исходник».

Для обробки fb2-файлів будемо використовувати клас QXmlStreamReader. Для роботи з ним необхідно підключити до проекту модулі xml і xmlpatterns. Як аргумент йому необхідно зрадити покажчик на об'єкт класу QFile.

QFile f(name);
QXmlStreamReader sr(&f);

Відкриття самого файлу виглядає як цикл з послідовним зчитуванням рядків. Ще нам знадобляться 3 змінні

QString book;
QString imgId;
QString imgType;

book потрібна для зберігання сформованого документа, imgId і imgType для вставки малюнків у текст. Клас QXmlStreamReader виробляє кілька важливих дій. По-перше, визначає і встановлює потрібний декодер. По-друге, відокремлює теги від вмісту. По-третє, виділяє властивості тегів. Нам залишається тільки обробляти розділені дані. Для зчитування даних використовується функція readNext (). Всі фрагменти, прочитані їй належать до одного з 5 типів: StartDocument, EndDocument, StartElement, EndElement і Characters. З них 2 перших для визначення початку і кінця файлу, 2 наступних для зчитування тегів і останній отримання заповнювача.

Отримавши StartDocument нам необхідно додати заголовну рядок html документа і 2 відкривають тега

book = "<!DOCTYPE HTML><html><body style=\"font-size:14px\">";

При досягненні EndDocument ми закриваємо теги, відкриті на початку файлу

book.append("</body></html>");

Поява StartElement значить, що прочитаний відкривається або рядковий тег. Відповідно EndElement сигналізує про зчитуванні закривається тега. Ім'я тега визначається викликом функції sr.name (). ToString (). Для контролю структури документа будемо зберігати список всіх відкритих тегів в об'єкті thisToken класу QStringList. Тому в разі StartElement додає до thisToken ім'я поточного тега і видаляти його в разі EndElement. Крім того, відкриваються (або рядкові) теги можуть містити атрибути. Атрибуту будуть прочитані і збережені в sr як масив рядків. Доступ до них можна отримати за допомогою методу sr.attributes (). Нам вони знадобляться для додавання малюнків в текст. Так при виявленні тега image необхідно додати в текст мітку цього малюнка.

book.append("<p align=\"center\">"+sr.attributes().at(0).value().toString()+"</p>");

Потім, при виявленні тега нам необхідно зберегти його мітку і формат.

imgId = sr.attributes().at(0).value().toString();
imgType = sr.attributes().at(1).value().toString();

Зверніть увагу, що imgId ідентичний атрибуту тега за винятком відсутності знака дієз (#).

Тепер нам залишається тільки помістити вміст в рядок book. Тут можна використовувати різний набір правил. Наприклад, ігнорувати опис книги

if(thisToken.contains("description"))
{
    break; // не выводим
}

або виділяти заголовки кольором, розміром і типом шрифту. Зупинимося лише на малюнках. Для їх вставки треба сформувати рядок типу

QString image = "<img src=\"data:"
        + imgType +";base64,"
        + sr.text().toString()
        + "\"/>";

де sr.text (). toString () вміст тега . Потім слід провести заміну в нашій рядку-документі мітки, яка відповідає цьому малюнку на даний рядок

book.replace("#"+imgId, image);

Алгоритм читання fb2-файлу

    while( !sr.atEnd() )
    {
        switch( sr.readNext() )
        {
        case QXmlStreamReader::NoToken:
            qDebug() << "QXmlStreamReader::NoToken";
            break;
        case QXmlStreamReader::StartDocument:
            book = "<!DOCTYPE HTML><html><body style=\"font-size:14px\">";
            break;
        case QXmlStreamReader::EndDocument:
            book.append("</body></html>");
            break;
        case QXmlStreamReader::StartElement:
            thisToken.append( sr.name().toString() );
            if( sr.name().toString() == "image" ) // расположение рисунков
            {
                if(sr.attributes().count() > 0)
                    book.append("<p align=\"center\">"+sr.attributes().at(0).value().toString()+"</p>");
            }
            if(sr.name() == "binary") // хранилище рисунков
            {
                imgId = sr.attributes().at(0).value().toString();
                imgType = sr.attributes().at(1).value().toString();
            }
            break;
        case QXmlStreamReader::EndElement:
            if( thisToken.last() == sr.name().toString() )
                thisToken.removeLast();
            else
                qDebug() << "error token";
            break;
        case QXmlStreamReader::Characters:
            if( sr.text().toString().contains( QRegExp("[A-Z]|[a-z]|[А-Я]|[а-я]") )) // если есть текст в блоке
            {
                if(thisToken.contains("description")) // ОПИСАНИЕ КНИГИ
                {
                    break; // не выводим
                }
                if(thisToken.contains("div"))
                    break;
                if(!thisToken.contains( "binary" ))
                    book.append("<p>" + sr.text().toString() + "</p>");
            }
            if(thisToken.contains( "binary" ) )//для рисунков
            {
                QString image = "<img src=\"data:"
                        + imgType +";base64,"
                        + sr.text().toString()
                        + "\"/>";
                book.replace("#"+imgId, image);
            }
            break;
        }
    }

Наш документ готовий. Залишилося тільки встановити сформований рядок в текстове поле

ui->textBrowser->setHtml(book);

Для повноцінної роботи fb2-читалки необхідно додати обробку посилань, таблиць та деяких інших об'єктів. Але наведеного матеріалу достатньо вилучення основного вмісту книги.

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

Коментарі

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

Qt - Тест 001. Сигналы и слоты

  • Результат:84бали,
  • Рейтинг балів4
Ua

Qt - Тест 001. Сигналы и слоты

  • Результат:42бали,
  • Рейтинг балів-8
ОК

Qt - Тест 001. Сигналы и слоты

  • Результат:47бали,
  • Рейтинг балів-6
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Дмитрий
Дмитрий03 лютого 2025 р. 06:24
Создание deb-пакета. Как создать ярлык на рабочем столе после установки собственного deb-пакета? Всем привет. Сделал свой deb-пакет с программой. Всё устанавливается и работает. Ставлю по пути /usr/bin/my_application. Как для пользователя при установке пакета сразу создать ярлык на раб…
NW
Nayo Wai30 січня 2025 р. 09:22
не запускается компьютер!!! Не запускается компьютер (точнее работает блок , но сам монитор вообще жесть)В общем я ничего с интернета не скачивала в последнее время. На компе никаких левых пр…
n
nkly03 січня 2025 р. 02:52
Нужно запретить перемещение только некоторых итемов, остальные перемещать можно. Вопрос решен. Узнать QModelIndex элемента на который мы перетаскиваем другой элемент, можно с помощью функции indexAt(event->position().toPoint()) представления QTreeViev вызываемой в переопр…
M
Marsel16 серпня 2023 р. 14:26
OAuth2.0 через VK, получение email Спасибо большое за помощь и простите за то что отнял время своей невнимательностью.
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.

Слідкуйте за нами в соціальних мережах