从任意数据结构生成 XML

本节使用 XSLT 将任意数据结构转换为 XML。

这是过程的概述:

首先,您需要一个要转换的数据集和一个能够读取数据的程序。接下来的两节创建一个简单的数据文件和一个读取该文件的程序。

创建一个简单的文件

本示例使用地址簿PersonalAddressBook\.ldif的数据集。如果尚未这样做,请下载 XSLT 示例并将其解压缩到 install-dir /jaxp\-1_4_2\- release-date /samples目录中。此处显示的文件是通过在 Netscape Messenger 中创建一个新的地址簿,为其提供一些虚拟数据(一个地址卡),然后以 LDAP 数据交换格式(LDIF)格式导出而生成的。解压缩 XSLT 示例后,它包含在目录xslt/data中。

以下Figure显示了已创建的地址簿条目。

图通讯录条目

导出地址簿会生成一个文件,如下所示。我们关心的文件部分以粗体显示。

dn: cn=Fred Flintstone,mail=fred@barneys.house
modifytimestamp: 20010409210816Z
cn: Fred Flintstone
xmozillanickname: Fred
mail: Fred@barneys.house
xmozillausehtmlmail: TRUE
givenname: Fred
sn: Flintstone
telephonenumber: 999-Quarry
homephone: 999-BedrockLane
facsimiletelephonenumber: 888-Squawk
pagerphone: 777-pager
cellphone: 555-cell
xmozillaanyphone: 999-Quarry
objectclass: top
objectclass: person

请注意,文件的每一行都包含一个变量名,一个冒号和一个空格,后跟该变量的值。 sn变量包含该人的姓(姓),变量cn包含通讯录条目中的DisplayName字段。

创建一个简单的解析器

下一步是创建一个解析数据的程序。


注意- 本节讨论的代码位于AddressBookReader01\.java,将XSLT examples解压缩到 install-dir /jaxp\-1_4_2\- release-date /samples目录后,该代码位于xslt目录中。


该程序的文本如下所示。这是一个非常简单的程序,它甚至不会循环 Importing 多个条目,因为毕竟它只是一个演示。

import java.io.*; 

public class AddressBookReader01 { 

    public static void main(String argv[]) {
        // Check the arguments
        if (argv.length != 1) {
            System.err.println("Usage: java AddressBookReader01 filename");
            System.exit (1);
        }

        String filename = argv[0];
        File f = new File(filename);
        AddressBookReader01 reader = new AddressBookReader01();
        reader.parse(f);
    }

    // Parse the input file
    public void parse(File f) {
        try {
            // Get an efficient reader for the file
            FileReader r = new FileReader(f);
            BufferedReader br = new BufferedReader(r);

            // Read the file and display its contents.
            String line = br.readLine();
            while (null != (line = br.readLine())) {
                if (line.startsWith("xmozillanickname: "))
                    break;
            }

            output("nickname", "xmozillanickname", line);
            line = br.readLine();
            output("email",  "mail", line);

            line = br.readLine();
            output("html", "xmozillausehtmlmail", line);

            line = br.readLine();
            output("firstname","givenname", line);

            line = br.readLine();
            output("lastname", "sn", line);

            line = br.readLine();
            output("work", "telephonenumber", line);

            line = br.readLine();
            output("home", "homephone", line);

            line = br.readLine();
            output("fax", "facsimiletelephonenumber", line);

            line = br.readLine();
            output("pager", "pagerphone", line);

            line = br.readLine();
            output("cell", "cellphone", line);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

该程序包含三种方法:

运行 AddressBookReader01 示例

% cd install-dir/jaxp-1_4_2-release-date/samples.
cd xslt

键入以下命令:

% javac AddressBookReader01.java

在以下情况下,AddressBookReader01在上面显示的文件PersonalAddressBook\.ldif上运行,该文件在解压缩 samples 包后位于xslt/data目录中。

% java AddressBookReader01 data/PersonalAddressBook.ldif

您将看到以下输出:

nickname: Fred
email: Fred@barneys.house
html: TRUE
firstname: Fred
lastname: Flintstone
work: 999-Quarry
home: 999-BedrockLane
fax: 888-Squawk
pager: 777-pager
cell: 555-cell

这比创建一个简单的文件中显示的文件更具可读性。

创建一个生成 SAX 事件的解析器

本节说明如何使解析器生成 SAX 事件,以便您可以将其用作 XSLT 转换中SAXSource对象的基础。


注意- 本节讨论的代码位于AddressBookReader02\.java,将XSLT examples解压缩到 install-dir /jaxp\-1_4_2\- release-date /samples目录后,该代码位于xslt目录中。 AddressBookReader02\.javaAddressBookReader01\.java相适应,因此这里仅讨论两个示例之间的代码差异。


AddressBookReader02需要以下AddressBookReader01中未使用的突出显示的类。

import java.io.*; 

import org.xml.sax.*;
import org.xml.sax.helpers.AttributesImpl;

该应用程序还扩展了XmlReader。此更改将应用程序转换为生成适当的 SAX 事件的解析器。

public class AddressBookReader02 implements XMLReader { /* ... */ }

AddressBookReader01示例不同,此应用程序没有main方法。

以下全局变量将在本节后面使用:

public class AddressBookReader02 implements XMLReader {
    ContentHandler handler;

    String nsu = "";  
    Attributes atts = new AttributesImpl();
    String rootElement = "addressbook";

    String indent = "\n ";

    // ...
}

SAX ContentHandler是将获取解析器生成的 SAX 事件的对象。为了使应用程序成为XmlReader,应用程序定义了setContentHandler方法。处理程序变量将保存对调用setContentHandler时发送的对象的引用。

当解析器生成 SAX 元素事件时,它将需要提供名称空间和属性信息。因为这是一个简单的应用程序,所以它为这两个都定义了空值。

该应用程序还为数据结构(addressbook)定义了一个根元素,并设置了一个缩进字符串 以提高输出的可读性。

此外,对 parse 方法进行了修改,以使其采用InputSource(而不是File)作为参数并说明了它可以生成的异常:

public void parse(InputSource input) throws IOException, SAXException

现在,与其像AddressBookReader01一样创建一个新的FileReader实例,不如将InputSource对象封装为阅读器:

try {
    java.io.Reader r = input.getCharacterStream();
    BufferedReader Br = new BufferedReader(r);
    // ...
}

注意- 下一部分将说明如何创建 Importing 源对象,并且实际上放置在缓冲读取器中的内容。但是AddressBookReader可以被其他人使用,位于行下某处。此步骤可确保无论您使用什么阅读器,处理都将高效。


下一步是修改 parse 方法以为文档的开头和根元素生成 SAX 事件。以下突出显示的代码可以做到这一点:

public void parse(InputSource input) {
    try {
        // ...
        String line = br.readLine();
        while (null != (line = br.readLine())) {
            if (line.startsWith("xmozillanickname: ")) 
                break;
        }

        if (handler == null) {
            throw new SAXException("No content handler");
        }

        handler.startDocument(); 
        handler.startElement(nsu, rootElement, rootElement, atts);

        output("nickname", "xmozillanickname", line);
        // ...
        output("cell", "cellphone", line);

        handler.ignorableWhitespace("\n".toCharArray(), 
            0,  // start index
            1   // length
        ); 
        handler.endElement(nsu, rootElement, rootElement);
        handler.endDocument(); 
    }
    catch (Exception e) {
        // ...
    }
}

在这里,应用程序检查以确保解析器已正确配置为ContentHandler。 (对于此应用程序,我们什么都不关心)。然后,它为文档的开头和根元素生成事件,并通过发送根元素的结束事件和文档的结束事件来结束。

此时,有两个项目值得注意:

现在已经为文档和根元素生成了 SAX 事件,下一步是修改输出方法以为每个数据项生成适当的元素事件。删除对System\.out\.println\(name + ": " + text\)的调用并添加以下突出显示的代码即可实现:

void output(String name, String prefix, String line) 
    throws SAXException {

    int startIndex = 
    prefix.length() + 2;   // 2=length of ": "
    String text = line.substring(startIndex);

    int textLength = line.length() - startIndex;
    handler.ignorableWhitespace (indent.toCharArray(), 
        0,    // start index
        indent.length()
    );
    handler.startElement(nsu, name, name /*"qName"*/, atts);
    handler.characters(line.toCharArray(), 
        startIndex,
        textLength;
    );
    handler.endElement(nsu, name, name);
}

因为ContentHandler方法可以将SAXExceptions发送回解析器,所以解析器必须准备好处理它们。在这种情况下,不会出现任何情况,因此,如果发生任何应用程序,仅允许该应用程序失败。

然后计算数据的 Long 度,再次为可读性生成一些可忽略的空格。在这种情况下,只有一层数据,因此我们可以使用固定缩进字符串。 (如果数据更加结构化,我们将不得不根据数据的嵌套计算要缩进多少空间)。


注意- 缩进字符串 对数据没有影响,但会使输出更易于阅读。没有该字符串,所有元素将首尾相连:

<addressbook>
<nickname>Fred</nickname>
<email>...

接下来,以下方法使用ContentHandler配置解析器以接收其生成的事件:

void output(String name, String prefix, String line)
    throws SAXException {
    //  ...
}

// Allow an application to register a content event handler.
public void setContentHandler(ContentHandler handler) {
    this.handler = handler;
}  

// Return the current content handler.
public ContentHandler getContentHandler() {
    return this.handler;
}

必须实现其他几种方法才能满足XmlReaderinterface。出于本练习的 Object,将为所有方法生成空方法。但是,生产应用程序将需要实现错误处理程序方法以生成更可靠的应用程序。但是,对于此示例,以下代码为它们生成空方法:

// Allow an application to register an error event handler.
public void setErrorHandler(ErrorHandler handler) { } 

// Return the current error handler.
public ErrorHandler getErrorHandler() { 
    return null; 
}

然后,以下代码为XmlReaderinterface的其余部分生成空方法。 (它们大多数对于 true 的 SAX 解析器有价值,但对像这样的数据转换应用程序影响很小)。

// Parse an XML document from a system identifier (URI).
public void parse(String systemId) throws IOException, SAXException 
{ } 

// Return the current DTD handler.
public DTDHandler getDTDHandler() { return null; } 

// Return the current entity resolver.
public EntityResolver getEntityResolver() { return null; } 

// Allow an application to register an entity resolver.
public void setEntityResolver(EntityResolver resolver) { } 

// Allow an application to register a DTD event handler.
public void setDTDHandler(DTDHandler handler) { } 

// Look up the value of a property.
public Object getProperty(String name) { return null; } 

// Set the value of a property.
public void setProperty(String name, Object value) { }  

// Set the state of a feature.
public void setFeature(String name, boolean value) { } 

// Look up the value of a feature.
public boolean getFeature(String name) { return false; }

现在,您具有可用于生成 SAX 事件的解析器。在下一节中,将使用它来构造一个 SAX 源对象,该对象将使您将数据转换为 XML。

将解析器用作 SAXSource

给定一个 SAX 解析器用作事件源,您可以构造一个转换器以产生结果。在本节中,TransformerApp将被更新以产生流输出结果,尽管它可以很容易地产生 DOM 结果。


注意- 注意:本节中讨论的代码位于TransformationApp03\.java,将XSLT examples解压缩到 install-dir /jaxp\-1_4_2\- release-date /samples目录后,该代码位于xslt目录中。


首先,TransformationApp03TransformationApp02的区别在于它需要导入以构造SAXSource对象的类。这些类在下面突出显示。此时不再需要 DOM 类,因此将其丢弃,尽管将它们保留在其中不会带来任何危害。

import org.xml.sax.SAXException; 
import org.xml.sax.SAXParseException; 
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;

import javax.xml.transform.sax.SAXSource; 
import javax.xml.transform.stream.StreamResult;

接下来,应用程序将创建 SAX 解析器,而不是创建 DOM DocumentBuilderFactory实例,而该解析器是AddressBookReader的实例:

public class TransformationApp03 {
    static Document document;  
    public static void main(String argv[]) {
        // ...
        // Create the sax "parser".
        AddressBookReader saxReader = new AddressBookReader();

        try {
            File f = new File(argv[0]);
            // ...
        }
        // ...
    }
}

然后,以下突出显示的代码构造了一个SAXSource对象

// Use a Transformer for output
// ...
Transformer transformer = tFactory.newTransformer();

// Use the parser as a SAX source for input
FileReader fr = new FileReader(f);
BufferedReader br = new BufferedReader(fr);
InputSource inputSource = new InputSource(br);
SAXSource source = new SAXSource(saxReader, inputSource);
StreamResult result = new StreamResult(System.out);
transformer.transform(source, result);

在这里,TransformationApp03构造了一个缓冲的读取器(如前所述),并将其封装在 Importing 源对象中。然后,它创建一个SAXSource对象,将读取器和InputSource对象传递给该对象,并将其传递给转换器。

当应用程序运行时,转换器将自身配置为 SAX 解析器的ContentHandler(AddressBookReader),并告诉解析器对inputSource对象进行操作。解析器生成的事件然后转到转换器,转换器执行适当的操作并将数据传递到结果对象。

最后,TransformationApp03不会生成异常,因此TransformationApp02中显示的异常处理代码不再存在。

运行 TransformationApp03 示例

% cd install-dir/jaxp-1_4_2-release-date/samples.
cd xslt

键入以下命令:

% javac TransformationApp03.java

在以下情况下,在解压缩 samples 包后,在xslt/data目录中的PersonalAddressBook\.ldif文件上运行TransformationApp03

% java TransformationApp03 
  data/PersonalAddressBook.ldif

您将看到以下输出:

<?xml version="1.0" encoding="UTF-8"?>
<addressbook>
    <nickname>Fred</nickname>
    <email>Fred@barneys.house</email>
    <html>TRUE</html>
    <firstname>Fred</firstname>
    <lastname>Flintstone</lastname>
    <work>999-Quarry</work>
    <home>999-BedrockLane</home>
    <fax>888-Squawk</fax>
    <pager>777-pager</pager>
    <cell>555-cell</cell>
</addressbook>

如您所见,LDIF 格式文件PersonalAddressBook已转换为 XML!

首页