$BUILTIN xdoc $FUNCTION xdoc_builtin $PRODUCES xmlsh.c $SHORT_DOC xdoc [cmd [handle [uri|expr]]] xdoc - XML/HTML query command with persistent states and XPath 1.0 support Usage: xdoc [help|clear|list] | [open xml|html uri] | [xpath expr] | [cmd handle] Examples: xdoc [help] xdoc clear xdoc open html ./test.html xdoc xpath d1 //h4 xdoc xpath n3 //li xdoc close d1 xdoc children n3 xdoc get na xdoc text n2 xdoc parent n3 xdoc first_child n2 xdoc last_child n2 xdoc next_sibling n3 xdoc prev_sibling n3 $END #include #include #include #include #include #include #include #include #include #include #include #include #include "../shell.h" #include "common.h" #define MAX_DOCS 256 #define MAX_NODES 65536 typedef struct { int doc_id; xmlDocPtr doc; int valid; } DocEntry; typedef struct { int node_id; xmlNodePtr node; int doc_id; // 所属文档ID,用于快速清理 int valid; } NodeEntry; DocEntry docs[MAX_DOCS]; NodeEntry nodes[MAX_NODES]; // 初始化 void init() { LIBXML_TEST_VERSION memset(docs, 0, sizeof(docs)); memset(nodes, 0, sizeof(nodes)); } // 清理所有资源 void cleanup() { for (int i = 1; i < MAX_DOCS; i++) { if (docs[i].valid) { xmlFreeDoc(docs[i].doc); } } xmlCleanupParser(); } static int initialized = 0; /* 初始化函数声明 */ static void xmlsh_initialize(void) { if (!initialized) { init(); atexit(cleanup); initialized = 1; } } // 分配文档句柄 int alloc_doc(xmlDocPtr doc) { for (int i = 1; i < MAX_DOCS; i++) { if (!docs[i].valid) { docs[i].doc = doc; docs[i].doc_id = i; docs[i].valid = 1; return i; } } return -1; } // 释放文档句柄及相关节点句柄 void free_doc(int doc_id) { if (doc_id < 1 || doc_id >= MAX_DOCS || !docs[doc_id].valid) return; xmlFreeDoc(docs[doc_id].doc); docs[doc_id].valid = 0; // 释放所有属于该文档的节点句柄 for (int i = 1; i < MAX_NODES; i++) { if (nodes[i].valid && nodes[i].doc_id == doc_id) { nodes[i].valid = 0; } } } // 分配节点句柄 int alloc_node(xmlNodePtr node, int doc_id) { // 先查找是否已存在相同节点指针 for (int i = 1; i < MAX_NODES; i++) { if (nodes[i].valid && nodes[i].node == node && nodes[i].doc_id == doc_id) { return i; // 返回已有句柄 } } // 不存在,找空闲位置分配 for (int i = 1; i < MAX_NODES; i++) { if (!nodes[i].valid) { nodes[i].node = node; nodes[i].doc_id = doc_id; nodes[i].valid = 1; nodes[i].node_id = i; // 如果需要存储自身ID(可选) return i; } } return -1; // 无空闲句柄 } // 目前我们还没有实现 del 命令,但将来若增加删除节点的功能,必须同时遍历节点表,将所有指向被删除节点的句柄置为无效(valid = 0)。否则这些句柄会变成悬空指针。可以添加一个辅助函数: void invalidate_node_handles(xmlNodePtr node) { for (int i = 1; i < MAX_NODES; i++) { if (nodes[i].valid && nodes[i].node == node) { nodes[i].valid = 0; } } } // 释放节点句柄(仅标记无效,不释放节点内存) void free_node(int node_id) { if (node_id < 1 || node_id >= MAX_NODES || !nodes[node_id].valid) return; nodes[node_id].valid = 0; } // 根据节点句柄获取节点指针 xmlNodePtr get_node(int node_id) { if (node_id < 1 || node_id >= MAX_NODES || !nodes[node_id].valid) return NULL; return nodes[node_id].node; } // 根据文档句柄获取文档指针 xmlDocPtr get_document(int doc_id) { if (doc_id < 1 || doc_id >= MAX_DOCS || !docs[doc_id].valid) return NULL; return docs[doc_id].doc; } // 解析带前缀的句柄字符串,返回ID,并通过type输出类型('d'或'n') int parse_handle(const char *str, char *type) { if (!str || strlen(str) < 2) return -1; if (str[0] == 'd' || str[0] == 'D') { *type = 'd'; } else if (str[0] == 'n' || str[0] == 'N') { *type = 'n'; } else { return -1; } char *endptr; long id = strtol(str + 1, &endptr, 16); if (*endptr != '\0' || id <= 0) return -1; if (*type == 'd' && id >= MAX_DOCS) return -1; if (*type == 'n' && id >= MAX_NODES) return -1; return (int)id; } // 输出节点句柄列表(空格分隔) void print_node_handles(xmlNodeSetPtr nodeset, int doc_id) { if (!nodeset) return; for (int i = 0; i < nodeset->nodeNr; i++) { xmlNodePtr node = nodeset->nodeTab[i]; int node_id = alloc_node(node, doc_id); if (node_id > 0) { printf("n%x ", node_id); } } printf("\n"); } // 处理XPath查询结果 void handle_xpath_result(xmlXPathObjectPtr result, int doc_id) { if (!result) { fprintf(stderr, "XPath evaluation returned NULL\n"); return; } switch (result->type) { case XPATH_NODESET: print_node_handles(result->nodesetval, doc_id); break; case XPATH_BOOLEAN: printf("%s\n", result->boolval ? "true" : "false"); break; case XPATH_NUMBER: printf("%g\n", result->floatval); break; case XPATH_STRING: printf("%s\n", result->stringval); break; default: printf("Unsupported XPath result type: %d\n", result->type); } } // 命令:open int cmd_open(char *type_str, char *uri) { xmlDocPtr doc = NULL; if (strcmp(type_str, "xml") == 0) { doc = xmlReadFile(uri, NULL, XML_PARSE_RECOVER); } else if (strcmp(type_str, "html") == 0) { doc = htmlReadFile(uri, NULL, HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING); } else { fprintf(stderr, "Invalid type: %s (use xml or html)\n", type_str); return 1; } if (!doc) { fprintf(stderr, "Failed to open %s\n", uri); return 1; } int doc_id = alloc_doc(doc); if (doc_id < 0) { fprintf(stderr, "No free document slot\n"); xmlFreeDoc(doc); return 1; } printf("d%x\n", doc_id); return 0; } // 命令:xpath int cmd_xpath(char *handle_str, char *expr) { char type; int id = parse_handle(handle_str, &type); if (id < 0) { fprintf(stderr, "Invalid handle\n"); return 1; } xmlDocPtr doc = NULL; xmlNodePtr node = NULL; int doc_id = -1; if (type == 'd') { doc = get_document(id); if (!doc) { fprintf(stderr, "Document not found\n"); return 1; } doc_id = id; // 上下文节点默认为文档根,稍后可不设置 } else if (type == 'n') { node = get_node(id); if (!node) { fprintf(stderr, "Node not found\n"); return 1; } doc = node->doc; doc_id = nodes[id].doc_id; // 从节点表获取文档ID if (!doc) { fprintf(stderr, "Node's document not found\n"); return 1; } } else { fprintf(stderr, "Unknown handle type\n"); return 1; } xmlXPathContextPtr ctx = xmlXPathNewContext(doc); if (!ctx) { fprintf(stderr, "Failed to create XPath context\n"); return 1; } // 如果提供了节点句柄,设置上下文节点 if (type == 'n' && node) { if (xmlXPathSetContextNode(node, ctx) != 0) { fprintf(stderr, "Failed to set context node\n"); xmlXPathFreeContext(ctx); return 1; } } xmlXPathObjectPtr result = xmlXPathEvalExpression(BAD_CAST expr, ctx); if (!result) { fprintf(stderr, "XPath evaluation failed\n"); xmlXPathFreeContext(ctx); return 1; } handle_xpath_result(result, doc_id); // 注意这里传入的是 doc_id,用于分配节点句柄 xmlXPathFreeObject(result); xmlXPathFreeContext(ctx); return 0; } // 命令:get int cmd_get(char *handle_str) { char type; int id = parse_handle(handle_str, &type); if (id < 0 || type != 'n') { fprintf(stderr, "Invalid node handle\n"); return 1; } xmlNodePtr node = get_node(id); if (!node) { fprintf(stderr, "Node not found\n"); return 1; } xmlBufferPtr buf = xmlBufferCreate(); xmlNodeDump(buf, node->doc, node, 0, 1); printf("%s\n", (char*)buf->content); xmlBufferFree(buf); return 0; } // 命令:close (文档或节点) int cmd_close(char *handle_str) { char type; int id = parse_handle(handle_str, &type); if (id < 0) { fprintf(stderr, "Invalid handle\n"); return 1; } if (type == 'd') { free_doc(id); printf("OK\n"); } else { free_node(id); printf("OK\n"); } return 0; } int cmd_parent(char *handle_str) { char type; int id = parse_handle(handle_str, &type); if (id < 0 || type != 'n') { fprintf(stderr, "Invalid node handle\n"); return 1; } xmlNodePtr node = get_node(id); if (!node) { fprintf(stderr, "Node not found\n"); return 1; } xmlNodePtr parent = node->parent; if (!parent) { // 没有父节点,输出空或提示 printf("\n"); return 0; } int parent_id = alloc_node(parent, nodes[id].doc_id); if (parent_id > 0) { printf("n%x\n", parent_id); return 0; } else { fprintf(stderr, "Failed to allocate handle for parent\n"); return 1; } } int cmd_text(char *handle_str) { char type; int id = parse_handle(handle_str, &type); if (id < 0 || type != 'n') { fprintf(stderr, "Invalid node handle\n"); return 1; } xmlNodePtr node = get_node(id); if (!node) { fprintf(stderr, "Node not found\n"); return 1; } xmlChar *content = xmlNodeGetContent(node); if (content) { printf("%s\n", (char*)content); xmlFree(content); } else { printf("\n"); // 空内容 } return 0; } int output_node_handle(xmlNodePtr node, int doc_id) { if (!node) { printf("\n"); // 空行表示无节点 return 0; } int node_id = alloc_node(node, doc_id); if (node_id > 0) { printf("n%x\n", node_id); return 0; } else { fprintf(stderr, "Failed to allocate node handle\n"); return 1; } } int cmd_first_child(char *handle_str) { char type; int id = parse_handle(handle_str, &type); if (id < 0 || type != 'n') { fprintf(stderr, "Invalid node handle\n"); return 1; } xmlNodePtr node = get_node(id); if (!node) { fprintf(stderr, "Node not found\n"); return 1; } return output_node_handle(node->children, nodes[id].doc_id); } int cmd_last_child(char *handle_str) { char type; int id = parse_handle(handle_str, &type); if (id < 0 || type != 'n') { fprintf(stderr, "Invalid node handle\n"); return 1; } xmlNodePtr node = get_node(id); if (!node) { fprintf(stderr, "Node not found\n"); return 1; } return output_node_handle(node->last, nodes[id].doc_id); } int cmd_next_sibling(char *handle_str) { char type; int id = parse_handle(handle_str, &type); if (id < 0 || type != 'n') { fprintf(stderr, "Invalid node handle\n"); return 1; } xmlNodePtr node = get_node(id); if (!node) { fprintf(stderr, "Node not found\n"); return 1; } return output_node_handle(node->next, nodes[id].doc_id); } int cmd_prev_sibling(char *handle_str) { char type; int id = parse_handle(handle_str, &type); if (id < 0 || type != 'n') { fprintf(stderr, "Invalid node handle\n"); return 1; } xmlNodePtr node = get_node(id); if (!node) { fprintf(stderr, "Node not found\n"); return 1; } return output_node_handle(node->prev, nodes[id].doc_id); } int cmd_children(char *handle_str) { char type; int id = parse_handle(handle_str, &type); if (id < 0 || type != 'n') { fprintf(stderr, "Invalid node handle\n"); return 1; } xmlNodePtr node = get_node(id); if (!node) { fprintf(stderr, "Node not found\n"); return 1; } xmlNodePtr child = node->children; int first = 1; while (child) { int child_id = alloc_node(child, nodes[id].doc_id); if (child_id > 0) { if (!first) printf(" "); printf("n%x", child_id); first = 0; } child = child->next; } printf("\n"); return 0; } int cmd_clear(void) { for (int i = 1; i < MAX_DOCS; i++) { if (docs[i].valid) { xmlFreeDoc(docs[i].doc); docs[i].valid = 0; } } for (int i = 1; i < MAX_NODES; i++) { nodes[i].valid = 0; } printf("OK\n"); return 0; } int xdoc_builtin(WORD_LIST *list) { char *cmd; if (list == NULL) { builtin_help(); return EX_USAGE; } cmd = list->word->word; if (strcmp(cmd, "help") == 0) { builtin_help(); return EXECUTION_SUCCESS; } else if (strcmp(cmd, "clear") == 0) { int ret = cmd_clear(); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } if (list->next == NULL) { builtin_usage(); return EX_USAGE; } if (strcmp(cmd, "get") == 0) { int ret = cmd_get(list->next->word->word); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } else if (strcmp(cmd, "text") == 0) { int ret = cmd_text(list->next->word->word); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } else if (strcmp(cmd, "first_child") == 0) { int ret = cmd_first_child(list->next->word->word); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } else if (strcmp(cmd, "last_child") == 0) { int ret = cmd_last_child(list->next->word->word); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } else if (strcmp(cmd, "next_sibling") == 0) { int ret = cmd_next_sibling(list->next->word->word); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } else if (strcmp(cmd, "prev_sibling") == 0) { int ret = cmd_prev_sibling(list->next->word->word); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } else if (strcmp(cmd, "children") == 0) { int ret = cmd_children(list->next->word->word); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } else if (strcmp(cmd, "close") == 0) { int ret = cmd_close(list->next->word->word); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } if (list->next->next == NULL) { builtin_usage(); return EX_USAGE; } if (strcmp(cmd, "open") == 0) { char *type, *full_uri; type = list->next->word->word; // 剩余部分组合为 uri(可能包含空格) full_uri = string_list(list->next->next); if (full_uri == NULL) { builtin_error("out of memory"); return EXECUTION_FAILURE; } xmlsh_initialize(); int ret = cmd_open(type, full_uri); free(full_uri); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } else if (strcmp(cmd, "xpath") == 0) { char *handle_str, *expr; handle_str = list->next->word->word; expr = string_list(list->next->next); if (expr == NULL) { builtin_error("out of memory"); return EXECUTION_FAILURE; } int ret = cmd_xpath(handle_str, expr); free(expr); return ret == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } builtin_usage(); return EX_USAGE; }