如何使用libxml2获取XML文件中指定的tiles元素?
解决libxml2遍历XML节点时的空指针与层级遍历问题
需求:获取XML文件中
tiles元素的xmlNode,XML结构包含map、export、region、floor、tiles等节点
尝试方案:通过节点的children和next属性手动遍历XML树,但遍历到export节点后调用cur->next会导致cur变为NULL并触发段错误;切换到region节点后也无法继续深入遍历。
问题根源
手动遍历children和next时,会把XML中的空白文本节点(换行、缩进、空格)当成有效节点,导致遍历逻辑混乱,甚至访问NULL指针触发段错误;同时未正确处理节点层级的跳转逻辑,无法深入到region->floor->tiles的深层结构。
解决方法
1. 手动过滤空白文本节点
写一个工具函数跳过所有空白文本节点,确保遍历的都是元素节点:
#include <libxml/parser.h> #include <libxml/tree.h> // 跳过空白文本节点,返回下一个有效元素节点 xmlNode* skip_blank_text_nodes(xmlNode* node) { while (node != NULL) { // 只保留元素节点,跳过空白文本、注释等非目标节点 if (node->type == XML_ELEMENT_NODE) { break; } else if (node->type == XML_TEXT_NODE && xmlIsBlankNode(node)) { node = node->next; } else { node = node->next; } } return node; }
修改原遍历代码,用这个函数处理节点跳转:
int main() { xmlDoc *doc = xmlParseFile("test.xml"); if (doc == NULL) { fprintf(stderr, "Failed to parse XML file\n"); return 1; } xmlNode *cur = xmlDocGetRootElement(doc); // 跳过根节点下的空白节点,直接取第一个子元素 cur = skip_blank_text_nodes(cur->children); while (cur != NULL) { // 找到region节点后,深入遍历其子节点 if (!xmlStrcmp(cur->name, (const xmlChar*)"region")) { xmlNode *floor_node = skip_blank_text_nodes(cur->children); while (floor_node != NULL) { if (!xmlStrcmp(floor_node->name, (const xmlChar*)"floor")) { xmlNode *tiles_node = skip_blank_text_nodes(floor_node->children); while (tiles_node != NULL) { if (!xmlStrcmp(tiles_node->name, (const xmlChar*)"tiles")) { // 成功找到tiles节点,添加业务逻辑 printf("Successfully found tiles node\n"); } tiles_node = skip_blank_text_nodes(tiles_node->next); } } floor_node = skip_blank_text_nodes(floor_node->next); } } cur = skip_blank_text_nodes(cur->next); } xmlFreeDoc(doc); xmlCleanupParser(); return 0; }
2. 使用libxml2自带的元素遍历函数
libxml2提供了xmlFirstElementChild和xmlNextElementSibling函数,会自动跳过非元素节点,无需手动处理空白文本:
int main() { xmlDoc *doc = xmlParseFile("test.xml"); if (doc == NULL) { fprintf(stderr, "Failed to parse XML file\n"); return 1; } xmlNode *root = xmlDocGetRootElement(doc); // 遍历根节点的所有子元素 xmlNode *region_node = xmlFirstElementChild(root); while (region_node != NULL) { if (!xmlStrcmp(region_node->name, (const xmlChar*)"region")) { // 遍历region的子元素找floor xmlNode *floor_node = xmlFirstElementChild(region_node); while (floor_node != NULL) { if (!xmlStrcmp(floor_node->name, (const xmlChar*)"floor")) { // 遍历floor的子元素找tiles xmlNode *tiles_node = xmlFirstElementChild(floor_node); while (tiles_node != NULL) { if (!xmlStrcmp(tiles_node->name, (const xmlChar*)"tiles")) { printf("Successfully found tiles node\n"); } tiles_node = xmlNextElementSibling(tiles_node); } } floor_node = xmlNextElementSibling(floor_node); } } region_node = xmlNextElementSibling(region_node); } xmlFreeDoc(doc); xmlCleanupParser(); return 0; }
3. 使用XPath直接查询(最简洁高效)
如果知道tiles节点的路径,用XPath可以直接定位,无需手动遍历整个XML树:
#include <libxml/xpath.h> int main() { xmlDoc *doc = xmlParseFile("test.xml"); if (doc == NULL) { fprintf(stderr, "Failed to parse XML file\n"); return 1; } // 创建XPath上下文 xmlXPathContext *xpath_ctx = xmlXPathNewContext(doc); if (xpath_ctx == NULL) { fprintf(stderr, "Failed to create XPath context\n"); xmlFreeDoc(doc); return 1; } // 构造XPath表达式,根据XML结构调整路径,示例为/map/region/floor/tiles const xmlChar *xpath_expr = (const xmlChar*)"/map/region/floor/tiles"; xmlXPathObject *xpath_obj = xmlXPathEvalExpression(xpath_expr, xpath_ctx); if (xpath_obj == NULL) { fprintf(stderr, "Failed to evaluate XPath expression\n"); xmlXPathFreeContext(xpath_ctx); xmlFreeDoc(doc); return 1; } // 检查是否找到匹配节点 xmlNodeSetPtr nodes = xpath_obj->nodesetval; if (nodes != NULL && nodes->nodeNr > 0) { // 取第一个匹配的tiles节点(多节点可遍历nodeTab数组) xmlNode *tiles_node = nodes->nodeTab[0]; printf("Successfully found tiles node\n"); // 添加业务逻辑 } else { fprintf(stderr, "No tiles node found\n"); } // 释放资源 xmlXPathFreeObject(xpath_obj); xmlXPathFreeContext(xpath_ctx); xmlFreeDoc(doc); xmlCleanupParser(); return 0; }
内容的提问来源于stack exchange,提问作者TraceTalcum
相关产品推荐
相关产品推荐

