从任意数据结构生成 XML

原文: https://docs.oracle.com/javase/tutorial/jaxp/xslt/generatingXML.html

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

以下是该过程的概述:

  1. 修改读取数据的现有程序,使其生成 SAX 事件。 (该程序是真正的解析器还是仅仅是某种数据过滤器,目前无关紧要)。

  2. 使用 SAX“解析器”为转换构造SAXSource

  3. 使用与上一个练习中创建的相同的StreamResult对象来显示结果。 (但请注意,您可以轻松创建DOMResult对象以在内存中创建 DOM)。

  4. 使用变换器对象将源连接到结果以进行转换。

对于初学者,您需要一个要转换的数据集和一个能够读取数据的程序。接下来的两节创建一个简单的数据文件和一个读取它的程序。

创建一个简单的文件

此示例使用地址簿的数据集PersonalAddressBook.ldif 。如果你还没有这样做, download the XSLT examples 并将它们解压缩到 install-dir / jaxp-1_4_2- 发布日期 ] / samples目录。此处显示的文件是通过在 Netscape Messenger 中创建新的地址簿,为其提供一些虚拟数据(一个地址卡),然后以 LDAP 数据交换格式(LDIF)格式导出它来生成的。在解压缩 XSLT 示例后,它包含在目录xslt / data中。

以下显示已创建的通讯簿条目。

图地址簿条目

Snapshot of a Mozilla Thunderbird contact details card.

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

  1. dn: cn=Fred Flintstone,mail=fred@barneys.house
  2. modifytimestamp: 20010409210816Z
  3. cn: Fred Flintstone
  4. xmozillanickname: Fred
  5. mail: Fred@barneys.house
  6. xmozillausehtmlmail: TRUE
  7. givenname: Fred
  8. sn: Flintstone
  9. telephonenumber: 999-Quarry
  10. homephone: 999-BedrockLane
  11. facsimiletelephonenumber: 888-Squawk
  12. pagerphone: 777-pager
  13. cellphone: 555-cell
  14. xmozillaanyphone: 999-Quarry
  15. objectclass: top
  16. objectclass: person

请注意,文件的每一行都包含一个变量名,一个冒号和一个空格,后跟一个变量值。 sn变量包含人的姓氏(姓氏),变量cn包含来自地址簿条目的DisplayName字段。

创建一个简单的解析器

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


:本节讨论的代码位于AddressBookReader01.java中,解压缩 XSLT examples 后可在xslt目录中找到]进入 install-dir / jaxp-1_4_2- 发布日期 /样例目录。


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

  1. import java.io.*;
  2. public class AddressBookReader01 {
  3. public static void main(String argv[]) {
  4. // Check the arguments
  5. if (argv.length != 1) {
  6. System.err.println("Usage: java AddressBookReader01 filename");
  7. System.exit (1);
  8. }
  9. String filename = argv[0];
  10. File f = new File(filename);
  11. AddressBookReader01 reader = new AddressBookReader01();
  12. reader.parse(f);
  13. }
  14. // Parse the input file
  15. public void parse(File f) {
  16. try {
  17. // Get an efficient reader for the file
  18. FileReader r = new FileReader(f);
  19. BufferedReader br = new BufferedReader(r);
  20. // Read the file and display its contents.
  21. String line = br.readLine();
  22. while (null != (line = br.readLine())) {
  23. if (line.startsWith("xmozillanickname: "))
  24. break;
  25. }
  26. output("nickname", "xmozillanickname", line);
  27. line = br.readLine();
  28. output("email", "mail", line);
  29. line = br.readLine();
  30. output("html", "xmozillausehtmlmail", line);
  31. line = br.readLine();
  32. output("firstname","givenname", line);
  33. line = br.readLine();
  34. output("lastname", "sn", line);
  35. line = br.readLine();
  36. output("work", "telephonenumber", line);
  37. line = br.readLine();
  38. output("home", "homephone", line);
  39. line = br.readLine();
  40. output("fax", "facsimiletelephonenumber", line);
  41. line = br.readLine();
  42. output("pager", "pagerphone", line);
  43. line = br.readLine();
  44. output("cell", "cellphone", line);
  45. }
  46. catch (Exception e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. }

该程序包含三种方法:

main

main方法从命令行获取文件的名称,创建解析器的实例,并将其设置为解析文件。当我们将程序转换为 SAX 解析器时,此方法将消失。 (这是将解析代码放入单独方法的一个原因)。

parse

该方法对主程序发送给它的File对象进行操作。如您所见,它非常简单。效率的唯一让步是使用BufferedReader ,当你开始操作大文件时,它会变得很重要。

output

输出方法包含行结构的逻辑。它需要三个参数。第一个参数赋予方法显示的名称,因此它可以输出html作为变量名,而不是xmozillausehtmlmail 。第二个参数给出了存储在文件中的变量名称( xmozillausehtmlmail )。第三个参数给出包含数据的行。然后,例程从行的开头剥离变量名称,并输出所需的名称和数据。

运行AddressBookReader01样例

  1. 导航至样例目录。

    1. cd install-dir / jaxp-1_4_2- 释放日期 /样例。
  2. Download the XSLT examples by clicking this link 并将它们解压缩到 install-dir / jaxp-1_4_2- 释放日期 /样例目录。

  3. 导航到xslt目录。

    1. cd xslt
  4. Compile the AddressBookReader01 sample.

    键入以下命令:

    1. % javac AddressBookReader01.java
  5. Run the AddressBookReader01 sample on a data file.

    在下面的例子中, AddressBookReader01在上面显示的文件PersonalAddressBook.ldif上运行,在解压缩样例包后找到xslt / data目录。

    1. % java AddressBookReader01 data/PersonalAddressBook.ldif

    您将看到以下输出:

    1. nickname: Fred
    2. email: Fred@barneys.house
    3. html: TRUE
    4. firstname: Fred
    5. lastname: Flintstone
    6. work: 999-Quarry
    7. home: 999-BedrockLane
    8. fax: 888-Squawk
    9. pager: 777-pager
    10. cell: 555-cell

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

创建生成 SAX 事件的分析器

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


:本节讨论的代码位于AddressBookReader02.java中,解压缩 XSLT examples 后可在xslt目录中找到]进入 install-dir / jaxp-1_4_2- 发布日期 /样例目录。 AddressBookReader02.java改编自AddressBookReader01.java ,因此这里仅讨论两个示例之间的代码差异。


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

  1. import java.io.*;
  2. import org.xml.sax.*;
  3. import org.xml.sax.helpers.AttributesImpl;

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

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

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

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

  1. public class AddressBookReader02 implements XMLReader {
  2. ContentHandler handler;
  3. String nsu = "";
  4. Attributes atts = new AttributesImpl();
  5. String rootElement = "addressbook";
  6. String indent = "\n ";
  7. // ...
  8. }

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

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

应用程序还定义了数据结构的根元素(地址簿)并设置了缩进字符串以提高输出的可读性。

此外,修改了解析方法,以便将InputSource (而不是文件)作为参数,并考虑它可以生成的异常:

  1. public void parse(InputSource input) throws IOException, SAXException

现在,不是像AddressBookReader01那样创建一个新的FileReader实例,而是由InputSource对象封装阅读器:

  1. try {
  2. java.io.Reader r = input.getCharacterStream();
  3. BufferedReader Br = new BufferedReader(r);
  4. // ...
  5. }

:下一节将介绍如何创建输入源对象,放入其中的内容实际上是一个缓冲读取器。但AddressBookReader可能被其他人使用,在某个地方。无论您获得何种读者,此步骤都可确保处理效率。


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

  1. public void parse(InputSource input) {
  2. try {
  3. // ...
  4. String line = br.readLine();
  5. while (null != (line = br.readLine())) {
  6. if (line.startsWith("xmozillanickname: "))
  7. break;
  8. }
  9. if (handler == null) {
  10. throw new SAXException("No content handler");
  11. }
  12. handler.startDocument();
  13. handler.startElement(nsu, rootElement, rootElement, atts);
  14. output("nickname", "xmozillanickname", line);
  15. // ...
  16. output("cell", "cellphone", line);
  17. handler.ignorableWhitespace("\n".toCharArray(),
  18. 0, // start index
  19. 1 // length
  20. );
  21. handler.endElement(nsu, rootElement, rootElement);
  22. handler.endDocument();
  23. }
  24. catch (Exception e) {
  25. // ...
  26. }
  27. }

这里,应用程序检查以确保使用ContentHandler正确配置解析器。 (对于这个应用程序,我们不关心任何其他事情)。然后,它生成文档开始和根元素的事件,并通过发送根元素的结束事件和文档的结束事件来完成。

此时有两项值得注意:

  • setDocumentLocator事件尚未发送,因为这是可选的。如果重要的话,该事件将在startDocument事件之前立即发送。

  • 在根元素结束之前生成ignorableWhitespace事件。这也是可选的,但它会大大提高输出的可读性,这很快就会看到。 (在这种情况下,空格由单个换行符组成,其发送方式与将字符发送到字符方法的方式相同:作为字符数组,起始索引和长度)。

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

  1. void output(String name, String prefix, String line)
  2. throws SAXException {
  3. int startIndex =
  4. prefix.length() + 2; // 2=length of ": "
  5. String text = line.substring(startIndex);
  6. int textLength = line.length() - startIndex;
  7. handler.ignorableWhitespace (indent.toCharArray(),
  8. 0, // start index
  9. indent.length()
  10. );
  11. handler.startElement(nsu, name, name /*"qName"*/, atts);
  12. handler.characters(line.toCharArray(),
  13. startIndex,
  14. textLength;
  15. );
  16. handler.endElement(nsu, name, name);
  17. }

因为ContentHandler方法可以将SAXExceptions发送回解析器,所以解析器必须准备好处理它们。在这种情况下,没有预期的,因此如果发生任何应用程序,则只允许应用程序失败。

然后计算数据的长度,再次生成一些可忽略的空白以便于阅读。在这种情况下,只有一个级别的数据,因此我们可以使用固定缩进字符串。 (如果数据更加结构化,我们必须计算缩进的空间,具体取决于数据的嵌套)。


:缩进字符串对数据没有影响,但会使输出更容易阅读。没有该字符串,所有元素将端到端连接:

  1. <addressbook>
  2. <nickname>Fred</nickname>
  3. <email>...

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

  1. void output(String name, String prefix, String line)
  2. throws SAXException {
  3. // ...
  4. }
  5. // Allow an application to register a content event handler.
  6. public void setContentHandler(ContentHandler handler) {
  7. this.handler = handler;
  8. }
  9. // Return the current content handler.
  10. public ContentHandler getContentHandler() {
  11. return this.handler;
  12. }

必须实现其他几种方法才能满足XmlReader接口。出于本练习的目的,将为所有这些方法生成 null 方法。但是,生产应用程序需要实现错误处理器方法以生成更强大的应用程序。但是,对于此示例,以下代码为它们生成 null 方法:

  1. // Allow an application to register an error event handler.
  2. public void setErrorHandler(ErrorHandler handler) { }
  3. // Return the current error handler.
  4. public ErrorHandler getErrorHandler() {
  5. return null;
  6. }

然后,以下代码为XmlReader接口的其余部分生成空方法。 (它们中的大多数对真正的 SAX 解析器很有价值,但对像这样的数据转换应用程序几乎没有影响)。

  1. // Parse an XML document from a system identifier (URI).
  2. public void parse(String systemId) throws IOException, SAXException
  3. { }
  4. // Return the current DTD handler.
  5. public DTDHandler getDTDHandler() { return null; }
  6. // Return the current entity resolver.
  7. public EntityResolver getEntityResolver() { return null; }
  8. // Allow an application to register an entity resolver.
  9. public void setEntityResolver(EntityResolver resolver) { }
  10. // Allow an application to register a DTD event handler.
  11. public void setDTDHandler(DTDHandler handler) { }
  12. // Look up the value of a property.
  13. public Object getProperty(String name) { return null; }
  14. // Set the value of a property.
  15. public void setProperty(String name, Object value) { }
  16. // Set the state of a feature.
  17. public void setFeature(String name, boolean value) { }
  18. // Look up the value of a feature.
  19. public boolean getFeature(String name) { return false; }

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

使用 Parser 作为SAXSource

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


:注意:本节讨论的代码位于TransformationApp03.java中,解压缩 XSLT examples后可在xslt目录中找到进入 install-dir / jaxp-1_4_2- 发布日期 /样例目录。


首先, TransformationApp03TransformationApp02的不同之处在于它需要导入以构造SAXSource对象。这些类在下面突出显示。此时不再需要 DOM 类,因此已被丢弃,尽管将它们留在其中不会造成任何伤害。

  1. import org.xml.sax.SAXException;
  2. import org.xml.sax.SAXParseException;
  3. import org.xml.sax.ContentHandler;
  4. import org.xml.sax.InputSource;
  5. import javax.xml.transform.sax.SAXSource;
  6. import javax.xml.transform.stream.StreamResult;

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

  1. public class TransformationApp03 {
  2. static Document document;
  3. public static void main(String argv[]) {
  4. // ...
  5. // Create the sax "parser".
  6. AddressBookReader saxReader = new AddressBookReader();
  7. try {
  8. File f = new File(argv[0]);
  9. // ...
  10. }
  11. // ...
  12. }
  13. }

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

  1. // Use a Transformer for output
  2. // ...
  3. Transformer transformer = tFactory.newTransformer();
  4. // Use the parser as a SAX source for input
  5. FileReader fr = new FileReader(f);
  6. BufferedReader br = new BufferedReader(fr);
  7. InputSource inputSource = new InputSource(br);
  8. SAXSource source = new SAXSource(saxReader, inputSource);
  9. StreamResult result = new StreamResult(System.out);
  10. transformer.transform(source, result);

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

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

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

运行TransformationApp03示例

  1. 导航至样例目录。

    1. cd install-dir / jaxp-1_4_2- 释放日期 /样例。
  2. Download the XSLT examples by clicking this link 并将它们解压缩到 install-dir / jaxp-1_4_2- 释放日期 /样例目录。

  3. 导航到xslt目录。

    1. cd xslt
  4. Compile the TransformationApp03 sample.

    键入以下命令:

    1. % javac TransformationApp03.java
  5. Run the TransformationApp03 sample on a data file you wish to convert to XML.

    在下面的例子中, TransformationApp03运行在PersonalAddressBook.ldif文件上,在解压缩样例包后找到xslt / data目录。

    1. % java TransformationApp03
    2. data/PersonalAddressBook.ldif

    您将看到以下输出:

    1. &lt;?xml version="1.0" encoding="UTF-8"?&gt;
    2. &lt;addressbook&gt;
    3. &lt;nickname&gt;Fred&lt;/nickname&gt;
    4. &lt;email&gt;Fred@barneys.house&lt;/email&gt;
    5. &lt;html&gt;TRUE&lt;/html&gt;
    6. &lt;firstname&gt;Fred&lt;/firstname&gt;
    7. &lt;lastname&gt;Flintstone&lt;/lastname&gt;
    8. &lt;work&gt;999-Quarry&lt;/work&gt;
    9. &lt;home&gt;999-BedrockLane&lt;/home&gt;
    10. &lt;fax&gt;888-Squawk&lt;/fax&gt;
    11. &lt;pager&gt;777-pager&lt;/pager&gt;
    12. &lt;cell&gt;555-cell&lt;/cell&gt;
    13. &lt;/addressbook&gt;

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