/
Zamar_Terrier
/
TigorEngine
Обзор
Документация
Войти
/
Zamar_Terrier
/
TigorEngine
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/Tools/xml_parser.c
443 строки
15 KB
Zamar_Terrier
svg fix
04 авг 2026, 17:58
04 авг 2026, 17:58
fda36b2
Код
Авторство
О чём код?
#include "Tools/xml_parser.h" #include <ctype.h> #include <stdio.h> #include <string.h> /* ============================================================================ Вспомогательные функции ============================================================================ */ static void skip_whitespace(const char **str) { while (**str == ' ' || **str == '\t' || **str == '\n' || **str == '\r') (*str)++; } static char* extract_string(const char* start, const char* end) { if (start >= end) return NULL; int len = (int)(end - start); char* str = (char*)calloc(len + 1, sizeof(char)); if (!str) return NULL; memcpy(str, start, len); str[len] = '\0'; return str; } /* ============================================================================ Пропуск "мусорных" XML-конструкций ============================================================================ */ static void skip_comment(const char **str) { /* <!-- ... --> */ if (strncmp(*str, "<!--", 4) != 0) return; *str += 4; while (**str != '\0') { if (**str == '-' && *((*str)+1) == '-' && *((*str)+2) == '>') { *str += 3; return; } (*str)++; } } static void skip_cdata(const char **str) { /* <![CDATA[ ... ]]> */ if (strncmp(*str, "<![CDATA[", 9) != 0) return; *str += 9; while (**str != '\0') { if (**str == ']' && *((*str)+1) == ']' && *((*str)+2) == '>') { *str += 3; return; } (*str)++; } } static void skip_processing_instruction(const char **str) { /* <?...?> (включая <?xml...?>) */ if (**str != '<' || *((*str)+1) != '?') return; *str += 2; while (**str != '\0') { if (**str == '?' && *((*str)+1) == '>') { *str += 2; return; } (*str)++; } } static void skip_doctype(const char **str) { /* <!DOCTYPE ... > с возможной вложенностью [<!ENTITY ...>]> */ if (**str != '<' || *((*str)+1) != '!') return; int depth = 0; while (**str != '\0') { if (**str == '<') depth++; else if (**str == '>') { depth--; if (depth == 0) { (*str)++; return; } } (*str)++; } } /** * Пропускает любую не-элементную XML-конструкцию на текущей позиции. * Возвращает 1 если что-то было пропущено, 0 если текущая позиция - * это либо не '<', либо это начало элемента (<tag или </tag). */ static int skip_non_element(const char **str) { if (**str != '<') return 0; if (strncmp(*str, "<!--", 4) == 0) { skip_comment(str); return 1; } if (strncmp(*str, "<![CDATA[", 9) == 0) { skip_cdata(str); return 1; } if (**str == '<' && *((*str)+1) == '?') { skip_processing_instruction(str); return 1; } /* <!DOCTYPE и прочие <!...> (но НЕ <!-- и не <![CDATA[ — они уже обработаны) */ if (**str == '<' && *((*str)+1) == '!') { skip_doctype(str); return 1; } return 0; } /** * Пропускает все подряд не-элементные конструкции (комментарии, директивы, * CDATA и т.д.) с пропуском whitespace между ними. */ static void skip_all_non_elements(const char **str) { for (;;) { skip_whitespace(str); if (!skip_non_element(str)) break; } } /* ============================================================================ Создание / уничтожение узлов, атрибуты, дети ============================================================================ */ xml_node_t* xml_create_node(const char* name) { xml_node_t* node = (xml_node_t*)calloc(1, sizeof(xml_node_t)); if (!node) return NULL; if (name) { node->name = (char*)calloc(strlen(name) + 1, sizeof(char)); if (node->name) strcpy(node->name, name); } node->value = NULL; node->parent = NULL; node->children = NULL; node->next = NULL; node->attributes = NULL; node->attr_count = 0; node->child_count = 0; return node; } void xml_free_node(xml_node_t* node) { if (!node) return; if (node->name) free(node->name); if (node->value) free(node->value); if (node->attributes) { for (int i = 0; i < node->attr_count; i++) { free(node->attributes[i].name); free(node->attributes[i].value); } free(node->attributes); } xml_node_t* child = node->children; while (child) { xml_node_t* next = child->next; xml_free_node(child); child = next; } free(node); } int xml_add_attribute(xml_node_t* node, const char* name, const char* value) { if (!node || !name || !value) return 0; xml_attribute_t* tmp = (xml_attribute_t*)realloc( node->attributes, (node->attr_count + 1) * sizeof(xml_attribute_t)); if (!tmp) return 0; node->attributes = tmp; node->attributes[node->attr_count].name = (char*)calloc(strlen(name) + 1, sizeof(char)); node->attributes[node->attr_count].value = (char*)calloc(strlen(value) + 1, sizeof(char)); if (!node->attributes[node->attr_count].name || !node->attributes[node->attr_count].value) return 0; strcpy(node->attributes[node->attr_count].name, name); strcpy(node->attributes[node->attr_count].value, value); node->attr_count++; return 1; } char* xml_get_attribute(xml_node_t* node, const char* name) { if (!node || !name) return NULL; for (int i = 0; i < node->attr_count; i++) { if (strcmp(node->attributes[i].name, name) == 0) return node->attributes[i].value; } return NULL; } int xml_add_child(xml_node_t* parent, xml_node_t* child) { if (!parent || !child) return 0; child->parent = parent; child->next = NULL; if (!parent->children) { parent->children = child; } else { xml_node_t* last = parent->children; while (last->next) last = last->next; last->next = child; } parent->child_count++; return 1; } xml_node_t* xml_find_node(xml_node_t* root, const char* name) { if (!root || !name) return NULL; if (root->name && strcmp(root->name, name) == 0) return root; xml_node_t* child = root->children; while (child) { xml_node_t* found = xml_find_node(child, name); if (found) return found; child = child->next; } return NULL; } /* ============================================================================ Ядро парсера: рекурсивный разбор одного XML-элемента ============================================================================ */ /** * Парсит ровно один XML-элемент (вместе со всем его содержимым). * Перед вызовом указатель *str должен стоять на '<' этого элемента. * После возврата указатель стоит сразу после закрывающего '>' данного элемента. */ static xml_node_t* parse_element(const char **str) { skip_whitespace(str); skip_all_non_elements(str); if (**str != '<') return NULL; (*str)++; /* пропускаем '<' */ /* Закрывающий тег — сюда мы не должны попадать (вызывающий код это проверяет) */ if (**str == '/') { (*str)--; /* откатываем обратно на '<' */ return NULL; } /* ---------- 1. Читаем имя тега ---------- */ const char* name_start = *str; while (**str != '\0' && **str != ' ' && **str != '\t' && **str != '\n' && **str != '\r' && **str != '>' && **str != '/') { (*str)++; } const char* name_end = *str; if (name_start == name_end) return NULL; xml_node_t* node = xml_create_node(NULL); if (!node) return NULL; node->name = extract_string(name_start, name_end); skip_whitespace(str); /* ---------- 2. Парсим атрибуты ---------- */ while (**str != '\0' && **str != '>' && **str != '/') { const char* aname_start = *str; while (**str != '\0' && **str != '=' && **str != ' ' && **str != '\t' && **str != '\n' && **str != '\r' && **str != '>' && **str != '/') { (*str)++; } const char* aname_end = *str; if (aname_start == aname_end) { skip_whitespace(str); if (**str == '>' || **str == '/' || **str == '\0') break; continue; } skip_whitespace(str); if (**str != '=') { /* атрибут без значения — пропускаем */ skip_whitespace(str); continue; } (*str)++; /* '=' */ skip_whitespace(str); char quote = **str; if (quote != '"' && quote != '\'') break; (*str)++; /* открывающая кавычка */ const char* avalue_start = *str; while (**str != '\0' && **str != quote) (*str)++; const char* avalue_end = *str; if (**str == quote) (*str)++; /* закрывающая кавычка */ char* aname = extract_string(aname_start, aname_end); char* avalue = extract_string(avalue_start, avalue_end); if (aname && avalue) xml_add_attribute(node, aname, avalue); if (aname) free(aname); if (avalue) free(avalue); skip_whitespace(str); } /* ---------- 3. Самозакрывающийся тег? ---------- */ if (**str == '/') { (*str)++; /* '/' */ if (**str == '>') (*str)++; /* '>' */ return node; } /* ---------- 4. Открывающий тег закрывается '>' ---------- */ if (**str != '>') { xml_free_node(node); return NULL; } (*str)++; /* '>' */ /* ---------- 5. Парсим содержимое (дети + текст) ---------- */ while (**str != '\0') { skip_whitespace(str); skip_all_non_elements(str); if (**str == '\0') break; if (**str == '<') { /* Закрывающий тег данного элемента? */ if (*(*str + 1) == '/') { *str += 2; /* '</' */ /* Пропускаем имя закрывающего тега до '>' */ while (**str != '\0' && **str != '>') (*str)++; if (**str == '>') (*str)++; break; } /* Дочерний элемент */ xml_node_t* child = parse_element(str); if (child) xml_add_child(node, child); } else { /* Текстовое содержимое */ const char* text_start = *str; while (**str != '\0' && **str != '<') (*str)++; const char* text_end = *str; /* обрезаем ведущие/замыкающие пробелы */ while (text_start < text_end && isspace((unsigned char)*text_start)) text_start++; while (text_end > text_start && isspace((unsigned char)*(text_end - 1))) text_end--; if (text_start < text_end) { int new_len = (int)(text_end - text_start); if (node->value) { int old_len = (int)strlen(node->value); char* merged = (char*)realloc(node->value, old_len + new_len + 1); if (merged) { memcpy(merged + old_len, text_start, new_len); merged[old_len + new_len] = '\0'; node->value = merged; } } else { node->value = extract_string(text_start, text_end); } } } } return node; } /* ============================================================================ Публичное API ============================================================================ */ xml_node_t* xml_parse(const char **str_point, xml_node_t* parent) { (void)parent; /* больше не используется — парсер полностью рекурсивный */ if (!str_point || !(*str_point)) return NULL; skip_whitespace(str_point); skip_all_non_elements(str_point); if (**str_point == '\0') return NULL; return parse_element(str_point); } /* ============================================================================ Сериализация ============================================================================ */ static void serialize_node(xml_node_t* n, char** pos, int* size) { if (!n) return; int len = snprintf(*pos, *size, "<%s", n->name ? n->name : ""); *pos += len; *size -= len; for (int i = 0; i < n->attr_count; i++) { len = snprintf(*pos, *size, " %s=\"%s\"", n->attributes[i].name, n->attributes[i].value); *pos += len; *size -= len; } if (n->value || n->children) { len = snprintf(*pos, *size, ">"); *pos += len; *size -= len; if (n->value) { len = snprintf(*pos, *size, "%s", n->value); *pos += len; *size -= len; } xml_node_t* child = n->children; while (child) { serialize_node(child, pos, size); child = child->next; } len = snprintf(*pos, *size, "</%s>", n->name ? n->name : ""); *pos += len; *size -= len; } else { len = snprintf(*pos, *size, " />"); *pos += len; *size -= len; } } char* xml_serialize(xml_node_t* node) { if (!node) return NULL; char* buffer = (char*)calloc(10240, sizeof(char)); if (!buffer) return NULL; char* pos = buffer; int size = 10240; serialize_node(node, &pos, &size); *pos = '\0'; return buffer; } xml_node_t* xml_read_file(const char* filename) { FILE* file = fopen(filename, "r"); if (!file) return NULL; fseek(file, 0, SEEK_END); long length = ftell(file); fseek(file, 0, SEEK_SET); char* buffer = (char*)calloc(length + 1, sizeof(char)); if (!buffer) { fclose(file); return NULL; } fread(buffer, 1, length, file); buffer[length] = '\0'; fclose(file); const char* ptr = buffer; xml_node_t* root = xml_parse(&ptr, NULL); free(buffer); return root; } int xml_write_file(xml_node_t* node, const char* filename) { if (!node || !filename) return 0; char* str = xml_serialize(node); if (!str) return 0; FILE* file = fopen(filename, "w"); if (!file) { free(str); return 0; } fprintf(file, "%s", str); fclose(file); free(str); return 1; }