Libxml 类库的教程(C语言)(翻译)
本文译自 libxml2 在 GNOME 的维基
1. 介绍
Libxml 是一个 C 语言库,实现了用于读取、创建和操作 XML 数据的功能。这个教程提供了示例代码和基本功能的解释。更多关于 Libxml 的详细信息以及使用文档可以在项目主页上找到。这个教程并不能替代完整的文档,但它能说明使用库进行基本操作所需的函数。
这个教程基于一个我用来编写文章的简单 XML 应用程序。格式包括元数据和文章的正文。
这个教程中的示例代码演示了如何:
- 解析文档。
- 提取指定元素中的文本。
- 添加一个元素及其内容。
- 添加一个属性。
- 提取属性的值。
完整的示例代码在附录中包含。
2. 数据类型
Libxml 声明了许多我们将会反复遇到的数据类型,它们隐藏了复杂的部分,这样除非有特定需要,你无需直接处理它们。
xmlChar
xmlChar是 char 的基本替代,用于 UTF-8 编码字符串中的一个字节。如果你的数据使用其他编码,需要转换成 UTF-8 才能使用 libxml 的函数。更多关于编码的信息可以在 libxml 的编码支持网页上找到。
xmlDoc
xmlDoc 是一个包含由解析文档创建的树的结构体。xmlDocPtr 是指向该结构体的指针。
xmlNodePtr 和 xmlNode
xmlNode 是一个包含单个节点的结构体。xmlNodePtr 是指向该结构体的指针,用于遍历文档树。
知识点详细讲解
-
解析文档:解析 XML 文档是处理 XML 数据的第一步。通过
xmlParseFile
或xmlReadFile
函数,你可以将 XML 文件读取到内存中,并生成一个树状结构来表示 XML 文档。 -
提取元素中的文本:一旦文档被解析,你可以遍历树找到特定的节点,并提取其中的文本。通常,我们会使用类似
xmlNodeGetContent
的函数来获取节点的内容。 -
添加元素及其内容:创建新的 XML 节点并添加到文档中可以通过
xmlNewNode
和xmlAddChild
函数实现。这些操作允许你动态生成或修改 XML 文档。 -
添加属性:为节点添加属性可以使用
xmlNewProp
函数。这在需要附加额外的元数据到节点时非常有用。 -
提取属性值:获取节点属性的值可以通过
xmlGetProp
函数。这使得你能够读取存储在属性中的额外信息。
这些基本操作涵盖了大部分处理 XML 数据的常见需求。通过理解这些操作,你将能够高效地解析、修改和生成 XML 文档。
3. 解析文件
解析文件只需要文件名和一次函数调用,并进行错误检查。完整代码请参见附录B,关键字示例代码。
代码解析
[1] xmlDocPtr doc;
[2] xmlNodePtr cur;
[3] doc = xmlParseFile(docname);
if (doc == NULL) {
fprintf(stderr,"Document not parsed successfully.\n");
return;
}
[4] cur = xmlDocGetRootElement(doc);
[5] if (cur == NULL) {
fprintf(stderr,"empty document\n");
xmlFreeDoc(doc);
return;
}
[6] if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
fprintf(stderr,"document of the wrong type, root node != story");
xmlFreeDoc(doc);
return;
}
步骤详细讲解
-
声明指向解析文档的指针
xmlDocPtr doc;
这里,我们声明了一个指针
doc
,它将指向解析后的 XML 文档。 -
声明一个节点指针
xmlNodePtr cur;
我们还需要一个节点指针
cur
,用于与单个节点进行交互。 -
解析 XML 文件并进行错误检查
doc = xmlParseFile(docname); if (doc == NULL) { fprintf(stderr,"Document not parsed successfully.\n"); return; }
调用
xmlParseFile
函数以解析指定名称的文件docname
。如果解析失败,doc
将为NULL
,此时打印错误信息并返回。注意:在这一点上,一个常见的错误是编码处理不当。XML 标准要求使用除 UTF-8 或 UTF-16 以外编码存储的文档必须明确声明其编码。如果声明存在,libxml 会自动将其转换为 UTF-8。
-
获取文档的根元素
cur = xmlDocGetRootElement(doc);
调用
xmlDocGetRootElement
函数获得 XML 文档的根节点,并将其赋值给cur
。 -
检查文档是否为空
if (cur == NULL) { fprintf(stderr,"empty document\n"); xmlFreeDoc(doc); return; }
如果根节点为空,则表示文档为空。此时打印错误信息,释放文档资源,并返回。
-
检查文档的根节点类型
if (xmlStrcmp(cur->name, (const xmlChar *) "story")) { fprintf(stderr,"document of the wrong type, root node != story"); xmlFreeDoc(doc); return; }
在这个教程的例子中,我们需要确保文档类型正确,即根节点的名称应为
"story"
。如果根节点名称与预期不符,打印错误信息,释放文档资源,并返回。
总结
通过这些步骤,我们可以顺利解析一个 XML 文件并确保文档内容符合预期类型。在处理 XML 文件时,正确的错误检查至关重要,可以有效避免程序崩溃或错误行为。
4.检索元素内容
检索元素的内容涉及遍历文档树,直到找到所需的元素。在本例中,我们正在寻找一个名为 “keyword” 的元素,该元素包含在名为 “story” 的元素内。找到感兴趣的节点的过程包括仔细地遍历树。假设你已经有一个名为 doc
的 xmlDocPtr
和一个名为 cur
的 xmlNodePtr
。
[1] cur = cur->xmlChildrenNode;
[2] while (cur != NULL) {
if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))) {
parseStory(doc, cur);
}
cur = cur->next;
}
- [1] 获取
cur
的第一个子节点。此时,cur
指向文档的根节点,也就是元素 “story”。 - [2] 这个循环遍历 “story” 的子元素,寻找名为 “storyinfo” 的元素。这个元素将包含我们需要的 “keywords”。它使用 libxml 的字符串比较函数
xmlStrcmp
。如果存在匹配项,则调用函数parseStory
。
void parseStory(xmlDocPtr doc, xmlNodePtr cur) {
xmlChar *key;
[1] cur = cur->xmlChildrenNode;
[2] while (cur != NULL) {
if ((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) {
[3] key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
printf("keyword: %s\n", key);
xmlFree(key);
}
cur = cur->next;
}
return;
}
- [1] 再次获取第一个子节点。
- [2] 类似于上面的循环,我们遍历节点,寻找一个与我们感兴趣的元素(在本例中是 “keyword” )匹配的节点。
- [3] 当找到 “keyword” 元素时,我们需要打印其内容。记住在 XML 中,包含在元素内的文本是该元素的子节点,因此我们转向
cur->xmlChildrenNode
。为了检索它,我们使用函数xmlNodeListGetString
,该函数也将doc
指针作为参数。在此示例中,我们只是将其打印出来。
注意:由于 xmlNodeListGetString
为返回的字符串分配了内存,因此必须使用 xmlFree
来释放它。
详细解析知识点
-
xmlDocPtr 和 xmlNodePtr
xmlDocPtr
是一个指向整个 XML 文档树的指针。xmlNodePtr
是一个指向单个节点的指针,节点可以是元素、属性、文本等。
-
遍历节点
cur = cur->xmlChildrenNode;
:获取当前节点的第一个子节点。cur = cur->next;
:移动到当前节点的下一个兄弟节点。
-
字符串比较
xmlStrcmp
:libxml2 提供的字符串比较函数,用于比较两个字符串是否相等。
-
获取节点内容
xmlNodeListGetString
:从节点列表中获取字符串内容。xmlFree
:释放xmlNodeListGetString
分配的内存,以避免内存泄漏。
5.使用XPath检索元素内容
除了通过遍历文档树来查找元素,Libxml2还支持使用XPath表达式来检索匹配指定条件的节点集。XPath允许通过文档搜索匹配指定条件的节点。在下面的例子中,我们搜索文档中的所有 “keyword” 元素的内容。
注意:XPath的全面讨论超出了本文档的范围。有关其使用的详细信息,请参阅XPath规范。
使用XPath检索元素内容的完整代码
要使用XPath,需要设置一个 xmlXPathContext
,然后将XPath表达式和上下文传递给 xmlXPathEvalExpression
函数。该函数返回一个 xmlXPathObjectPtr
,其中包含满足XPath表达式的节点集。
xmlXPathObjectPtr
getnodeset(xmlDocPtr doc, xmlChar *xpath) {
[1] xmlXPathContextPtr context;
xmlXPathObjectPtr result;
[2] context = xmlXPathNewContext(doc);
[3] result = xmlXPathEvalExpression(xpath, context);
[4] if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
xmlXPathFreeObject(result);
printf("No result\n");
return NULL;
}
}
-
[1] 声明变量:
- 首先声明变量。
-
[2] 初始化上下文变量:
- 使用
xmlXPathNewContext
初始化context
变量。
- 使用
-
[3] 应用XPath表达式:
- 使用
xmlXPathEvalExpression
应用XPath表达式。
- 使用
-
[4] 检查结果:
- 检查结果,如果未找到结果,则释放分配给结果的内存。
xmlXPathObjectPtr
由函数返回,包含一个节点集和其他需要的相关信息,用于遍历并处理结果。在这个例子中,我们的函数返回 xmlXPathObjectPtr
。我们使用它来打印文档中 “keyword” 节点的内容。节点集对象包括集合中的元素数量(nodeNr
)和一个节点数组(nodeTab
):
void printKeywords(xmlDocPtr doc, xmlXPathObjectPtr xpathObj) {
int i;
xmlChar *keyword;
xmlNodeSetPtr nodeset;
nodeset = xpathObj->nodesetval;
[1] for (i = 0; i < nodeset->nodeNr; i++) {
[2] keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, 1);
printf("keyword: %s\n", keyword);
xmlFree(keyword);
}
}
-
[1] 遍历节点集:
nodeset->nodeNr
持有节点集中的元素数量。我们使用它来遍历数组。
-
[2] 打印每个节点的内容:
- 打印每个返回节点的内容。注意,因为 “keyword” 元素的内容是子文本节点,我们实际上在打印返回节点的子节点。
详细解析知识点
-
XPath基础
- XPath是一种用于在XML文档中查找信息的语言,可用来遍历和处理XML文档。
-
上下文和表达式
xmlXPathContextPtr
:表示XPath上下文,用于在特定上下文中求值XPath表达式。xmlXPathEvalExpression
:用于在指定上下文中求值XPath表达式。
-
节点集
xmlXPathObjectPtr
:包含结果节点集的对象。nodeNr
:节点集中节点的数量。nodeTab
:指向节点的数组。
-
内存管理
xmlXPathFreeObject
:用于释放xmlXPathObjectPtr
对象分配的内存。xmlFree
:用于释放由xmlNodeListGetString
分配的字符串内存。
通过上述步骤,便可以使用XPath有效地查找并处理XML文档中的元素内容。这种方法比纯遍历更加简洁和高效。
6.写入元素内容
写入元素内容的过程与之前的检索过程有很多相似之处。我们需要解析文档并遍历树状结构。先解析文档,然后遍历树找到我们想要插入元素的位置。在这个示例中,我们仍然寻找 “storyinfo” 元素,并插入一个新的 keyword。之后,我们将把文件写回磁盘。
解析并添加元素
主要的不同在于 parseStory
函数:
void parseStory(xmlDocPtr doc, xmlNodePtr cur, char *keyword) {
[1] xmlNewTextChild(cur, NULL, "keyword", keyword);
return;
}
- [1] 使用
xmlNewTextChild
函数在当前节点cur
的位置添加一个新的子元素。
xmlNewTextChild
函数会在树的当前节点指针,指定由 cur
所在的位置添加一个新的子元素。在我们的例子中,这个新元素是带有关键字 keyword
的 “keyword” 元素。
保存文档到文件
在节点被添加后,我们需要将文档写回到文件中。如果你希望元素有命名空间,你也可以在此加入。在我们的例子中,命名空间为 NULL
。
xmlSaveFormatFile(docname, doc, 1);
- 第一个参数是要写入的文件名。在这个示例中,我们覆盖了刚刚读取的文件。
- 第二个参数是指向
xmlDoc
结构体的指针。 - 第三个参数设置为
1
以确保输出时的缩进。
详细解析知识点
-
xmlNewTextChild
- 用途:在指定节点下添加一个新的子节点。
- 参数:
cur
:目标节点指针。namespace
:命名空间指针,若无则传NULL
。name
:新元素的名称,在此例中为"keyword"
。content
:新元素的内容,在此例中为关键字keyword
。
-
xmlSaveFormatFile
- 用途:将 XML 文档保存到文件。
- 参数:
docname
:目标文件名。doc
:指向 XML 文档结构体的指针。format
:是否格式化输出,1
表示格式化。
完整代码示例
这里是完整代码的示例,展示了如何遍历文档树,插入关键字,并将文档写回文件:
#include <libxml/parser.h>
#include <libxml/tree.h>
void parseStory(xmlDocPtr doc, xmlNodePtr cur, char *keyword) {
xmlNewTextChild(cur, NULL, "keyword", keyword);
return;
}
void addKeywordToStory(const char *filename, const char *keyword) {
xmlDocPtr doc;
xmlNodePtr cur;
doc = xmlParseFile(filename);
if (doc == NULL) {
fprintf(stderr, "Document not parsed successfully. \n");
return;
}
cur = xmlDocGetRootElement(doc);
if (cur == NULL) {
fprintf(stderr, "empty document\n");
xmlFreeDoc(doc);
return;
}
if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
fprintf(stderr, "document of the wrong type, root node != story");
xmlFreeDoc(doc);
return;
}
cur = cur->children;
while (cur != NULL) {
if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))) {
parseStory(doc, cur, (char *)keyword);
break;
}
cur = cur->next;
}
xmlSaveFormatFile(filename, doc, 1);
xmlFreeDoc(doc);
return;
}
int main() {
addKeywordToStory("story.xml", "new_keyword");
return 0;
}
在上述代码中,我们定义了一个名为 addKeywordToStory
的函数,用于打开指定的 XML 文件,在 “storyinfo” 元素下添加一个新的 “keyword” 元素,并将更新的文档写回到文件中。
7.编写属性
编写属性类似于向新元素写入文本。在这里,我们将向文档中添加一个引用URI。完整代码见附录E,添加属性示例代码。
引用是story元素的子节点,因此找到要放置新元素和属性的位置很简单。一旦我们在parseDoc
中进行错误检查测试,我们就可以在合适的位置添加元素。但在此之前,我们需要声明一种本教程中尚未出现的数据类型:
[1] xmlAttrPtr newattr;
我们还需要一个额外的xmlNodePtr
:
[2] xmlNodePtr newnode;
parseDoc
的其余部分与之前相同,直到我们检查根元素是否为story。如果是,那么我们就处于添加元素的正确位置:
[3] newnode = xmlNewTextChild(cur, NULL, "reference", NULL);
[4] newattr = xmlNewProp(newnode, "uri", uri);
首先,我们在当前节点指针cur
的位置使用xmlNewTextChild
函数添加一个新节点。一旦添加了节点,文件写入磁盘的方式就像我们在前一个示例中添加带文本内容的元素一样。
详细解析知识点
-
声明
xmlAttrPtr
xmlAttrPtr
是指向属性结构的指针。在libxml2中,每个属性都使用该结构存储。
-
声明额外的
xmlNodePtr
xmlNodePtr
是指向单个节点的指针。在代码中,我们需要一个新的节点指针来添加新元素。
-
添加新节点
xmlNewTextChild(cur, NULL, "reference", NULL)
:这个函数在指定的父节点(在本例中是cur
)的子节点列表中创建一个新的文本节点。该函数接受四个参数:- 第一个参数是父节点指针。
- 第二个参数通常用于命名空间,这里为
NULL
。 - 第三个参数是新节点的名称(在本例中为"reference")。
- 第四个参数是新节点的文本内容,这里为
NULL
,表示没有文本。
-
添加属性
xmlNewProp(newnode, "uri", uri)
:该函数为指定节点(在本例中为newnode
)创建属性。该函数也接受三个参数:- 第一个参数是节点指针。
- 第二个参数是属性名称,在本例中为"uri"。
- 第三个参数是属性值,这里是变量
uri
。
8.检索属性值
检索属性值的过程类似于前例中检索节点文本内容的过程。在这种情况下,我们将提取先前添加的 URI 属性值。以下内容有一个完整的代码示例:附录 F,检索属性值的代码示例。
此示例的初始步骤类似于之前的示例:解析文档,找到你感兴趣的元素,然后进入一个函数来执行所需的特定任务。在这种情况下,我们调用 getReference
函数:
void getReference(xmlDocPtr doc, xmlNodePtr cur) {
xmlChar *uri;
cur = cur->xmlChildrenNode;
while (cur != NULL) {
if ((!xmlStrcmp(cur->name, (const xmlChar *)"reference"))) {
[1] uri = xmlGetProp(cur, "uri");
printf("uri: %s\n", uri);
xmlFree(uri);
}
cur = cur->next;
}
return;
}
- [1] 关键函数是
xmlGetProp
,它返回一个包含属性值的xmlChar
。在这种情况下,我们只需打印它。
注意:如果你正在使用声明了固定或默认属性值的 DTD(文档类型定义),此函数将检索到该值。
详细解析知识点
-
xmlGetProp 函数
xmlGetProp
是 libxml2 提供的一个函数,用于获取节点属性的值。- 它接受两个参数:一个是节点指针(
cur
),另一个是属性名(在本例中是"uri"
)。 - 返回值是一个
xmlChar*
类型的指针,指向属性的值。
-
遍历节点
- 和之前的例子一样,使用
cur = cur->xmlChildrenNode
获取子节点。 - 使用
cur = cur->next
来遍历兄弟节点。
- 和之前的例子一样,使用
-
字符串比较
xmlStrcmp
:用于比较两个字符串是否相等,判断当前节点是否是名为 “reference” 的节点。
-
打印并释放内存
- 将获取到的属性值打印出来:
printf("uri: %s\n", uri);
- 由于
xmlGetProp
为返回的字符串分配了内存,因此必须使用xmlFree
来释放该内存,以避免内存泄漏。
- 将获取到的属性值打印出来:
这样,你就可以成功地检索和使用 XML 文档中的属性值了。
附录A:简单xml文档
<?xml version="1.0"?>
<story>
<storyinfo>
<author>John Fleck</author>
<datewritten>June 2, 2002</datewritten>
<keyword>example keyword</keyword>
</storyinfo>
<body>
<headline>This is the headline</headline>
<para>This is the body text.</para>
</body>
</story>
附录B:Keyword例子的代码
#include <stdio.h> // 标准输入输出库头文件
#include <string.h> // 字符串处理库头文件
#include <stdlib.h> // 通用工具库头文件
#include <libxml/xmlmemory.h> // libxml内存处理头文件
#include <libxml/parser.h> // libxml解析库头文件
// 解析"story"元素内的内容
void parseStory(xmlDocPtr doc, xmlNodePtr cur) {
// xmlChar是libxml2定义的数据类型,表示一个XML字符串
xmlChar *key;
// 获取当前节点的子节点
cur = cur->xmlChildrenNode;
// 遍历子节点
while (cur != NULL) {
// 如果节点名称是"keyword"
if ((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) {
// 获取节点内容
key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
// 打印关键词内容
printf("keyword: %s\n", key);
// 释放内存
xmlFree(key);
}
// 指向下一个子节点
cur = cur->next;
}
return;
}
// 解析并处理XML文档
static void parseDoc(char *docname) {
// xmlDocPtr表示XML文档指针
xmlDocPtr doc;
// xmlNodePtr表示XML节点指针
xmlNodePtr cur;
// 解析XML文件,返回文档指针
doc = xmlParseFile(docname);
// 如果文档解析失败
if (doc == NULL) {
fprintf(stderr, "Document not parsed successfully. \n");
return;
}
// 获取根节点
cur = xmlDocGetRootElement(doc);
// 如果根节点为空
if (cur == NULL) {
fprintf(stderr, "empty document\n");
// 释放文档内存
xmlFreeDoc(doc);
return;
}
// 判断根节点名称是否为"story"
if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
fprintf(stderr, "document of the wrong type, root node != story");
// 释放文档内存
xmlFreeDoc(doc);
return;
}
// 获取第一个子节点
cur = cur->xmlChildrenNode;
// 遍历子节点
while (cur != NULL) {
// 如果子节点名称为"storyinfo"
if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))) {
// 解析"story"元素内容
parseStory(doc, cur);
}
// 指向下一个子节点
cur = cur->next;
}
// 释放文档内存
xmlFreeDoc(doc);
return;
}
int main(int argc, char **argv) {
char *docname;
// 检查命令行参数
if (argc <= 1) {
printf("Usage: %s docname\n", argv[0]);
return 0;
}
// 获取XML文档文件名
docname = argv[1];
// 调用文档解析函数
parseDoc(docname);
return 1;
}
附录C:XPath例子的代码
以下是添加了详细注释的代码示例:
#include <libxml/parser.h> // 引入libxml库的解析器功能
#include <libxml/xpath.h> // 引入libxml库的XPath功能
// 函数:getdoc
// 用途:解析XML文档并生成xmlDoc对象
// 参数:docname - XML文件的路径
// 返回值:xmlDoc对象的指针,失败时返回NULL
xmlDocPtr getdoc (char *docname) {
xmlDocPtr doc;
// 解析XML文件
doc = xmlParseFile(docname);
if (doc == NULL) {
// 如果解析失败,输出错误信息并返回NULL
fprintf(stderr, "Document not parsed successfully. \n");
return NULL;
}
return doc;
}
// 函数:getnodeset
// 用途:根据传入的XPath表达式从XML文档中获取节点集
// 参数:doc - XML文档对象
// xpath - XPath表达式
// 返回值:XPath对象的指针,失败时返回NULL
xmlXPathObjectPtr getnodeset (xmlDocPtr doc, xmlChar *xpath) {
xmlXPathContextPtr context;
xmlXPathObjectPtr result;
// 创建一个XPath上下文
context = xmlXPathNewContext(doc);
if (context == NULL) {
// 如果创建失败,输出错误信息并返回NULL
printf("Error in xmlXPathNewContext\n");
return NULL;
}
// 评估XPath表达式
result = xmlXPathEvalExpression(xpath, context);
// 释放XPath上下文
xmlXPathFreeContext(context);
if (result == NULL) {
// 如果评估失败,输出错误信息并返回NULL
printf("Error in xmlXPathEvalExpression\n");
return NULL;
}
if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
// 如果得到的节点集为空,释放XPath对象并返回NULL
xmlXPathFreeObject(result);
printf("No result\n");
return NULL;
}
return result;
}
// 函数:main
// 用途:主函数,程序入口
int main(int argc, char **argv) {
char *docname; // XML文件名称
xmlDocPtr doc; // XML文档对象
xmlChar *xpath = (xmlChar*) "//keyword"; // XPath表达式,选择所有keyword节点
xmlNodeSetPtr nodeset; // 节点集对象
xmlXPathObjectPtr result; // XPath评估结果对象
int i;
xmlChar *keyword; // 用于存储节点内容数据
// 检查程序参数
if (argc <= 1) {
// 如果没有传入文档名,输出用法信息并结束程序
printf("Usage: %s docname\n", argv[0]);
return 0;
}
// 获取XML文档名
docname = argv[1];
// 解析XML文档
doc = getdoc(docname);
// 使用XPath表达式获取节点集
result = getnodeset(doc, xpath);
if (result) {
nodeset = result->nodesetval;
// 遍历节点集
for (i = 0; i < nodeset->nodeNr; i++) {
// 获取节点的数据
keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, 1);
// 打印节点数据
printf("keyword: %s\n", keyword);
// 释放节点数据
xmlFree(keyword);
}
// 释放节点集对象
xmlXPathFreeObject(result);
}
// 释放XML文档对象
xmlFreeDoc(doc);
// 清理libxml库
xmlCleanupParser();
return 1;
}
附录D:添加Keyword例子的代码
#include <stdio.h> // 标准输入输出库
#include <string.h> // C字符串操作库
#include <stdlib.h> // 通用工具库
#include <libxml/xmlmemory.h> // libxml内存操作库
#include <libxml/parser.h> // libxml解析器库
// 函数 parseStory 用于在给定的XML节点下添加一个新的子节点 "keyword"
void parseStory(xmlDocPtr doc, xmlNodePtr cur, char *keyword) {
// xmlNewTextChild 函数为节点 cur 添加新的子节点 "keyword",其内容为传入的 keyword 字符串
xmlNewTextChild(cur, NULL, BAD_CAST "keyword", BAD_CAST keyword);
return;
}
// 函数 parseDoc 用于解析XML文件,找到特定的节点并调用 parseStory 函数添加子节点
xmlDocPtr parseDoc(char *docname, char *keyword) {
xmlDocPtr doc; // XML文档指针
xmlNodePtr cur; // 当前节点指针
// 解析XML文件 docname,如果解析失败则返回 NULL
doc = xmlParseFile(docname);
if (doc == NULL) {
fprintf(stderr, "Document not parsed successfully. \n");
return NULL;
}
// 获取文档的根节点
cur = xmlDocGetRootElement(doc);
// 如果根节点为空,说明文档是空的,释放文档并返回 NULL
if (cur == NULL) {
fprintf(stderr, "empty document\n");
xmlFreeDoc(doc);
return NULL;
}
// 比较根节点的名称是否为 "story",如果不是,释放文档并返回 NULL
if (xmlStrcmp(cur->name, BAD_CAST "story")) {
fprintf(stderr, "document of the wrong type, root node != story");
xmlFreeDoc(doc);
return NULL;
}
// 遍历根节点的所有子节点
cur = cur->xmlChildrenNode;
while (cur != NULL) {
// 查找名称为 "storyinfo" 的节点,并调用 parseStory 添加 "keyword" 节点
if ((!xmlStrcmp(cur->name, BAD_CAST "storyinfo"))) {
parseStory(doc, cur, keyword);
}
cur = cur->next; // 移动到下一个子节点
}
return doc; // 返回处理后的文档
}
int main(int argc, char **argv) {
char *docname; // 传入的文档名称
char *keyword; // 传入的关键词
xmlDocPtr doc; // XML文档指针
// 检查参数数量,如果参数不足2个,打印用法并退出
if (argc <= 2) {
printf("Usage: %s docname, keyword\n", argv[0]);
return 0;
}
// 获取命令行参数
docname = argv[1];
keyword = argv[2];
// 解析并处理XML文档
doc = parseDoc(docname, keyword);
// 如果文档不为空,保存处理后的文档并释放内存
if (doc != NULL) {
xmlSaveFormatFile(docname, doc, 0); // 将修改后的文档保存回同一文件
xmlFreeDoc(doc); // 释放文档内存
}
return 1;
}
附录E:添加属性例子的代码
#include <stdio.h> // 标准输入输出库, 用于文件输入输出
#include <string.h> // 字符串操作库
#include <stdlib.h> // 标准库,提供内存分配、进程控制、转换等函数
#include <libxml/xmlmemory.h> // libxml的内存管理头文件
#include <libxml/parser.h> // libxml的解析头文件
/**
* 解析XML文档,并在根节点中添加一个新的子节点。
* @param docname 输入的XML文件名
* @param uri 新节点的uri属性值
* @return 返回处理后的xmlDoc指针,失败返回NULL
*/
xmlDocPtr parseDoc(char *docname, char *uri) {
xmlDocPtr doc; // xmlDocPtr是libxml库中定义的xml文档指针类型
xmlNodePtr cur; // xmlNodePtr是libxml库中定义的xml节点指针类型
xmlNodePtr newnode; // 储存新添加的xml节点指针
xmlAttrPtr newattr; // xmlAttrPtr是libxml库中定义的xml属性指针类型
// 解析xml文件
doc = xmlParseFile(docname);
if (doc == NULL) {
fprintf(stderr, "Document not parsed successfully. \n");
return (NULL);
}
// 获取文档的根节点
cur = xmlDocGetRootElement(doc);
if (cur == NULL) {
fprintf(stderr, "empty document\n");
xmlFreeDoc(doc); // 释放文档对象内存
return (NULL);
}
// 检查根节点名称是否为"story"
if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
fprintf(stderr, "document of the wrong type, root node != story");
xmlFreeDoc(doc); // 释放文档对象内存
return (NULL);
}
// 创建一个新的子节点,节点名称为"reference"
newnode = xmlNewTextChild(cur, NULL, (const xmlChar *) "reference", NULL);
// 为新节点添加属性,属性名为"uri",值为传入的`uri`
newattr = xmlNewProp(newnode, (const xmlChar *) "uri", (const xmlChar *) uri);
// 返回修改后的文档对象
return(doc);
}
int main(int argc, char **argv) {
char *docname; // xml文档文件名
char *uri; // 新节点的uri属性值
xmlDocPtr doc; // xml文档指针
// 检查命令行参数个数,至少需要两个参数:文件名和uri值
if (argc <= 2) {
printf("Usage: %s docname, uri\n", argv[0]);
return(0);
}
// 将命令行参数赋值给变量
docname = argv[1];
uri = argv[2];
// 解析文档并进行修改
doc = parseDoc(docname, uri);
// 如果文档解析成功并进行了修改
if (doc != NULL) {
// 将修改后的文档保存到文件中
xmlSaveFormatFile(docname, doc, 1);
// 释放文档对象内存
xmlFreeDoc(doc);
}
return (1);
}
附录F:获取属性值例子的代码
// 包含标准输入输出库
#include <stdio.h>
// 包含处理字符串的库
#include <string.h>
// 包含标准库以便使用内存分配等功能
#include <stdlib.h>
// 包含 libxml2 的内存管理功能
#include <libxml/xmlmemory.h>
// 包含 libxml2 的解析功能
#include <libxml/parser.h>
// 给定一个 XML 文档的指针和当前节点的指针,从当前节点的子节点中解析 "reference" 并打印其中的 "uri" 属性
void getReference (xmlDocPtr doc, xmlNodePtr cur) {
// 定义一个指向字符数据的指针,用于存储属性 "uri"
xmlChar *uri;
// 移动到当前节点的第一个子节点
cur = cur->xmlChildrenNode;
// 遍历当前节点的所有子节点
while (cur != NULL) {
// 如果当前节点的名称是 "reference"(比较 XML 字符串名称)
if ((!xmlStrcmp(cur->name, (const xmlChar *)"reference"))) {
// 获取当前节点的 "uri" 属性
uri = xmlGetProp(cur, "uri");
// 打印 "uri" 属性的值
printf("uri: %s\n", uri);
// 释放 "uri" 属性的内存
xmlFree(uri);
}
// 移动到下一个兄弟节点
cur = cur->next;
}
return;
}
// 解析给定的 XML 文件并处理其内容
void parseDoc(char *docname) {
// 定义一个指向 XML 文档的指针
xmlDocPtr doc;
// 定义一个指向 XML 节点的指针
xmlNodePtr cur;
// 解析给定的 XML 文件
doc = xmlParseFile(docname);
// 如果解析失败
if (doc == NULL ) {
// 向标准错误输出流打印错误信息
fprintf(stderr,"Document not parsed successfully. \n");
return;
}
// 获取文档的根节点
cur = xmlDocGetRootElement(doc);
// 如果根节点为空
if (cur == NULL) {
// 向标准错误输出流打印错误信息
fprintf(stderr,"empty document\n");
// 释放 XML 文档的内存
xmlFreeDoc(doc);
return;
}
// 如果根节点的名称不是 "story"
if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
// 向标准错误输出流打印错误信息
fprintf(stderr,"document of the wrong type, root node != story");
// 释放 XML 文档的内存
xmlFreeDoc(doc);
return;
}
// 解析 "reference" 节点的 "uri" 属性
getReference (doc, cur);
// 释放 XML 文档的内存
xmlFreeDoc(doc);
return;
}
// 程序的主函数
int main(int argc, char **argv) {
// 定义一个指向文档名称的字符指针
char *docname;
// 如果参数数量小于等于1,则说明没有提供文件名
if (argc <= 1) {
// 打印用法信息
printf("Usage: %s docname\n", argv[0]);
return(0);
}
// 获取文档名称
docname = argv[1];
// 解析文档
parseDoc (docname);
return (1);
}
附录G:致谢
许多人慷慨地提供了反馈、代码和建议以改进本教程。以下名单排名不分先后:Daniel Veillard、Marcus Labib Iskander、Christopher R. Harris、Igor Zlatkovic、Niraj Tolia、David Turover