Linux下使用LibXML2处理XML文件

来源:互联网 发布:ubuntu 根目录扩容 编辑:程序博客网 时间:2024/05/10 13:20

引言   

要创建完善的、高度互操作性的应用程序,XML 是一个很好的选择,因为它正越来越广泛地应用于数据存储和配置文件管理。本文研究了一个使用 XML(可扩展标记语言)作为其配置文件格式的示例应用程序,并通过该示例向您介绍如何在自己的 UNIX 应用程序中使用 XML。该示例应用程序使用 Perl 编写,并且其中使用了基于 Gnome 项目的 LibXML2 库的 Perl 模块。   

在给出 XML 的简单定义之后,本文介绍了一个使用 XML 编写的示例配置文件。然后,通过示例代码来介绍如何解析这个配置文件。系统管理员可以手动修改该配置文件,但通常在一定程度上,需要应用程序直接地修改该配置文件。然后,本文通过一个示例介绍如何以编程的方式向这个 XML 文档添加新的配置选项,以及如何修改当前条目的值。最后,本文介绍了将这个经过修改的配置文件写入到磁盘的代码。   

关于 XML在开始研究 LibXML2 库之前,让我们先来巩固一下 XML 的相关基础。XML 是一种基于文本的格式,它可用来创建能够通过各种语言和平台访问的结构化数据。它包括一系列类似 HTML 的标记,并以树型结构来对这些标记进行排列。例如,可参见清单 1中介绍的简单文档。这是配置文件部分中研究的配置文件示例的简化版本。为了更清楚地显示 XML 的一般概念,所以对其进行了简化。   

清单 1. 一个简单的 XML 文件 view plaincopy to clipboardprint?
<?xml version="1.0" encoding="UTF-8"?>  
            <files>  
            <owner>root</owner>  
            <action>delete</action>  
            <age units="days">10</age>  
            </files> 

清单 1中的第一行是 XML 声明,它告诉负责处理 XML 的应用程序,即解析器,将要处理的 XML 的版本。大部分的文件使用版本 1.0 编写,但也有少量的版本 1.1 的文件。它还定义了所使用的编码。大部分文件使用 UTF-8,但是,XML 设计用来集成各种语言中的数据,包括那些不使用英语字母的语言。 接下来出现的是元素。一个元素以开始标记开始(如 ),并以结束标记结束(如 ),其中使用斜线 (/) 来区别于开始标记。元素是 Node的一种类型。XML 文档对象模型 (DOM) 定义了几种不同的 Nodes类型,包括Elements(如 files或者 age)、Attributes(如 units)和 Text(如 root或者 10)。元素可以具有子节点。例如,age 元素有一个子元素,即文本节点 10。而 files 元素有七个子元素。其中三个很明显。它们分别是三个子元素:owner、action和 age。其他四个分别是元素前后的空白文本符号。XML 解析器可以利用这种父子结构来遍历文档,甚至修改文档的结构或内容。LibXML2 是这样的解析器中的其中一种,并且文中的示例应用程序正是使用这种结构来实现该目的。对于各种不同的环境,有许多不同的解析器和库。LibXML2 是用于 UNIX 环境的解析器和库中最好的一种,并且经过扩展,它提供了对几种脚本语言的支持,如 Perl 和 Python。 作为研究工作的第一步,让我们先来看一个示例配置文件。配置文件文中的示例应用程序读取了特定文件的操作列表。而配置文件定义了这些文件和操作。假设该配置文件是位于 UNIX 文件系统中某个位置的一个文件。例如,在 UNIX 系统 cron 中可能会使用这个配置文件。该 XML 文件定义了目录路径和根据条件要执行的操作,而这些条件则包括所有者和文件存在时间(请参见清单 2)。   

清单 2. 配置文件 view plaincopy to clipboardprint?

<?xml version="1.0" encoding="UTF-8"?>  
            <filesystem>  
            <path>  
            <dirname>/var</dirname>  
            <files>  
            <owner>root</owner>  
            <action>delete</action>   
            <age units="days">10</age>   
            </files>  

            <files>  
            <owner>any</owner>  
            <action>delete</action>  
            <age units="hours">96</age>  
            </files>  
            </path>  


            <path>  
            <dirname>/tmp</dirname>  
            <files>  
            <owner>any</owner>  
            <action>delete</action>  
            <age units="hours">24</age>  
            </files>  
            </path>  
            </filesystem> 

 在本例中,根元素是 filesystem,它包含两个 path元素。每个 path元素包含相应的目录名和一个或多个 files元素。每个 files元素通过 age元素的 units属性中指定的存在时间单位,定义了当用户或用户文件达到特定的存在时间时,应用程序应采取的操作。请记住,空白符号是有意义的。从结构的观点来看,每个空白符号组成了独立的 Text节点。在产品环境中,一个编写完善的 UNIX 应用程序不仅应该具有读取数据并对其进行操作的能力,而且还应具有根据用户输入对数据进行添加、删除和修改的能力。   

现在让我们来研究使用该数据的应用程序。示例程序本文余下的内容通过示例代码介绍对 XML 配置文件的解析和管理。这些示例逐一地读取并修改配置文件,但在 UNIX 开发人员的日常工作中,您可以在任何类型的任务中使用这些概念。而且,因为使用了 LibXML2 库,所以您可以将这些概念插入到几乎任何的 UNIX 应用程序中。我们将在本文中介绍使用 Perl 版本的 LibXML2 库的示例。Internet 上的大部分文档都在讨论如何使用 Java 或 Microsoft Visual Studio 工具进行编程,但对于 UNIX 用户或开发人员来说,Perl 则更有价值。清单 3显示了解析该 XML 文档所需的 Perl 模块。清单 3. 需要的库

XML::LibXML   

XML::LibXML::Common   

XML::NamespaceSupport   

XML::SAX      

下面部分中介绍的代码仅仅只是一个框架。可分三个部分对其进行介绍:解析、操作和导出。   

在加载和解析阶段中,可能会将数据加载到 Perl 变量中,如列表或哈希,但由于每个程序员都有他/她自己首选的方法来完成这项任务,所以我们把它留给读者作为一项练习。下面的代码只是简单地显示了数据,以此说明该脚本正确地找到了相应的数据。  

在操作阶段中,该程序对 XML 文档中的元素进行添加、修改和删除的数据更新操作。通常地,这将按照用户的操作来进行。  最后在导出阶段中,将经过修改的最终的文档写回到磁盘。

加载和解析数据对于应用程序来说,读取 XML 文件的第一步是加载该数据并将其解析为一个 Document对象。在此基础上,可以对 DOM 树进行遍历以获取特定的节点。让我们来看看清单 4中的代码是如何完成该任务的。  

清单 4. 加载和解析 example.xml 的代码   

my $parser = XML::LibXML->new();   
my $doc = $parser->parse_file("example.xml");   
$filesystem = $doc->getDocumentElement();   
@nodes=$filesystem->childNodes;   
foreach $node (@nodes) {   
if($node->nodeType==ELEMENT_NODE) { 
# ignore text nodes   
# just get the first match   
@dirnames = $node->getElementsByTagName("dirname")->item(0);   
foreach $dirname (@dirnames) {   
print "dirname: ". $dirname->textContent . "\n";   # push this into an array   
}   
# get all children   
@files = $node->getChildrenByTagName("files");   
foreach $file (@files) {   
foreach $values ($file->childNodes) {   
# ignore text nodes   
if($values->nodeType!=XML_TEXT_NODE) {   
if($values->nodeName() eq "age") {   
# check for attribute, otherwise, use default of 'hours'   
if($values->hasAttributes()) {   
print $values->nodeName() . ": ". $values->textContent;   
print "". $values->attributes->item(0)->value();   
print "\n";   

else {   
print $values->nodeName() . ": ". $values->textContent;   
print "hours\n";   
}   
# calculate extended value from units and put in a   
# hash linked with this dirname, etc.   
}
else {   
print $values->nodeName() . ": ". $values->textContent;   
print "\n";   
# put this value into a hash linked with $dirname.   
# We may have multiple entries for each $dirname, so   
# perhaps use an array within a hash   
}   
}   
}   
}   
}  
}

首先,在清单 4中,创建了解析器并将 XML 从文件加载到 XML::LibXML::Document变量。这个对象包含了整个 XML 树,并且具有与之关联的各种方法可用来搜索节点、导出、验证和创建新的节点。本文将在后面的几个部分中对其中的一些方法进行介绍。从代码的起始处开始,您可以看到 getDocumentElement()方法,它用于返回文档的根节点。从这个根节点,就可以遍历整个 XML 树。 主foreach循环对父 filesystem元素中的每个节点进行循环。当仅选择元素节点时,该循环将得到 path 元素。getElementsByTagName()方法根据相应的名称在节点中搜索对应的元素,并通过 NodeList对象返回它们。每个 path元素包含了一个 dirname元素,所以代码搜索名称为 dirname的元素,并获取其中的第一个条目。在代码中只能选择 ELEMENT 类型的节点,因为该方法不支持 TEXT 节点,并且会在 Perl 中产生一个不可恢复的错误。在一个 path 元素中可能存在多个 files元素,所以代码对 getChildrenByTagName()方法的每一个元素进行循环,这个方法类似于 getElementsByTagName(),但仅搜索目标节点的直接子节点。这将返回所有的 files元素,但必须进行进一步的解析以获得 owner、action和 age元素。在得到了这些节点之后,可以调用 textContent以从相应的元素中获取实际的值。下面显示的是选择 ELEMENT 节点孩子的 TEXT 节点值的快捷方法: 

print $values->nodeName() . ": "   

print $values->firstChild()->nodeValue(); 

在使用 age元素的情况下,还可以通过一个属性来指定时间单位。使用 hasAttributes()和 Attributes函数,该程序可以提取相应的属性,如果它存在话。如果它不存在,那么该程序使用缺省值 hours。

现在让我们来介绍对数据的操作,这样就能够以编程的方式来添加、删除和编辑操作。对数据进行操作现有的代码本身就是一个有用的程序。用户可以很容易地以手动地方式对该 XML 文件完成程序所做的修改。然而,有经验的 UNIX 开发人员还可以使用 XML 函数在程序中直接对文件进行修改。例如,可以包含用于添加新的操作或删除现有操作的菜单选项。要实现这个目的,让我们来看看在程序中对数据进行操作的代码。   

清单 5. 向文档添加填充的 path 节点   

$newnode = $doc->createElement("path");   
$newdirnode = $doc->createElement("dirname");   
$newdirnode->appendText("/root");   
$newfilesnode = $doc->createElement("files");   
$newownernode = $doc->createElement("owner");  
$newownernode->appendText("any");   
$newactionnode = $doc->createElement("action");   
$newactionnode->appendText("archive");   
$newagenode = $doc->createElement("age");   
$newagenode->appendText("30");   
$newagenode->setAttribute("units","days");   
$newfilesnode->addChild($newownernode);   
$newfilesnode->addChild($newactionnode);   
$newfilesnode->addChild($newagenode);   
$newnode->addChild($newdirnode);   
$newnode->addChild($newfilesnode);   
$filesystem->addChild($newnode);

清单 5中的代码创建了一个 path元素,并为其填充了所有的元素。然后,将这个新创建的节点添加到根元素,即 filesystem。需要使用 XML::LibXML::Document类的 createElement()方法来创建每个元素。(正是 Document创建了您所需要的任何新的节点。)该方法返回一个尚未连接到文档树中任何位置的空节点。然后可以使用 XML::LibXML::Element 类的 appendText()方法为每个节点添加内容。此外,相对于创建一个新的 TEXT 节点,然后对其进行填充并将其添加到相应的元素,这是一种快捷的方法。可以使用 setAttribute()方法来添加属性,如果在目标元素中不存在给定名称的属性,该方法将自动创建一个新的 ATTRIBUTE节点。在完成每个节点的创建并分别对它们进行了填充之后,可以使用要求的子节点作为参数,对父节点调用 addChild()方法。因此在上面的代码中,$newownernode成为了 $newfilesnode的子节点。文档中所有的节点保持其添加时的顺序。如果您希望指定其他的顺序,可以使用 insertAfter()或 insertBefore()函数。将每个节点添加到相应的父节点,直到最后将主父节点添加到已经存在于文档中的一个节点。在上面的示例中,将该节点添加到了 filesystem根节点。(如果您是从头开始创建该文档,那么可以对 Document对象本身调用 addChild()来添加根元素,然后再向该元素添加任何其他的节点。)正如前面所解释的,清单 2中的示例 XML 代码是一种可读的格式。换行和缩进使得文档更容易阅读。XML 解析器将读取所有这些字符,并将其作为一个 TEXT 类型的节点。清单 5中的示例没有添加任何这样的 TEXT 节点。因此,该示例的输出将不包含任何换行或缩进。如果您希望创建这种空白字符,那么需要使用 XML::LibXML::Text类来创建 TEXT 类型的节点,或者使用该文档对象的 createTextNode()函数。该构造函数的返回值是一个节点,可以使用与上面示例中相同的方式将其添加到树中。 要更改文件的内容,可以直接设置相关 TEXT 节点的 nodeValue(),或者替换整个元素:   

$newnode = $doc->createElement("owner");   

$newnode->appendText("toor");   

$oldnode->replaceNode($newnode);     

要删除一个节点,有以下几种选择。一种方法是仅将其从结构中删除,代码如下所示:   

$file->unbindNode();     

在找到需要删除的节点之后,通过一行命令即可将其从结构中删除,但并没有从文档中删除。这个函数调用直到程序结束时才真正销毁该数据结构。如果您需要将节点移动到树中的其他部分,那么可以使用相同的变量来调用 addNode()以将其重新添加到文档中新的位置。您还可以使用 removeChild()或 removeChildNodes()函数,这样可以从文档中彻底地释放相应的资源。保存 XML 文件在一些编程语言中,将 XML 文档保存到文件中可能比较烦琐,但幸运的是,LibXML 让这项任务变得非常简单:   

$doc->toFile("example.xml");     

在对数据进行的所有的操作中,这是最简单的一种操作。在对内存中的 XML 文档完成了相应的修改之后,只需使用一个函数调用就可以将其写回到对应的配置文件中。还可以使用相关的 Perl 函数,如 toString()和 toFH(),这些函数分别将 XML 输出到一个字符串变量或者一个已打开的 Perl 文件句柄,而文件句柄将为您的应用程序的构建带来更大的灵活性。   

结束语   

通过提供 LibXML2 库以及对 Perl 模块的支持,Gnome 项目完成了一项很有价值的任务。本文对管理和使用 XML 配置文件所需要的三个重要的步骤进行了介绍。解析阶段可能是最复杂的,因为它需要一定程度的递归设计来解析 XML 树。尽管有些烦琐,但对内存中 XML 文档的操作却是非常简单明了的。使用 LibXML2 库导出经过修改的配置,也是非常容易的。   尽管相对于标准的UNIX 思维方式来说,需要进行一定的思维模式转移,但是 XML 可以为数据管理提供一种功能强大的方法。与简单的数据库格式相比,树型结构提供了更加灵活的数据视图。在开发新的应用程序或修改旧的应用程序时,可将其规范化为使用 XML 配置文件,在进行规范化的过程中可以很容易地使用 Gnome 项目所提供的免费的标准库,正如本文所介绍的。 本文转自http://www.chinaitpower.com/2006Aug/2006-08-25/212810.html

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/alex_yan/archive/2008/07/07/2622296.aspx

0 0
原创粉丝点击