© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB

Открываем fb2-файлы средствами Qt

fb2, html, xml, QXmlStreamReader

В настоящее время fb2 является популярным форматом для хранения книг. Файл fb2 – частный случай xml. Основным элементом его структуры, как и для html, является тэг (управляющие слова). В этой статье я расскажу, как создать простейший просмотрщик fb2-файлов. Проект с исходным текстом можно скачать по ссылке .

Общие сведения

Теги делятся блочные и строчные. Блочные теги группируются в пары из открывающего тега, закрывающего тега между которыми располагается содержимое. Например, абзац текста записывается как

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

Внутрь такой блочной пары можно поместить другие тэги. Строчные тэги используются для объектов, в которые вложить ничего нельзя. Например, указатель на рисунок

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

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

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

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

В первом тэге <?xml> файла содержится техническая информация о формате, его версии и используемой кодировки. Второй тэг <FictionBook> охватывает саму книгу целиком. Как правило, в любой книге есть 2 части: описание <description> и основная часть <body> (как в html). Описание содержит имя автора, название книги, аннотацию, и т.д. Основная часть содержит заголовки <title> (всей книги или отдельных глав), главы/части/разделы <section> и текст <p> (как в html).

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

Кроме того, можно встретить тэги эпиграф <epigraph>, ссылка <a> (как в 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>");

Затем, при обнаружении тэга <binary> нам необходимо сохранить его метку и формат.

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

Обратите внимание, что imgId идентичен атрибуту тэга <image> за исключением отсутствия знака диез (#).

Теперь нам остаётся только поместить содержимое в строку book. Здесь можно использовать различный набор правил. Например, игнорировать описание книги

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

или выделять заголовки цветом, размером и типом шрифта. Остановимся только на рисунках. Для их вставки надо сформировать строку типа

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

где sr.text().toString() содержимое тэга <binary>. Затем следует провести замену в нашей строке-документе метки, соответствующей данному рисунку на данную строку

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-читалки необходимо добавить обработку ссылок, таблиц и некоторых других объектов. Но приведённого материала достаточно извлечения основного содержимого книги.

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
22 июля 2018 г. 20:56
Тарас

C++ - Тест 001. Первая программа и типы данных

  • Результат 73баллов,
  • Очки рейтинга1
22 июля 2018 г. 18:29
Kaptn

C++ - Тест 003. Условия и циклы

  • Результат 100баллов,
  • Очки рейтинга10
22 июля 2018 г. 7:48
Kaptn

C++ - Тест 003. Условия и циклы

  • Результат 64баллов,
  • Очки рейтинга-1
Последние комментарии
18 июля 2018 г. 12:45
plgrm44

Qt/C++ - Урок 050. Логирование событий Qt приложения в текстовый файл

А что мешает сохранить адрес дефолтного обработчика и после вывода в файл вызывать и его?
17 июля 2018 г. 13:34
Arrow

Qt/C++ - Урок 049. QTranslator - динамический перевод мультиязычного приложения на Qt

Работает так: find_package (Qt5LinguistTools)file (GLOB TS_FILES ${SOURCE_DIR}/translations/*.ts)qt5_add_translation (QM_FILES ${TS_FILES})add_custom_target (translations ALL DEPE...
17 июля 2018 г. 9:53
Илья Чичак

Django - Урок 035. Различные шаблоны для рендеринга разных типов контента в поисковой выдаче

тут все упирается в то, что вы хотите дать поисковым роботам. был у меня опыт проектирования страницы для роботов - сделал точки входа - со статикой для роботов и АПИ для JS клиента=) а отлавл...
17 июля 2018 г. 9:43
Евгений Легоцкой

Django - Урок 035. Различные шаблоны для рендеринга разных типов контента в поисковой выдаче

Думаю, что это всё равно стоит оставить для индексирующих роботов поисковых систем, которые испоьлзуют простые GET запросы. Они же AJAX не используют. Так что полностью уйти от этого не получи...
Сейчас обсуждают на форуме
23 июля 2018 г. 11:24
Arrow

QComboBox делегат для QTableView

И можно еще один маленький вопрос: Как изменить значение в ячейке QTableView?
23 июля 2018 г. 8:56
Arrow

Получение прав пользователей на таблицу базы данных

Да, только самый основной прикол в том что для возможности редактирования таблицы пользователю одних прав на таблицу мало, нужны еще и на последовательность: GRANT USAGE O...
23 июля 2018 г. 8:52
Евгений Легоцкой

Перестал работать Qt Maintenance Tool

В настройках есть параметры прокси, возможно, через них сможете завести, через какой-нибудь прокси сервер. По поводу списка репозиториев. сам не в курсе. Спросил на официальном форум...
23 июля 2018 г. 7:59
Евгений Легоцкой

Qt Android

Так, по поводу ошибок, там в main.cpp ещё одна ошибка у вас Надо так написать QQmlApplicationEngine engine;engine.load(QUrl(QStringLiteral("qrc:/main.qml")));DBase myClas...
23 июля 2018 г. 6:44
Евгений Легоцкой

как проверить состояние у динамически созданного CheckBox в qml

Тогда можно расширить эту модель, наследоваться от неё. И добавить в качестве роли ещё и состояние чекбокса, для выбора. Также можно переопределить метод setData, чтобы можно было ус...

Рекомендуемые страницы