Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Возможности тулкита GTK+ и сопутствующих библиотек


Автор: А.Панин
Дата публикации: 3 декабря 2014 г.

Простой механизм разбора документов формата XML

Предыдущая статья была посвящена механизму для работы с файлами, содержащими пары ключ-значение, который отлично подходит для сохранения данных конфигурации приложений. Но в некоторых случаях упомянутый механизм может оказаться недостаточно мощным или же может просто не подходить ввиду того, что данные сохраняются сторонним приложением в отличном стандартизированном формате. Таким форматом с высокой вероятностью может оказаться формат XML, поэтому полезно иметь возможность задействования механизма для разбора документов формата XML, который также реализован в рамках библиотеки GLib. Следует отметить, однако, что данный механизм имеет ряд ограничений, поэтому должен использоваться для разбора документов формата XML, сгенерированных либо самим приложением, либо сторонними приложениями в случае выполнения следующих условий:
  • При генерации документа формата XML может использоваться исключительно кодировка UTF-8
  • В генерируемом документе не могут использоваться объявленные пользователем сущности (entities)
  • Описание типа документа, инструкции обработки документа и комментарии могут использоваться при генерации документа, но они не будут обрабатываться каким-либо образом, а будут просто "пропускаться"
  • При разборе документа не будет осуществляться проверка его корректности, поэтому объявление типа документа может не использоваться
При этом в рамках документа в соответствии со спецификацией формата XML могут использоваться:
  • Элементы
  • Атрибуты
  • Стандартные сущности: &, &alt;, >, ", '
  • Ссылки на символы
  • Секции, отмеченные, как CDATA

Следует учитывать тот факт, что описываемый механизм может обрабатывать некоторые документы, которые не принимаются полнофункциональными системами для разбора документов формата XML, однако, в случае нарушения форматирования документа он будет прекращать его разбор и сообщать об ошибке.

1. Разбор и сохранение документов

В первую очередь следует обратить внимание на тот факт, что при работе с документами формата XML в случае использования библиотеки GLib не будет предоставляться каких-либо функций для автоматического формирования текстового представления результирующего файла (как это было в случае с объектом типа GKeyFile, предоставляющим возможность работы с файлами, содержащими пары ключ-значение). Исходя из этого, для сохранения данных конфигурации приложения в форме документа формата XML в коде приложения придется объявлять переменные, в которых будут храниться значения параметров конфигурации приложения. Кроме того, придется самостоятельно формировать текстовое представление документа формата XML и сохранять его в файл на диске. Перед разбором документа следует загрузить данные из соответствующего файла в память, так как механизм разбора документов формата XML из состава библиотеки GLib принимает исключительно адрес строкового представления файла, расположенного в памяти. При осуществлении загрузки содержимого файла в память может использоваться либо упомянутая в предыдущей статье функция g_file_get_contents() для загрузки содержимого файла в резервируемый фрагмент памяти, либо функция g_mapped_file_new() совместно с функцией g_mapped_file_get_contents() для отображения файла в память. После этого следует объявить структуру типа GMarkupParser.

Данная структура содержит указатели на функции обратного вызова, которые будут задействованы в ходе разбора документа формата XML. Если для разбора документа не требуется реализации каких-либо функций обратного вызова, в качестве значения указателя должно использоваться значение NULL. В любом случае для корректного разбора документа формата XML придется реализовать как минимум две функции обратного вызова, поэтому упомянутые функции заслуживают внимания:

  1. Функция начала разбора элемента
    void start_element(GMarkupParseContext *context, 
                       const gchar         *element_name, 
                       const gchar        **attribute_names, 
                       const gchar        **attribute_values, 
                       gpointer             user_data, 
                       GError             **error);	
    

    Данная функция вызывается тогда, когда осуществляется разбор открывающихся тэгов документа (например, <foo bar="baz">), поэтому в рамках нее обычно устанавливается идентификатор элемента, который может быть определен на основе имени элемента, передаваемого с помощью аргумента element_name (в случае работы с рассматриваемым примером тэга с помощью аргумента element_name будет передана строка "foo"). Также с помощью данной функции передаются имена и значения атрибутов, которые хранятся в массивах строк attribute_names и attribute_values соответственно (при работе с рассматриваемым примером тэга массив attribute_names будет содержать значения "bar" и NULL, а массив attribute_values - значения "baz" и NULL). С помощью аргумента user_data в функцию передаются пользовательские данные. Аргумент error может быть задействован для передачи указания на ошибку из данной функции в функцию сообщения об ошибке, причем в качестве идентификаторов ошибок могут использоваться идентификаторы G_MARKUP_ERROR_UNKNOWN_ELEMENT (для указания на неизвестный элемент), G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE (для указания на неизвестный атрибут) и G_MARKUP_ERROR_INVALID_CONTENT (для указания на некорректные данные). Первым же аргументом является указатель на объект контекста разбора документа типа GMarkupContext, средствами которого осуществляется разбор документа. Реализация данной функции необходима для корректного разбора документа формата XML.

  2. Функция окончания разбора элемента
    void end_element(GMarkupParseContext *context, 
                     const gchar         *element_name, 
                     gpointer             user_data, 
                     GError             **error);
    

    Данная функция вызывается тогда, когда осуществляется разбор закрывающихся тэгов документа (например, </foo>), поэтому в рамках нее может сбрасываться идентификатор разбираемого элемента. Кроме того, данная функция может вызываться при разборе пустых тегов, например, <foo/>. Как и в предыдущей функции, с помощью аргумента element_name в функцию передается имя элемента (при работе с рассматриваемым примером тэга с помощью аргумента element_name будет передана строка "foo"). Все остальные аргументы используются для передачи тех же данных, что и в предыдущей функции.

  3. Функция разбора текста
    void text(GMarkupParseContext *context, 
              const gchar         *text, 
              gsize                text_len, 
              gpointer             user_data, 
              GError             **error);
    

    Данная функция вызывается каждый раз при разборе текста, расположенного между открывающимися и закрывающимися тэгами. Указатель на адрес начала фрагмента текста передается с помощью аргумента text, при этом сам текст не копируется в автоматически резервируемый фрагмент памяти, а также не завершается нулевым символом. По этим причинам с помощью аргумента text_len передается значение длины извлеченного фрагмента текста. Забегая вперед, следует отметить, что при разборе текста не происходит обратного преобразования стандартных сущностей, поэтому данное преобразование должно выполняться на уровне приложения. Реализация данной функции необходима для корректного разбора документа формата XML.

  4. Функция пропуска текста
    void passthrough(GMarkupParseContext *context, 
                     const gchar         *passthrough_text, 
                     gsize                text_len, 
                     gpointer             user_data, 
                     GError             **error);
    

    Данная функция работает аналогично функции разбора текста, но вызывается только тогда, когда текст должен быть сохранен без изменений и не может быть интерпретирован каким-либо образом. В частности, данная функция вызывается при разборе объявления типа документа, инструкций обработки документа и комментариев.

  5. Функция обработки сообщения об ошибке
    void error(GMarkupParseContext *context, 
               GError              *error, 
               gpointer             user_data);
    

    Данная функция вызывается в случае обнаружения ошибки при разборе документа формата XML, причем ошибка может выявляться в рамках одной из описанных выше функций. Память, использованная для хранения данных ошибки не должна освобождаться вручную ни при каких обстоятельствах.

После объявления структуры типа GMarkupParser и присвоения значений адресов функций обратного вызова соответствующим указателям из этой структуры может быть создан объект контекста разбора документа XML типа GMarkupParseContext. Следует помнить о том, что для разбора некоторых документов может быть задействовано несколько наборов функций обратного вызова, но обычно используется единственный набор. Объект контекста разбора документа формата XML создается с помощью функции g_markup_parse_context_new():
GMarkupParseContext * g_markup_parse_context_new(const GMarkupParser *parser, 
                                                 GMarkupParseFlags flags, 
                                                 gpointer user_data, 
                                                 GDestroyNotify user_data_dnotify);

В качестве первого аргумента функции передается указатель на объявленную ранее структуру типа GMarkupParser с указателями на реализации функций обратного вызова, используемые для непосредственного доступа к данным документа. С помощью второго аргумента может быть передана бытовая маска из флагов операции разбора документа. Наиболее важными флагами операции разбора документа являются флаги G_MARKUP_TREAT_CDATA_AS_TEXT (при использовании данного флага данные из секций CDATA будут передаваться в функцию разбора текста вместо функции пропуска текста) и G_MARKUP_PREFIX_ERROR_POSITION (при использовании данного флага в случае самостоятельной передачи данных ошибок из первых четырех функций обратного вызова к описаниям ошибок будет автоматически добавляться информация о текущей позиции в документе точно так же, как это делается в случае обнаружения ошибки форматирования документа). С помощью аргумента user_data в описанные функции обратного вызова передаются произвольные пользовательские данные, а с помощью аргумента user_data_dnotify - указатель на функцию, которая будет вызвана перед уничтожением объекта контекста разбора документа формата XML и может использоваться для освобождения памяти, зарезервированной для хранения пользовательских данных.

Теперь все готово для непосредственного разбора документа. Разбор документа осуществляется с помощью функции g_markup_parse_context_parse():
gboolean g_markup_parse_context_parse(GMarkupParseContext *context, 
                                      const gchar *text, 
                                      gssize text_len, 
                                      GError **error);

С помощью первого аргумента context функции передается указатель на объект контекста разбора документа формата XML. С помощью аргументов text и text_len передаются указатель на строковое представление документа, расположенное в памяти, и длина этого строкового представления соответственно. При этом совсем не обязательно сразу же передавать полное строковое представление документа. Вместо этого вы можете повторно вызывать данную функцию, передавая новые фрагменты документа, что полезно, к примеру, при передаче данных документа по сети. Для строкового представления документа в любом случае должна использоваться кодировка UTF-8. В случае несоблюдения этого условия выполнение функции завершится неудачей, а с помощью аргумента error будет возвращена информация об ошибке. После возникновения ошибки контекст разбора документа формата XML не может использоваться и должен быть уничтожен.

В ходе разбора документа наборы функций обратного вызова могут заменяться с помощью функций g_markup_parse_context_push() и g_markup_parse_context_pop(). Функция g_markup_parse_context_push() может вызываться из функции начала разбора элемента при разборе определенного открывающегося тэга и позволяет задействовать для разбора документа измененный набор функций обратного вызова, сформированный в рамках структуры типа GMarkupParser, причем эта функция должна использоваться совместно с функцией g_markup_parse_context_pop(), которая должна вызываться из функции окончания разбора документа в момент разбора соответствующего закрывающегося тэга за исключением тех случаев, когда разбор документа прекращается ввиду ошибки:
void g_markup_parse_context_push(GMarkupParseContext *context, 
                                 const GMarkupParser *parser, 
                                 gpointer user_data);
gpointer g_markup_parse_context_pop(GMarkupParseContext *context);
В ходе разбора документа также могут использоваться вспомогательные функции для получения параметров объекта контекста разбора документа формата XML. Функция g_markup_parse_context_get_position() может использоваться в функциях обработки сообщений об ошибках для получения текущей позиции в документе:
void g_markup_parse_context_get_position(GMarkupParseContext *context, 
                                         gint *line_number, 
                                         gint *char_number);
С помощью параметров line_number и char_number возвращаются текущие номера строк и символов соответственно. Функция g_markup_parse_context_get_element() может использоваться для получения имени открытого в текущий момент элемента в функциях начала и окончания разбора элемента:
const gchar *g_markup_parse_context_get_element(GMarkupParseContext *context);
Функция g_markup_parse_context_get_element_stack() может использоваться а рамках функций начала и окончания разбора элементов для получения связанного списка, содержащего имена элементов, причем первым именем будет имя открытого в текущий момент тэга, а вторым - имя родительского тэга:
const GSList *g_markup_parse_context_get_element_stack(GMarkupParseContext *context);
Функция g_markup_parse_context_get_user_data() может использоваться для получения пользовательских данных, переданных при вызове функции g_markup_parse_context_new() или при последнем вызове функции g_markup_parse_context_push():
gpointer g_markup_parse_context_get_user_data(GMarkupParseContext *context);
Функция g_markup_parse_context_end_parse() используется для сообщения объекту контекста разбора документа формата XML о том, что работа с документом заканчивается. В том случае, если документ был передан не в полном объеме, выполнение данной функции завершится неудачей с возвратом данных об ошибке посредством аргумента error:
gboolean g_markup_parse_context_end_parse(GMarkupParseContext *context, 
                                          GError **error); 
Объекты контекста разбора документов формата XML используют счетчики ссылок, поэтому разработчику предоставляется возможность уменьшения и увеличения значений счетчика ссылок объекта с помощью функций g_markup_parse_context_ref() и g_markup_parse_context_unref() соответственно:
GMarkupParseContext *g_markup_parse_context_ref(GMarkupParseContext *context);
void g_markup_parse_context_unref(GMarkupParseContext *context); 
При достижении счетчиком ссылок значения 0 объект контекста разбора документа формата XML будет уничтожен. Для явного уничтожения объекта разбора документа формата XML может использоваться функция g_markup_parse_context_free():
void g_markup_parse_context_free(GMarkupParseContext *context);

Следует помнить о том, что при разборе документов формата XML в функции обратного вызова будут передаваться исключительно фрагменты строкового представления этого документа, поэтому при необходимости извлечения значений определенных типов придется прибегнуть к вспомогательным функциям (таким, как функция atoi() для получения целочисленных значений), а при извлечении строковых значений придется самостоятельно осуществлять замену стандартных сущностей на соответствующие им символы (вариант реализации функции, выполняющей данную операцию, содержится в примере ниже).

При сохранении текстового представления документа формата XML на диск на этапе формирования текстового представления могут быть задействованы функции g_markup_escape_text(), g_markup_printf_escaped() и g_markup_vprintf_escaped(). Функция g_markup_escape_text() просто осуществляет замену специальных символов на стандартные сущности, а также замену управляющих символов за исключением символов табуляции, переноса строки и возврата каретки на соответствующие ссылки из диапазона &1; ... &1f; в переданной с помощью аргумента text строке длиной в length байт (в качестве значения длины строки может использоваться значение -1 при условии использования завершающего нулевого символа):
gchar *g_markup_escape_text(const gchar *text, 
                            gssize length);

Возвращаемая строка сохраняется в специально зарезервированном фрагменте памяти, который впоследствии должен быть освобожден. Функции g_markup_printf_escaped() и g_markup_vprintf_escaped() предназначены для выполнения аналогичной задачи, но отличаются от рассмотренной функции тем, что принимают строку форматирования с аргументами в стиле функции printf() и различное число аргументов соответственно. Сохранение результирующего текстового представления на диск может осуществляться с помощью упомянутой в предыдущей статье функции g_file_set_contents().


Продолжение статьи : 2. Пример использования.