antonio's blog

antonio's blog


Блог о всяком разном, связанном с разработкой ПО. Пишу редко, когда есть время и желание.

Anton Dobkin
Author

Share


Tags


Обработка ошибок в LibXML2

Anton DobkinAnton Dobkin

В библиотеке libxml2 есть несколько функций с помощью которых можно задать пользовательские callback-функции для обработки общих ошибок. Эти функции будут вызваны библиотекой при обнаружении ошибки во время обработки XML документа.

Первая функция initGenericErrorDefaultFunc() устанавливает или сбрасывает, ранее установленный, обработчик. Для сброса обработчика в качестве значения необходимо передать NULL, в качестве обработчика будет использована внутрення функция. Прототип функции:

void initGenericErrorDefaultFunc(xmlGenericErrorFunc * handler)  

Функция xmlSetGenericErrorFunc() - устанавливает или сбрасывает, ранее установленный, обработчик, а также позволяет передать пользовательские данные в callback-функцию. Пользовательские данные будут переданы в callback-функцию первым параметром:

void xmlSetGenericErrorFunc(void * ctx, xmlGenericErrorFunc handler)  

По умолчанию сообщения об ошибках выводятся на stderr, c помощью этой функции можно изменить устройство вывода. Для этого необходимо первым параметром передать указатель на файловый дескриптор FILE, а в качестве второго параметра NULL:

FILE *fd = fopen("./debug.txt", "a+");  
//..
if(fd) {  
   xmlSetGenericErrorFunc((void *)fd, NULL);
}
//...

Теперь все сообщения об ошибках будут записываться в файл debug.txt. Если в качестве значения второго параметра передать NULL, то это приведет к сбросу, ранее установленного, обработчика и в качестве обработчика будет использована внутрення функция.

Прототип callback-функции:

void xmlGenericErrorFunc (void * ctx, const char * msg, ...)  

Функция принимает указатель на пользовательские данные(ctx), человеко-понятное сообщение об ошибке(msg) и список дополнительных пераметров. Сообщение об ошибке, предаваемое вторым аргументом в функцию, представляет из себя шаблон со спецификаторами формата:

Entity: line %d:  

Последующими парметрами в функцию передаются значения спецификаторов шаблона.

Пример функции обработки ошибок:

//...

static void my_libxml2_err(void * ctx, const char * msg, ...) {  
   va_list args;
   va_start(args, msg);
   vfprintf((FILE *)ctx, msg, args);
   va_end(args);
}

//...

FILE *fd;  
fd = fopen("./debug.txt", "w+");  
if(fd != NULL) {  
    xmlSetGenericErrorFunc((void *)fd, (xmlGenericErrorFunc)my_libxml2_err);
} else {
    xmlSetGenericErrorFunc((void *)stderr, (xmlGenericErrorFunc)my_libxml2_err);
}

//...

Для запрета вывода сообщений оставляем тело функции пустым:

//...
static void my_libxml2_err(void * ctx, const char * msg, ...) {  
    (void) ctx;
    (void) msg; 
    return;
}

xmlGenericErrorFunc handle = (xmlGenericErrorFunc)my_libxml2_err;  
initGenericErrorDefaultFunc(&handle;);  
//...

На практике часто встречаются ситуации при которых сообщение о возникшей ошибке, переданное в callback-функцию, заданную с помощью xmlSetGenericErrorFunc() или initGenericErrorDefaultFunc(), является мало информативным или непригодным к использованию в данном контексте. В таких случаях вместо выше описанных функций необходимо использовать функцию xmlSetStructuredErrorFunc()

Функция xmlSetStructuredErrorFunc() - устанавливает или сбрасывает, ранее установленный, обработчик, а также позволяет передать пользовательские данные в callback-функцию. Пользовательские данные будут переданы в callback-функцию первым параметром. Прототип функции:

    void xmlSetStructuredErrorFunc (void *userData, xmlStructuredErrorFunc handler)

Первым параметром функция принимает пользовательские данные, вторым - указатель на структуру xmlErrorPtr с информацией об ошибке.

Структура xmlErrorPtr:

struct _xmlError {  
    int domain;             /* Код, из перечисления xmlErrorDomain, модуля библиотеки, который обнаружил ошибку. */
    int code;               /* Код ошибки из перечисления xmlParserErrors */
    char *message;          /* Человеко-понятное сообщение об ошибке */
    xmlErrorLevel level;    /* Код уровня ошибки из перечисления xmlErrorLevel */
    char *file;             /* Имя файла, если доступно, при обработке которого произошла ошибка, иначе NULL */
    int line;              /* Номер линии в файле, если доступно, на которой обнаружена ошибка, иначе 0 */
    char *str1;           /* Дополнительная строковая информация */
    char *str2;           /* Дополнительная строковая информация */
    char *str3;           /* Дополнительная строковая информация */
    int int1;             /* Дополнительная числовая информация */
    int int2;             /* Номер столбца или 0, если не доступно  */
    void *ctxt;           /* Указатель (xmlParserCtxt) на контекст парсера */
    void *node;           /* Указатель (xmlNodePtr) на элемент в дереве при обработке которого произошла ошибка */
};

Структура xmlErrorDomain:

    typedef enum {
        XML_FROM_NONE = 0,
        XML_FROM_PARSER,    /* The XML parser */
        XML_FROM_TREE,    /* The tree module */
        XML_FROM_NAMESPACE,    /* The XML Namespace module */
        XML_FROM_DTD,    /* The XML DTD validation with parser context*/
        XML_FROM_HTML,    /* The HTML parser */
        XML_FROM_MEMORY,    /* The memory allocator */
        XML_FROM_OUTPUT,    /* The serialization code */
        XML_FROM_IO,    /* The Input/Output stack */
        XML_FROM_FTP,    /* The FTP module */
        XML_FROM_HTTP,    /* The HTTP module */
        XML_FROM_XINCLUDE,    /* The XInclude processing */
        XML_FROM_XPATH,    /* The XPath module */
        XML_FROM_XPOINTER,    /* The XPointer module */
        XML_FROM_REGEXP,    /* The regular expressions module */
        XML_FROM_DATATYPE,    /* The W3C XML Schemas Datatype module */
        XML_FROM_SCHEMASP,    /* The W3C XML Schemas parser module */
        XML_FROM_SCHEMASV,    /* The W3C XML Schemas validation module */
        XML_FROM_RELAXNGP,    /* The Relax-NG parser module */
        XML_FROM_RELAXNGV,    /* The Relax-NG validator module */
        XML_FROM_CATALOG,    /* The Catalog module */
        XML_FROM_C14N,    /* The Canonicalization module */
        XML_FROM_XSLT,    /* The XSLT engine from libxslt */
        XML_FROM_VALID,    /* The XML DTD validation with valid context */
        XML_FROM_CHECK,    /* The error checking module */
        XML_FROM_WRITER,    /* The xmlwriter module */
        XML_FROM_MODULE,    /* The dynamically loaded module module*/
        XML_FROM_I18N,    /* The module handling character conversion */
        XML_FROM_SCHEMATRONV    /* The Schematron validator module */
    } xmlErrorDomain;    

Структура xmlErrorLevel:

    typedef enum {
        XML_ERR_NONE = 0,
        XML_ERR_WARNING = 1, /* Простое предупреждение */
        XML_ERR_ERROR = 2, /* Устранимая ошибка */
        XML_ERR_FATAL = 3  /* Фатальная ошибка */
    } xmlErrorLevel;    

Прототип callback-функции:

    void xmlStructuredErrorFunc (void * userData, xmlErrorPtr error)

Пример функции обработки ошибок:

    void *my_malloc(size_t);
    void *my_realloc(void *, size_t);
    unsigned short my_str_rtrim(char *, size_t);

    void *my_malloc(size_t size) {
        void *mem = malloc(size);
        if (mem == NULL) {
            return NULL;
        }
        if (memset(mem, '\0', size)) {
            return mem;
        } 
        free(mem);
        return NULL;
    }

    void *my_realloc(void *ptr, size_t size) {
        void *new_ptr = NULL;
        if (size == 0) {
            free(ptr);
        } else if (ptr) {
            new_ptr = realloc(ptr, size);
            if (new_ptr == NULL) {
                free(ptr);
            }
            return new_ptr;
        }
        return my_malloc(size);
    }

    unsigned short my_str_rtrim(char *line, size_t line_len) {
        char *p = line;
        size_t i = 0;
        if (!p) {
            return 1;
        }
        while (*p++ != '\0' && i < line_len) {
            i++;
        }
        for (i+=1; i > 0; i--) {   
            p--;  
            if (isspace(*p) || *p == '\0' ) {
                *p = '\0';             
                continue;
            } 
            break;
        }
        return 0;
    }

    void my_strcat(char * dest, const char *src, size_t size) {
        size_t tmp_src_size = 0;
        char *buff = dest;
        const char *s = src;
        if (!dest || !src || size == 0) {
            return;
        }
        while (*buff != '\0') {
            buff++;
        }
        tmp_src_size = size;
        if (tmp_src_size > 0) {
            while (--tmp_src_size != 0) {
                if ((*buff++ = *s++) == '\0') {
                    return;
                }
            }
        }
        *buff = '\0';
    }

    //...

    static void my_libxml2_err(void *ctx, xmlErrorPtr error) {
        char *level_str = NULL, *buff = NULL, *message = NULL;
        const xmlChar *node_name = NULL;
        size_t alloc_mem = 1, len = 0;
        int line = 0, column = 0;

        if (!error || error->code == XML_ERR_OK) {
            return;
        }

        line = error->line;          // Номер линии
        column = error->int2;        // Номер колонки
        message = error->message;    // Сообщение об ошибке сформированное парсером

        // Удаляем пробельные символы с конца строки
        (void)my_str_rtrim(message, strlen(message));

        // Получаем уровень возникшей ошибки в виде строки
        switch (error->level) {
            case XML_ERR_NONE:
                level_str = "";
                break;
            case XML_ERR_WARNING:
                level_str = "Warning: ";
                break;
            case XML_ERR_ERROR:
                level_str = "Error: ";
                break;
            case XML_ERR_FATAL:
                level_str = "Fatal error: ";
                break;
        }

        // Рассчитываем размер памяти, который необходимо выделить: длинна строки + 1 для '\0'
        alloc_mem += strlen(level_str);

        // Выделяем память
        buff = my_malloc(alloc_mem);
        if (!buff) {
            return;
        }
        // Помещаем в буфер строку
        my_strcat(buff, level_str, alloc_mem);

        // Определяем где произошла ошибка (Файл, линиия, колонка) и
        // Добавляем информацию в буфер
        if (error->file != NULL) {
            alloc_mem += strlen(error->file);
            buff = my_realloc(buff, alloc_mem);
            if (!buff) {
                return;
            }
            my_strcat(buff, error->file, alloc_mem);
        } else if ((line != 0) && (error->domain == XML_FROM_PARSER)) {
            len = snprintf(NULL, 0, "Entity: line %d, column: %d", line, column);
            buff = my_realloc(buff, alloc_mem + len);
            if (!buff) {
                return;
            }
            snprintf(buff + alloc_mem - 1, len + 1, "Entity: line %d, column: %d", line, column);
            alloc_mem += len;
        }

        // Определяем название элемента в котором произошла ошибка и
        // Добавляем информацию в буфер
        if ((error->node != NULL) && ((xmlNodePtr) error->node)->type == XML_ELEMENT_NODE) {
            node_name = ((xmlNodePtr) error->node)->name;
            len = snprintf(NULL, 0, ", element %s: ", node_name);
            buff = my_realloc(buff, alloc_mem + len);
            if (!buff) {
                return;
            }
            snprintf(buff + alloc_mem - 1, len + 1, ", element %s: ", node_name);
            alloc_mem += len;
        } else {
            alloc_mem += 2;
            buff = my_realloc(buff, alloc_mem);
            if (!buff) {
                return;
            }
            my_strcat(buff, ": ", alloc_mem);
        }

        // Добавляем в буфер сообщение об ощибке
        alloc_mem += strlen(message);
        buff = my_realloc(buff, alloc_mem);
        if (!buff) {
            return;
        }
        my_strcat(buff, message, alloc_mem);

        // Если есть дополнительная информация, 
        // то также помещаем ее в буфер
        if ((error->domain == XML_FROM_XPATH) && (error->str1 != NULL)) {
            alloc_mem += strlen(error->str1) + 2;
            buff = dwavdapi_realloc(buff, alloc_mem);
            if (!buff) {
                return;
            }
            my_strcat(buff, ": ", alloc_mem);
            my_strcat(buff, error->str1, alloc_mem);
        }

        fprintf((FILE *)ctx, "LibXML2: %s\n", buff);
    }
    //..

    FILE *fd;
    fd = fopen("./debug.txt", "w+");
    if(fd != NULL) {
        xmlSetStructuredErrorFunc((void *)fd, (xmlStructuredErrorFunc)my_libxml2_err);
    } else {
        xmlSetGenericErrorFunc((void *)stderr, (xmlStructuredErrorFunc)my_libxml2_err);
    }

Вывод:

LibXML2: Fatal error: Entity: line 1, column: 589: error parsing attribute name
LibXML2: Fatal error: Entity: line 1, column: 589: attributes construct error
LibXML2: Fatal error: Entity: line 1, column: 589: Couldn't find end of Start Tag processing line 1
LibXML2: Fatal error: Entity: line 1, column: 614: expected '>'
LibXML2: Fatal error: Entity: line 1, column: 614: Opening and ending tag mismatch: infections line 1 and processing
LibXML2: Fatal error: Entity: line 1, column: 661: Opening and ending tag mismatch: server-statistics line 1 and infections

Если в коде будут одновременно вызвана функция xmlSetStructuredErrorFunc() и xmlSetGenericErrorFunc(), то приоритет будет у функции xmlSetStructuredErrorFunc()

Для многопоточных приложений функция xmlSetStructuredErrorFunc()/xmlSetGenericErrorFunc() должна быть вызвана отдельно для каждого потока.

Описанные функции и структуры объявлены в заголовочном файле libxml/xmlerror.h. Также информацию об описанных функциях и структурах можно найти в документации к модулю xmlerror: http://xmlsoft.org/html/libxml-parser.html

Anton Dobkin
Author

Anton Dobkin

Comments