跳转到内容

XML - 管理数据交换/解析 XML 文件

来自 Wikibooks,开放世界的开放书籍



上一章 下一章
Cocoon XUL



学习目标

  • 理解解析 XML 文件的概念
  • 使用不同的 API 处理 XML 文件
  • 了解不同解析 XML 文件方法之间的差异
  • 决定何时使用特定技术


在前面的章节中,我们学习了如何创建 XML 文件 的详细内容。这包括开发 XML 文档样式表模式 以及它们的验证。在本节中,我们将重点介绍解析 XML 文件 的不同方法以及何时使用它们。

但首先,是时候回顾一下我们所学的关于解析的知识了。

解析 XML 文件的过程

[编辑 | 编辑源代码]

XML 格式的目标之一是通过包含对内容含义的详细描述来增强纯文本等原始数据格式。现在,为了能够读取 XML 文件,我们使用一个解析器,它基本上通过一个称为 API(应用程序编程接口)来公开文档的内容。换句话说,客户端应用程序通过接口访问 XML 文档的内容,而不是自行解释 XML 代码!

简单文本解析

[编辑 | 编辑源代码]

从 XML 文档中提取数据的一种方法是简单的文本解析 - 浏览文档中的所有字符并检查所需的模式


<house>
<value><int>150,000</int></value>
</house>

假设我们对房子的价值感兴趣。使用直接文本解析,我们将扫描文件以查找字符序列 <int> 并将其称为起始模式。然后,我们将进一步扫描文档以查找结束模式(即 </int></value>)。最后,我们将这两个模式之间的文本字符串声明为周围 <house>...</house> 标签的价值。

为什么不这样工作

[编辑 | 编辑源代码]

显然,这种方法不适合从大型复杂的 XML 文档中提取信息,因为我们必须确切地知道文件的样子以及所需信息的位置。从更一般的角度来看,XML 文件的结构和语义由文档的构成、其标签和属性决定 - 因此,我们需要一种能够识别和理解这种结构并能够指出其中的任何错误的设备。此外,它必须通过接口提供文档的内容,以便其他应用程序可以轻松访问它。这种设备被称为 XML 解析器。

解析器做什么

[编辑 | 编辑源代码]

几乎所有需要处理 XML 文档的程序都使用 XML 解析器来提取存储在 XML 文档中的信息,以避免读取和解释原始 XML 数据时遇到的任何困难。解析器通常是一个类库(例如一组 Java 类文件),它读取给定文档并检查它是否符合 W3C 规范。然后,任何客户端软件都可以使用解析器 API 提供的接口方法来访问解析器从 XML 文件中检索的信息。

总而言之,解析器保护用户免受处理 XML 的复杂细节的困扰,例如组装分布在多个 XML 文件中的信息、检查格式良好的约束等等。

解析:一个例子

[编辑 | 编辑源代码]

为了更清楚地说明解析 XML 文件的真正含义,创建了以下示例,其中包含有关一些城市的信息。它还跟踪谁在度假,并使用当前最常见的解析方法演示解析过程。

示例:cities.xml

[编辑 | 编辑源代码]
<?xml version="1.0" encoding="UTF-8" ?>
<cities>
<city vacation="Sam">
<cityName>Atlanta</cityName>
<cityCountry>USA</cityCountry> 
</city>
<city vacation="David">
<cityName>Sydney</cityName>
<cityCountry>Australia</cityCountry> 
</city>
<city vacation="Pune">
<cityName>Athens</cityName>
<cityCountry>Greece</cityCountry> 
</city>
</cities>

基于存储在这个 XML 文档中的信息,我们可以轻松地检查谁在度假以及在哪里。解析器将使用本章后面介绍的各种技术之一读取文件。

这个过程非常复杂,容易出现各种错误。幸运的是,我们永远不必编写代码,因为网上有大量免费的、功能齐全的解析器。我们所做的就是下载一个解析器类库,并通过解析器软件提供的接口访问 XML 文档。随着 Java 的更新版本发布,大多数解析器甚至不需要下载。换句话说,我们使用类库中包含的函数或方法来提取信息。

基本上,解析器读取 XML 文档,并尝试识别文件本身的结构,同时检查错误。它只是检查开始/结束标签、属性、命名空间、前缀等等。然后,客户端软件可以使用解析器软件(即接口)提供的方

了解解析器功能的最佳方法是实际使用它们;因此,下一节将演示不同的解析方法。

解析器 API(应用程序编程接口)

[编辑 | 编辑源代码]

目前市场上占主导地位的是两种“传统”方法:一种是基于事件的推模型,以 SAX (Simple API for XML) 为代表;另一种是基于树的模型,使用 DOM (Document Object Model) 方法。

然而,现在正在兴起一些新方法和技术,试图克服这些传统模型固有的缺陷——一种基于事件的拉模型和一种“游标模型”,例如 VTD-XML,它允许我们像在基于树的方法中一样浏览 XML 文档,但更简单、更容易使用。

SAX (Simple API for XML)

[编辑 | 编辑源代码]

推模型,通常以 SAX (www.saxproject.org) 为例,是 XML 解析的“黄金标准”,因为它可能是迄今为止最完整、最准确的方法。SAX 类在 XML 文档读取的输入流和接收解析器提供的数据的客户端软件之间提供了一个接口。解析器浏览整个文档,并在每次识别 XML 结构时触发事件(例如,它识别开始标签并触发事件——客户端软件收到通知并可以使用此信息……或不使用)。

这种模型的优势是我们不需要将整个 XML 文档存储在内存中,因为我们一次只读取一个信息片段。如果您还记得 XML 结构是一组不同类型的节点(如元素节点)——使用 SAX 解析器解析文档意味着一次遍历每个节点。这使得我们能够以内存高效的方式读取即使非常大的 XML 文档。但是,解析器只提供有关当前读取的节点的信息这一事实也意味着客户端软件的程序员负责将某些信息保存在单独的数据结构中(例如,当前处理的节点的父节点或子节点)。此外,SAX 方法几乎是只读的,因为当我们没有某种全局视图时,很难修改 XML 结构。

实际上,解析器控制着何时读取。用户只能等到某个事件发生,然后使用当前处理的节点中存储的信息。

示例:TGSAXParser.java

[编辑 | 编辑源代码]

如前所述,完全理解解析过程的概念的最佳方法是实际使用它。在以下代码示例中,将显示人们度假城市的名字和国家的信息。SAX API 是 Xerces 解析器包的一部分,用于实现它((Xerces 2 首页)


// import the basic SAX API classes
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;

public class TGSAXParser extends DefaultHandler
{
    public boolean onVacation = false;

    // what to do when a start-element event was triggered
    public void startElement(String uri, String name, String qName, Attributes atts)
    {
        // stores the string in the XML file          
        String vacationer = atts.getValue("vacation");
        String cityName = atts.getValue("cityName");
        String cityCountry = atts.getValue("cityCountry");

        // if the start tag is "city" set vacationer to true
        if (qName.equals("city") && (vacationer != null))
        {
            onVacation = true;
            System.out.print("\n" + vacationer + " is on vacation in ");
        }
        if (qName.equals("cityName") && onVacation)
            {                       
            }
        if (qName.equals("cityCountry") && onVacation)
        {                       
        }
    }

    /**This method is used to stop printing information once the element has
    *been read.  It will also reset the onVacation variable for the next
    *element.
    */
    public void endElement(String uri, String name, String qName)
    {
        //reset flag
        if (qName.equals("city"))
        {
            onVacation = false;
        }
    }

    /**This method is triggered to store and print the values between
    *the XML tags.  It will only print those values if onVacation == true.
    */
    public void characters(char[] ch, int start, int length)
    {
        if (onVacation)
        {
            for (int i = start; i < start + length; i++)
            System.out.print(ch[i]);
        }
    }

    public static void main(String[] args)
    {
        System.out.println("People on vacation in the following cities:");

        try
        {
            // create a SAX parser from the Xerces package
            XMLReader xml = XMLReaderFactory.createXMLReader();
            TGSAXParser handler = new TGSAXParser();
            xml.setContentHandler(handler);
            xml.setErrorHandler(handler);
            FileReader r = new FileReader("cities.xml");
            xml.parse(new InputSource(r));
        }
        catch (SAXException se)
        {
            System.out.println("XML Parsing Error: " + se);
        } 
        catch (IOException io) 
        {
            System.out.println("File I/O Error: " + io);
        }
    }
}

DefaultHandler:如前所述,SAX 完全是事件驱动的。因此,我们需要一个“监听”来自输入文件(在本例中为cities.xml)的输入流的处理程序。

SAX API 提供接口类,我们必须用我们自己的代码扩展它们来读取我们自己的特定 XML 文档。为了将我们的代码包含在 SAX API 中,我们只需要用我们自己的类扩展 DefaultHandler 接口,并将内容处理程序设置为我们自定义的处理程序类(它包含三个方法:startElement、endElement 和 characters)

startElement()endElement() 方法:这些方法分别在 SAX 解析器找到开始标签或结束标签时被调用。SAX API 为这两种方法提供了空白存根,我们必须用我们自己的代码填充它们。

在本例中,我们希望我们的程序在设置vacation 属性时做一些事情,因此我们会在找到这样的元素时将一个布尔变量设置为 true,并通过打印开始和结束标签之间的字符序列来处理该节点。character 方法会在触发startElementendElement 事件时自动调用,但只会打印出字符字符串,前提是onVacation 属性已设置。

DOM (Document Object Model)

[编辑 | 编辑源代码]

另一种流行的方法是基于树的模型,以 DOM (Document Object Model,参见 W3C 建议) 为代表。这种方法的实际工作原理与 SAX 解析器类似,因为它通过浏览文件并识别 XML 结构来从输入流中读取 XML 文档。

这一次,DOM 方法不是将文档的内容以一系列小片段的形式返回,而是将 XML 层次结构映射到一个 DOM 树对象,该对象包含原始 XML 文档中的所有内容。从元素、注释、文本信息或处理指令到树对象中的节点,从文档本身作为根节点开始,所有内容都存储在树对象中。

现在,由于我们所需的所有信息都存储在内存中,我们可以使用解析器软件提供的方法来访问数据,以读取或修改树中的对象。这便于随机访问 XML 文档的内容,并提供修改其中包含的数据甚至通过将 DOM 转换回 XML 文档来创建新 XML 文件的可能性。

但是,这种方法的主要缺点是它需要更多的内存,因此不适合使用大型 XML 文件的情况。更重要的是,即使对于小型和简单的问题,它也比简单的 SAX 方法复杂得多。

示例:MyDOMParser.java

[编辑 | 编辑源代码]

在以下代码示例中,使用基于树的方法再次创建了一个包含度假人员的城市列表。

// import all necessary DOM API classes
import org.apache.xerces.parsers.*;
import org.apache.xerces.dom.*;
import org.w3c.dom.*;
public class MyDOMParser{
	public static void main(String[] args) {
		System.out.println("People on vacation in the following cities:");  
		try {
			// creates a DOM parser object
			DOMParser parser = new DOMParser();
			parser.parse("cities.xml"); 

			// stores the tree object in a variable
         		org.w3c.dom.Document doc  = parser.getDocument();

			// returns a list of all city elements in my city list
	 		NodeList list = doc.getElementsByTagName("city");

			// now, for every element in the city list, check if the
			// "vacation" attribute is set and if yes, print out the   
			// information about the vacationer.
			for(int i = 0, length = list.getLength(); i < length; i++){
				Element city  = (Element)list.item(i);
				Attr vacationer = city.getAttributeNode("vacation");
				if(vacationer!= null){
					String v = vacationer.getValue();
					System.out.print(v + " is vacationing in ");

					// grab information about city name and country
					// directly from the DOM tree object
					ParentNode cityname = (ParentNode)
					doc.getElementsByTagName("cityName").item(0);
					ParentNode country = (ParentNode)
					doc.getElementsByTagName("cityCountry").item(0);
					System.out.println(cityname.getTextContent() + ", " + country.getTextContent());
				}
			}
		} catch (Exception e) {         
			System.out.println(e.getMessage());
		}  
	}
}

parser.getDocument()解析 XML 文档后,树对象临时存储在解析器变量中。为了使用 DOM 对象,我们必须创建一个包含它的变量(类型为org.w3c.dom.Document)。

然后,我们创建一个包含所有带有标签名称<city>的元素的节点列表。解析器通过浏览 DOM 树来查找这些节点。然后,我们只需遍历每个 city 元素并检查vacation属性是否设置,如果设置,则显示有关度假者的所有信息。

Xerces 提供了一个名为getTextContent()的有用方法,它允许我们直接访问元素节点的文本节点,避免了来自不必要空格等的所有困难。

在 XML 项目开始时选择 API 是一个非常重要的决定。一旦您决定使用哪一个,尝试不同的供应商很容易,不会遇到太多麻烦,但切换到不同的 API 将是一个非常耗时且昂贵的过程,因为您将不得不重新设计整个程序代码。

SAX API 是一种广泛接受且运行良好的解析器,易于实现,尤其适合处理流式内容(例如在线 XML 源)。由于它是一个只读 API,因此无法修改底层 XML 数据源。由于它一次只读取一个节点,因此它非常节省内存且快速。但是,这意味着您的应用程序期望信息紧密相连且有序。

如果您希望在任何时候随机访问整个文档,那么 DOM 方法可能是更好的选择。DOM API 更复杂,更难实现,但它可以让您完全控制整个文档,并允许您修改数据。但是,它会将整个 XML 文档读入内存,因此 DOM API 不适合处理非常大的 XML 文件。

[编辑 | 编辑源代码]

使用本章中 SAX 和 DOM 解析器的代码示例,并进行一些操作。您可能想要打印不同的节点或添加更多约束条件。这绝对是可选的,但它将让您了解 SAX 和 DOM 之间的主要区别。

现在进行练习

[编辑 | 编辑源代码]
  • 创建一个 SAX 解析器来解析文件 movies.xml。输出只需要来自您的 IDE,不需要发送到网页。


为了帮助您 下载这个,它提供了一个问题的结构,以便您更容易在 NetBeans 5.0 中运行应用程序。

如果您有兴趣使用 Xerces - 只需下载以下文件

           http://www.apache.org/dist/xml/xerces-j/Xerces-J-bin.2.8.0.zip

如果上面的链接失效了。请访问 http://www.apache.org/dist/xml/xerces-j/ 下载最新的 zip 二进制文件。它应该以“Xerces-J-bin.#.#.#.zip”格式。

然后将内容放入 NetBeans 目录的 \lib\ext 子文件夹中,并启动 NetBeans IDE。现在,Xerces 包已成功安装在您的机器上。

[编辑 | 编辑源代码]
  1. http://www.cafeconleche.org
  2. http://www.xml.com
  3. http://www.xmlpull.org
  4. http://workshop.bea.com/xmlbeans/reference/com/bea/xml/XmlCursor.html
  5. http://workshop.bea.com/xmlbeans/reference/com/bea/xml/XmlCursor.html


如果这段文字显示为蓝色,则可以通过单击 此处 找到本页示例的 答案
华夏公益教科书