Skip to content

Latest commit

 

History

History
578 lines (448 loc) · 32.4 KB

File metadata and controls

578 lines (448 loc) · 32.4 KB

十一、XML

假设我们希望存储对我们的程序有意义的结构的信息。此外,我们希望这些信息在某种程度上是人类可读的,有时甚至是人类可编辑的。为了实现这一点,我们经常转向 XML。

Java 为我们提供了处理、读取和写入 XML 原始文本和文件的强大工具。然而,正如许多强大工具的情况一样,我们要学习如何使用它们会有一点知识开销。在本章中,我们将首先了解如何使用 Java 将 XML 文件加载到 Java 对象中。接下来,我们将介绍如何使用 Java 解析 XML 数据。最后,我们将看到编写和修改 XML 数据的 Java 代码。

本章将介绍以下主题:

  • 用于读取 XML 数据的 Java 代码
  • 解析 XML 数据
  • 编写和修改 XML 数据

读取 XML 数据

在本节中,我们将完成一项非常简单的任务,开始学习 Java 如何与 XML 交互。我们将使用本书代码文件中提供的cars.xml文件中的 XML 信息。这个文件应该存储在我们 Java 项目的当前目录中,因此当我们运行 Java 程序时,它将能够访问cars.xml,而不需要任何额外的路径。我们将编辑以下 Java 程序来加载cars.xml文件:

package loadinganxmlfile; 

import java.io.*; 
import javax.xml.parsers.*; 
import javax.xml.transform.*; 
import javax.xml.transform.dom.*; 
import javax.xml.transform.stream.*; 
import org.w3c.dom.*; 
import org.xml.sax.*; 

public class LoadingAnXMLFile { 
    public static void main(String[] args) { 

        try { 
            //Write code that can throw errors here 
        } 
        catch (ParserConfigurationException pce) { 
            System.out.println(pce.getMessage()); 
        } 
        catch (SAXException se) { 
            System.out.println(se.getMessage()); 
        } 
        catch (IOException ioe) { 
            System.err.println(ioe.getMessage()); 
        } 
    } 

    private static void PrintXmlDocument(Document xml) 
    { 
        try{ 
            Transformer transformer = 
             TransformerFactory.newInstance().newTransformer(); 
            StreamResult result = new StreamResult
             (new StringWriter()); 
            DOMSource source = new DOMSource(xml); 
            transformer.transform(source, result); 
            System.out.println(result.getWriter().toString()); 
        } 
        catch(TransformerConfigurationException e) 
        { 
            System.err.println("XML Printing Failed"); 
        } 
        catch(TransformerException e) 
        { 
            System.err.println("XML Printing Failed"); 
        } 
    } 
} 

在我们开始之前,请注意这个程序需要大量的导入。我们导入的transform类对于我们要编写的任何东西都不是必需的;我已经编写了一个名为PrintXmlDocument()的函数,如果我们成功加载它,它将把 XML 文档打印到控制台窗口。如果您遵循本节中的代码,我建议您首先从一开始就导入这些transform类。然后,在使用其他功能时,继续使用 NetBeans 的修复导入功能,以准确地查看工具所使用的库的来源。

让我们开始吧。这里我们的最终目标是拥有一个Document类的对象,该对象包含cars.xml文件中的信息。一旦我们有了这个Document对象,我们需要做的就是在控制台窗口中查看信息,在Document实例上调用PrintXmlDocument()函数。

不幸的是,创建这个Document对象并不像说Document dom = new Document();语句那么简单。相反,我们需要以结构化和过程化的方式创建它,以适当地保留 XML 文件的可解析性。为此,我们将使用两个额外的类:DocumentBuilderDocumentBuilderFactory类。

信不信由你,DocumentBuilder类将负责为我们实际构建文档。DocumentBuilder类作为独立于Document对象的实体存在,因此作为程序员,我们可以从逻辑上分离我们可以在文档本身上执行的方法,以及首先创建该文档所需的其他方法。与Document类类似,我们不能只实例化DocumentBuilder类。相反,我们将使用第三个类来获得DocumentBuilder,即DocumentBuilderFactory类。我将创建Document对象(存储 XML 文件)所需的代码分为三部分:

  1. DocumentBuilderFactory类包含一个名为newInstance()的静态方法。让我们向main()方法中的第一个try块添加以下方法调用。这将实例化DocumentBuilderFactory供我们从以下位置工作:
        DocumentBuilderFactory factory = 
        DocumentBuilderFactory.newInstance(); 
  1. 一旦我们有了DocumentBuilderFactory,我们就可以为自己获得一个新的DocumentBuilder对象。为此,我们将调用工厂的newDocumentBuilder()方法。让我们将其添加到我们的 try 块:
        DocumentBuilder builder = factory.newDocumentBuilder(); 
  1. 最后,我们需要指示DocumentBuilder为我们构建一个Document对象,并且该对象应该反映我们的cars.xml文件的结构。我们将简单地用try块中的值实例化Document对象。我们将通过builderparse()方法获取该值。此方法的参数之一是引用文件名的字符串。如果 Java 程序中有一个被引用的文件对象,我们也可以使用它:
        Document dom = builder.parse("cars.xml"); 

所以现在我们的main()方法如下:

        public static void main(String[] args) { 
            DocumentBuilderFactory factory = 
            DocumentBuilderFactory.newInstance(); 
            try { 
                // Write code that can throw errors here... 
                DocumentBuilder builder = 
                factory.newDocumentBuilder(); 
                Document dom = builder.parse("cars.xml"); 

                PrintXmlDocument(dom); 
            } 
            catch (ParserConfigurationException pce) { 
                System.out.println(pce.getMessage()); 
            }  
            catch (SAXException se) { 
                System.out.println(se.getMessage()); 
            } 
            catch (IOException ioe) { 
                System.err.println(ioe.getMessage()); 
            } 
        } 

现在是时候检查我们的代码是否有效了。我们使用DocumentBuilderFactory类的静态方法获取DocumentBuilderFactory对象,这将创建一个全新的实例。通过DocumentBuilderFactory,我们创建了一个新的DocumentBuilder对象,它将能够智能地解析 XML 文件。在解析 XML 文件时,DocumentBuilder对象了解其中包含的信息的性质,并能够将其存储在 XML 文档或Document对象模型元素中。运行此程序时,我们将原始 XML 文档的原始文本视图作为输出:

因为像这样加载 XML 文件有很多步骤,所以我想把它放在它自己的部分中。这样,当我们作为程序员学习如何从 XML 中操作和读取有价值的信息时,我们就不会被我们在这里看到的所有语法所困扰。

解析 XML 数据

Document类为我们在对象中存储格式化信息提供了一种简单的方法。在上一节的程序中,我们将cars.xml文件中的信息读入 JavaDocument对象。以下是cars.xml文件的外观:

<?xml version="1.0"?> 
<cars> 
    <owner name="Billy"> 
        <car vin="LJPCBLCX11000237"> 
            <make>Ford</make> 
            <model>Fusion</model> 
            <year>2014</year> 
            <color>Blue</color> 
        </car> 
        <car vin="LGHIALCX89880011"> 
            <make>Toyota</make> 
            <model>Tacoma</model> 
            <year>2013</year> 
            <color>Green</color> 
        </car> 
        <car vin="GJSIALSS22000567"> 
            <make>Dodge</make> 
            <model>Charger</model> 
            <year>2013</year> 
            <color>Red</color> 
        </car> 
    </owner> 
    <owner name="Jane"> 
        <car vin="LLOKAJSS55548563"> 
            <make>Nissan</make> 
            <model>Altima</model> 
            <year>2000</year> 
            <color>Green</color> 
        </car> 
        <car vin="OOKINAFS98111001"> 
            <make>Dodge</make> 
            <model>Challenger</model> 
            <year>2013</year> 
            <color>Red</color> 
        </car> 
    </owner> 
</cars> 

该文件的根节点是cars节点,该节点中包含两个owner节点,即 Billy 和 Jane,每个节点中都有多个car节点。这些car元素中存储的信息由前面的 Java 类可以存储的信息镜像。

本节中我们的目标是从cars.xml中获取车辆信息,在本例中,仅针对特定的车主 Jane,并将此信息存储在我们的自定义Car类中,以便我们可以利用Car类的toString()覆盖以良好的格式将 Jane 的所有车辆打印到我们的控制台。

通过我们已经设置的代码,我们的Document对象dom以相同的格式镜像cars.xml中存储的信息,所以我们只需要弄清楚如何问这个Document对象这个问题:Jane 拥有什么车?为了弄清楚如何编写代码,您需要了解一点 XML 术语。在本节中,我们将讨论术语“元素”和“节点”。

在 XML 中,元素是一个具有开始和结束标记的实体,它还包含其中的所有信息。当我们的Document对象返回信息时,它通常会以节点的形式返回信息。节点是 XML 文档的构建块,我们几乎可以将其视为一种继承关系,其中所有元素都是节点,但并非所有节点都是元素。节点可以比整个 XML 元素更简单、更小。

访问 Jane 的 XML 元素

本节将帮助我们使用以下代码访问有关 Jane 拥有的汽车的信息。我将要添加到main()函数中的代码分为六部分:

  1. So, in the pursuit of finding all the cars owned by Jane, let's see what functionality our XML document provides for us right off the bat. If we take a look at code completion to quickly scan through our list of methods, we can call dom from our Document instance. We're going to see the getDocumentElement() method return an element for us:

这可能是一个很好的开始。此方法返回 XML 中的顶级元素;在本例中,我们将获得cars元素,它包含我们需要的所有信息。它还包含一些我们不需要的信息,比如比利的车,但我们会在访问后解析出来。一旦我们导入了正确的库,我们就可以使用Element类在代码中直接引用 XML 元素的概念。我们可以创建一个新的Element对象,并将其值分配给 XML 文档的根元素:

        Element doc = dom.getDocumentElement(); 

当然,我们需要更深入。XML 文档cars的根级别对我们没有直接的帮助;我们需要其中包含的信息。我们只需要一个owner节点的信息(包含有关 Jane 汽车的信息)。但是由于 XML 解析的工作方式,我们可能需要首先获取这两个所有者节点,然后找到我们真正感兴趣的节点。

为了获取这两个节点,我们可以对刚刚创建并存储在doc中的根 XML 元素调用一个方法。XML 元素可以包含其中的其他元素;在本例中,我们的根元素包含许多owner元素。getElementsByTagName()方法允许我们收集大量这些内部元素。XML 元素的标记名正是您所期望的;它是我们给 XML 中特定元素的名称。在本例中,如果我们要求文档根元素中包含标记名为owner的所有元素,我们将进一步缩小正在使用的 XML 的数量,从而更接近我们想要的小部分。

但是,getElementsByTagName()方法返回的不是单个元素。即使在最高级别,本节也有两个不同的元素,即两个所有者:BillyJane。因此,getElementsByTagLineName()方法不返回单个元素;相反,它返回一个NodeList对象,它是 XML 节点的集合:

        NodeList ownersList = doc.getElementsByTagName("owner"); 

我们现在不再处理根节点;我们只有它的内容。现在是我们缩小搜索范围的时候了。我们的NodeList对象包含多个所有者,但只有当与该所有者关联的属性名恰好为Jane时,我们才需要一个所有者。为了找到这个特定的元素,如果它存在,我们只需通过NodeList循环,检查它包含的每个元素的属性。请注意,ownersList不是传统的数组。它是一个NodeList对象,它自己的类型的对象。所以,我们不能对它使用普通数组语法。幸运的是,它向我们展示了模仿正常数组语法的方法。例如,getLength()方法会告诉我们ownersList中有多少个对象:

        for(int i = 0; i < ownersList.getLength(); i++) 
        { 
        } 
  1. 类似地,当我们试图创建一个新的Element对象并将该值分配给ownersList的当前循环部分时,我们将无法使用数组的常规语法。然而,ownersList再次为我们提供了一种做同样事情的方法。item()方法提供或请求索引作为输入。

注意,ownersListNodeList,但是虽然元素是节点,但并非所有节点都是元素,所以我们需要在这里做出决定。我们可以检查此函数返回的对象的性质,并确保它们实际上是 XML 元素。但是为了让事情继续发展,我们假设我们的 XML 格式正确,我们只是让 Java 知道,item()方法返回的节点实际上是一个元素;“开始”标记和“结束”标记中的其他元素可以是:

            Element owner = (Element)ownersList.item(i); 

一旦我们成功地从我们的所有者列表中访问了一个元素,就应该检查并查看这是否是我们正在寻找的所有者;因此,我们需要一个条件语句。XML 元素向我们公开了getAttribute()方法,我们感兴趣的属性是name属性。因此,下面的代码将询问当前的owner,“您的name属性的值是多少?”如果该值等于Jane,那么我们就知道访问了正确的 XML 元素。

在 Jane 的 XML 元素中,现在我们只有一些car元素。所以,再一次,是时候创建NodeList并用这些car元素填充它了。我们现在需要调用Jane上的getElementByTagName()方法,我们当前的所有者。如果我们使用顶级文档调用此函数,我们将获得文档中的所有car元素,甚至 Billy 的:

            if(owner.getAttribute("name").equals("Jane")) 
            { 
                NodeList carsList = 
                owner.getElementsByTagName("car"); 
  1. 这个main()方法有点紧张了;这就是我愿意用一种方法走多远。我们的代码已经深入了两个层次,而且我们编写的代码并不简单。我认为现在是我们将下一位解析为它自己的方法的时候了。让我们简单地声明我们将有一个PrintCars()方法,这个函数将使用car元素中的NodeList来打印 car 节点:
        PrintCars(carsList); 

我们的main方法如下:

        public static void main(String[] args) { 
            DocumentBuilderFactory factory = 
            DocumentBuilderFactory.newInstance(); 
            try { 
                DocumentBuilder docBuilder = 
                factory.newDocumentBuilder(); 
                Document dom = docBuilder.parse("cars.xml"); 

                // Now, print out all of Jane's cars 
                Element doc = dom.getDocumentElement(); 
                NodeList ownersList = 
                doc.getElementsByTagName("owner"); 

                for(int i = 0; i < ownersList.getLength(); i++) 
                { 
                    Element owner = (Element)ownersList.item(i); 
                    if(owner.getAttribute("name").equals("Jane")) 
                    { 
                        NodeList carsList = 
                        owner.getElementsByTagName("car"); 
                        PrintCars(carsList); 
                    } 
                } 
            } 
            catch (ParserConfigurationException pce) { 
                System.out.println(pce.getMessage()); 
            }  
            catch (SAXException se) { 
                System.out.println(se.getMessage()); 
            }  
            catch (IOException ioe) { 
                System.err.println(ioe.getMessage()); 
            } 
        } 

打印简的汽车详细信息

现在,离开我们的main()方法,我们将定义新的PrintCars()方法。我将PrintCars()函数的定义分为八个部分:

  1. 因为我们在程序的 entry 类中,PrintCars()方法被静态main()方法调用,所以它可能是一个static函数。它所要做的就是打印到我们的控制台上,所以void是一个合适的返回类型。我们已经知道它将以NodeList辆车作为输入:
        public static void PrintCars(NodeList cars) 
        { 
        } 
  1. 一旦我们输入了这个函数,我们就知道我们有一个carXML 元素列表可供使用。但是为了把每一个都打印出来,我们必须对它们进行循环。我们已经在程序中循环使用了 XMLNodeList,所以我们将使用一些非常类似的语法。让我们来看看这个新代码需要改变什么。好吧,我们不再循环通过ownersList;我们有一个新的NodeList对象要循环通过carsNodeList
        for(int i = 0; i < cars.getLength(); i++) 
        { 
        } 
  1. 我们知道汽车仍然是Element实例,所以我们在这里设置一个快捷方式仍然是合适的,但我们可能希望将我们循环使用的每个car变量重命名为类似carNode的变量。每次我们循环一辆车时,我们都要创建一个新的Car对象,并将信息存储在该车的 XML 中,存储在这个实际的 Java 对象中:
        Element carNode = (Element)cars.item(i); 
  1. 因此,除了访问carXML,我们还声明一个Car对象,并将其实例化为一个新的Car对象:
        Car carObj = new Car(); 
  1. 现在我们将通过从carNode中读取carObj中存储的值来建立这些值。如果我们快速跳回 XML 文件并查看存储在car元素中的信息,我们将看到它将makemodelyearcolor存储为 XML 节点。车辆识别号vin实际上是一个属性。让我们简单地看看我们的 T8 课:
        package readingxml; 

        public class Car { 
            public String vin; 
            public String make; 
            public String model; 
            public int year; 
            public String color; 
            public Car() 
            { 

            } 
            @Override 
            public String toString() 
            { 
                return String.format("%d %s %s %s, vin:%s", year, 
                color, make, model, vin); 
            } 
        } 

让我们先从简单的部分开始;所以,makemodelcolor都是存储在Car类中的字符串,它们恰好都是car元素中的节点。

回到PrintCars()函数,我们已经知道如何访问元素中的节点。我们将再次使用carNodegetElementsByTagName()函数。如果我们得到标签名为color的所有元素,我们应该得到一个列表,其中只包含一个元素,即我们感兴趣的元素,它告诉我们汽车的颜色。不幸的是,我们这里确实有一个列表,所以我们不能直接操作该元素,除非我们从列表中提取它。不过,再一次,我们知道如何做到这一点。如果我们确信 XML 格式正确,我们就知道我们将得到一个只包含一项的列表。因此,如果我们在该列表的第 0 个索引处获得该项,那么这将是我们要查找的 XML 元素。

但是,存储在这个 XML 元素中的颜色信息不是属性;这是内在的文本。因此,我们将了解 XML 元素公开了哪些方法,并查看是否有合适的方法来获取内部文本。有一个getTextContent()函数,它将为我们提供所有实际上不属于 XML 元素标记的内部文本。在这种情况下,它将为我们提供汽车的颜色。

仅仅获取这些信息是不够的;我们需要储存它。幸运的是,carObj的所有属性都是公共的,因此我们可以在创建car对象后自由地为它们赋值。如果这些字段是没有 setter 的私有字段,我们可能必须在构造carObj之前完成这些信息,然后将它们传递给构造函数,它希望有:

        carObj.color = 
        carNode.getElementsByTagName("color").item(0).getTextContent(); 

我们将为makemodel做几乎完全相同的事情。我们唯一需要更改的是在查找元素时提供的关键字:

        carObj.make = 
        carNode.getElementsByTagName("make").item(0).getTextContent(); 
        carObj.model = 
        carNode.getElementsByTagName("model").item(0).getTextContent(); 
  1. 现在,我们可以继续对我们的车的year使用相同的总体策略,但我们应该注意,就carObj而言,year是一个整数。就我们的 XML 元素而言,year和其他元素一样,是一个TextContent字符串。幸运的是,将一个string转换成一个integer,只要它是格式良好的,这是我们将在这里做的一个假设,并不太困难。我们将简单地使用Integer类并调用其parseInt()方法。这将尽最大努力将字符串值转换为整数。我们将其分配到carObjyear字段:
        carObj.year = 
        Integer.parseInt(carNode.getElementsByTagName
        ("year").item(0).getTextContent()); 
  1. 这只给我们留下了一个领域。注意,carObj有一个车辆识别号字段。该字段实际上不是整数;车辆识别号可以包含字母,因此该值存储为字符串。这对我们来说会有点不同,因为它不是一个内部元素,而是car元素本身的属性。再一次,我们知道如何从carNode获取属性;我们只需获取名为vin的属性并将其分配给carObj
         carObj.vin = carNode.getAttribute("vin"); 
  1. 所有这些都完成后,我们的carObj对象应该在其所有成员中都具有合理的价值。现在是使用carObj的时候了,因为它存在的原因:覆盖toString()功能。对于我们循环通过的每辆车,让我们调用carObjtoString()函数并将结果打印到控制台:
        System.out.println(carObj.toString()); 

我们的PrintCars()函数现在如下所示:

public static void PrintCars(NodeList cars) 
{ 
    for(int i = 0; i < cars.getLength(); i++) 
    { 
        Element carNode = (Element)cars.item(i); 
        Car carObj = new Car(); 
        carObj.color = 
         carNode.getElementsByTagName
         ("color").item(0).getTextContent(); 
        carObj.make = 
         carNode.getElementsByTagName
         ("make").item(0).getTextContent(); 
        carObj.model = carNode.getElementsByTagName
         ("model").item(0).getTextContent(); 
        carObj.year = 
         Integer.parseInt(carNode.getElementsByTagName
         ("year").item(0).getTextContent()); 
        carObj.vin = carNode.getAttribute("vin"); 
        System.out.println(carObj.toString()); 
    } 
} 

我们应该善于编译我们的程序。现在,当我们运行它时,希望它能打印出 Jane 的所有汽车,利用被重写的carObj方法,很好地格式化输出。当我们运行这个程序时,我们得到两辆车作为输出打印出来,如果我们转到 XML 并查看分配给 Jane 的车,我们将看到这些信息确实与存储在这些车中的信息相匹配:

XML 和 Java 的结合非常强大。XML 是人类可读的。我们可以理解它,甚至可以作为人对它进行修改,但它也包含关于它的结构的真正有价值的信息。这也是编程语言(如 Java)可以理解的。我们在这里编写的程序确实有它的怪癖,需要一定的知识来编写。与来自原始文本文件的类似程序相比,它编写起来容易得多,程序员理解和维护起来也容易得多。

编写 XML 数据

能够读取 XML 信息是非常好的,但要使该语言对我们真正有用,我们的 Java 程序可能还需要能够写出 XML 信息。以下程序是对同一 XML 文件进行读取和写入的程序的基本模型:

package writingxml; 

import java.io.*; 
import javax.xml.parsers.*; 
import javax.xml.transform.*; 
import javax.xml.transform.dom.*; 
import javax.xml.transform.stream.*; 
import org.w3c.dom.*; 
import org.xml.sax.*; 

public class WritingXML { 
    public static void main(String[] args) { 
        File xmlFile = new File("cars.xml"); 
        Document dom = LoadXMLDocument(xmlFile);       
        WriteXMLDocument(dom, xmlFile); 
    } 

    private static void WriteXMLDocument
     (Document doc, File destination) 
    { 
        try{ 
            // Write doc to destination file here... 
        } 
        catch(TransformerConfigurationException e) 
        { 
            System.err.println("XML writing failed."); 
        } 
        catch(TransformerException e) 
        { 
            System.err.println("XML writing failed."); 
        } 
    } 

    private static Document LoadXMLDocument(File source) 
    { 
        try { 
            DocumentBuilderFactory factory = 
             DocumentBuilderFactory.newInstance(); 
            DocumentBuilder builder = 
             factory.newDocumentBuilder(); 
            Document dom = builder.parse(source); 
        } 
        catch (ParserConfigurationException e) { 
             System.err.println("XML loading failed."); 
        } 
        catch (SAXException e) { 
             System.err.println("XML loading failed."); 
        } 
        catch (IOException e) { 
            System.err.println("XML loading failed."); 
        } 

        return dom; 
    } 
} 

它的main()方法非常简单。它获取一个文件,然后从该文件中读取 XML,并将其存储在 XML 文档的树对象中。然后,这个程序调用WriteXMLDocument()将 XML 写回同一个文件。目前,我们已经实现了读取 XML 的方法(LoadXMLDocument();然而,写出 XML 的方法还没有完成。让我们看看将 XML 信息写入文档需要做些什么。我已经将WriteXMLDocument()功能的代码分为四个部分。

编写 XML 数据的 Java 代码

以下是编写 XML 数据要执行的步骤:

  1. 由于 XML 文档的存储方式,我们需要先将其转换为不同的格式,然后才能将其打印到与原始 XML 相同格式的文件中。为此,我们将使用一个名为Transformer的 XML 特定类。与我们在文档模型中处理 XML 时处理的许多类一样,Transformer实例最好使用工厂创建。在本例中,工厂名为TransformerFactory,与许多工厂一样,它公开了newInstance()方法,允许我们在需要时创建一个。为了获得新的Transformer对象,这将允许我们将Document对象转换为可以发送到文件的可流化对象,我们只需调用TransformerFactorynewTransformer()方法:
        TransformerFactory tf = TransformerFactory.newInstance(); 
        Transformer transformer = tf.newTransformer(); 
  1. 现在,在Transformer可以将 XML 文档转换为其他内容之前,它需要知道我们希望它将 XML 文档的信息转换为什么。该类为StreamResult类;它是存储在当前Document对象中的信息的目标。该流是一个原始二进制信息泵,可以发送到任意数量的目标。在这种情况下,我们的目标是提供给StreamResult构造函数的目标文件:
        StreamResult result = new StreamResult(destination); 
  1. 然而,我们的Transformer对象并没有自动链接到 XML 文档,它希望我们以一种独特的方式引用 XML 文档:作为DOMSource对象。请注意,我们的source对象(定义如下)与result对象配对。Transformer对象,当我们同时提供这两个对象时,将知道如何将一个对象转换为另一个对象。现在,要创建DOMSource对象,只需传入 XML 文档:
        DOMSource source = new DOMSource(doc); 
  1. 最后,当所有设置完成后,我们可以执行代码的功能部分。让我们抓取我们的Transformer对象,让它将我们的源(即DOMSource对象)转换为针对目标文件的可流化结果:
         transformer.transform(source, result); 

以下是我们的WriteXMLDocument()功能:

private static void WriteXMLDocument
(Document doc, File destination) 
{ 
    try{ 
        // Write doc to destination file here 
        TransformerFactory tf = 
         TransformerFactory.newInstance(); 
        Transformer transformer = tf.newTransformer(); 
        StreamResult result = new StreamResult(destination); 
        DOMSource source = new DOMSource(doc); 

        transformer.transform(source, result); 
    } 
    catch(TransformerConfigurationException e) 
    { 
        System.err.println("XML writing failed."); 
    } 
    catch(TransformerException e) 
    { 
        System.err.println("XML writing failed."); 
    } 
} 

当我们运行这个程序时,我们会在文件中得到一些 XML,但是我说的时候你必须相信我:这是我们以前拥有的同一个 XML,这是我们首先读入 XML,然后打印回来的结果。

为了真正测试我们的程序是否正常工作,我们需要在 Java 代码中对Document对象进行一些更改,然后看看是否可以将这些更改打印到此文件中。让我们换一下车主的名字。让我们把所有汽车的交易转给一个叫迈克的车主。

修改 XML 数据

XML I/O 系统的强大之处在于,在加载和写入 XML 文档之间,我们可以自由修改存储在内存中的Document对象dom。此外,我们对 Java 内存中的对象所做的更改将写入我们的永久 XML 文件。因此,让我们从做一些更改开始:

  1. 我们将使用getElementsByTagName()获取 XML 文档中的所有owner元素。这将返回一个NodeList对象,我们将其称为owners
        NodeList owners = dom.getElementsByTagName("owner"); 
  1. 要将所有这些所有者的姓名转换为Mike,我们必须循环浏览此列表。作为复习,我们可以通过调用ownersgetLength()函数,即我们的NodeList对象来获取列表中的项数。要访问当前正在迭代的项,我们将使用ownersitem()函数并传入迭代变量i以获取该索引处的项。让我们把这个值存储在一个变量中,这样我们就可以很容易地使用它;再一次,我们将假设我们的 XML 格式良好,并告知 Java,实际上,此时我们正在处理一个成熟的 XML 元素。

接下来,XML 元素公开了许多方法,允许我们修改它们。其中一个元素是setAttribute()方法,这就是我们将在这里使用的。注意,setAttribute()接受两个字符串作为输入。首先,它想知道我们想要修改什么属性。我们将修改name属性(这是我们在这里唯一可用的属性),并将其值分配给Mike

            for(int i = 0; i < owners.getLength(); i++) 
            { 
                Element owner = (Element)owners.item(i); 
                owner.setAttribute("name", "Mike"); 
            } 

现在我们的main()方法如下:

public static void main(String[] args) { 
    File xmlFile = new File("cars.xml"); 
    Document dom = LoadXMLDocument(xmlFile); 

    NodeList owners = dom.getElementsByTagName("owner"); 
    for(int i = 0; i < owners.getLength(); i++) 
    { 
        Element owner = (Element)owners.item(i); 
        owner.setAttribute("name", "Mike"); 
    } 
    WriteXMLDocument(dom, xmlFile); 
} 

当我们运行程序并检查 XML 文件时,我们将看到Mike现在是所有这些汽车的所有者,如以下屏幕截图所示:

现在,将这两个 XML 元素组合起来,使Mike只是一个所有者,而不是将其拆分为两个,这可能是有意义的。这有点超出了本节的范围,但这是一个有趣的问题,我想鼓励你们反思一下,也许现在就可以尝试一下。

总结

在本章中,我们看到了将 XML 文件读入Document对象的 Java 代码。我们还了解了如何使用 Java 解析 XML 数据。最后,我们了解了如何在 Java 中编写和修改 XML 数据。

祝贺您现在是一名 Java 程序员。