作为XML文件写出DOM
不管是通过解析XML文件还是按部就班地构建出一个DOM,你可能都希望将其保存为XML。本节将告诉你如何利用Xalan转换包来实现这一目标。
利用这个包,可以创建一个转换器对象,并为StreamResult写一个DomSource。然后就可以调用该转换器的transform()方法来写出作为XML数据的DOM。
读取XML
第一步是通过解析XML文件创建一个DOM。到目前为止,你应该很熟悉该过程。
注意:本节讨论的代码可以在TransformationApp01.java中找到。
下面的代码提供了一个基本的模板。(大家应该对这些代码很熟悉。它们和在DOM教程的开始部分中编写的代码基本一样。如果你还没忘记的话,就会发现它们与下面的代码几乎相同。)
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.w3c.dom.Document;
import org.w3c.dom.DOMException;
import java.io.*;
public class TransformationApp
{
static Document document;
public static void main(String argv[])
{
if (argv.length != 1) {
System.err.println (
"Usage: java TransformationApp filename");
System.exit (1);
}
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
//factory.setNamespaceAware(true);
//factory.setValidating(true);
try {
File f = new File(argv[0]);
DocumentBuilder builder =
factory.newDocumentBuilder();
document = builder.parse(f);
} catch (SAXParseException spe) {
// Error generated by the parser
System.out.println("\n** Parsing error"
+ ", line " + spe.getLineNumber()
+ ", uri " + spe.getSystemId());
System.out.println(" " + spe.getMessage() );
// Use the contained exception, if any
Exception x = spe;
if (spe.getException() != null)
x = spe.getException();
x.printStackTrace();
} catch (SAXException sxe) {
// Error generated by this application
// (or a parser-initialization error)
Exception x = sxe;
if (sxe.getException() != null)
x = sxe.getException();
x.printStackTrace();
} catch (ParserConfigurationException pce) {
// Parser with specified options can't be built
pce.printStackTrace();
} catch (IOException ioe) {
// I/O error
ioe.printStackTrace();
}
} // main
}
创建转换器
下一步是创建一个可用来将XML传送到System.out的转换器。
让我们从添加下面的导入语句开始吧:
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
这里已添加的一系列类应该能够组成一个标准模式:一个实体(Transformer)、创建它的工厂(TransformerFactory)以及每个类可以生成的异常。由于转换需要有一个源(source)和一个结果(result),所以必须将使用DOM所需的类导出为源(DomSource),并且将输出流作为结果(StreamResult)。
下一步,添加实现转换的代码:
try {
File f = new File(argv[0]);
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse(f);
// Use a Transformer for output
TransformerFactory tFactory =
TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(System.out);
transformer.transform(source, result);
这里创建了一个转换器对象,用DOM构建了一个源对象,并且用System.out构建了一个结果对象。然后告诉转换器操作源对象并输出到结果对象。
注意:在这种情况下,“转换器”实际上并没有作任何变化。在XSLT术语中,使用身份转换(identity transform)来表示“转换”生成源的一个未变化的副本。
最后,添加下面的代码来捕获可能生成的新错误。
} catch (TransformerConfigurationException tce) {
// Error generated by the parser
System.out.println ("* Transformer Factory error");
System.out.println(" " + tce.getMessage() );
// Use the contained exception, if any
Throwable x = tce;
if (tce.getException() != null)
x = tce.getException();
x.printStackTrace();
} catch (TransformerException te) {
// Error generated by the parser
System.out.println ("* Transformation error");
System.out.println(" " + te.getMessage() );
// Use the contained exception, if any
Throwable x = te;
if (te.getException() != null)
x = te.getException();
x.printStackTrace();
} catch (SAXParseException spe) {
...
:
注意;
·
TransformerExceptions被转换器对象拋出。
·
TransformerConfigurationException被工厂拋出。
·
为了保存XML文档的DOCTYPE设置,有必要添加如下代码:
import javax.xml.transform.OutputKeys;
...
if (document.getDoctype() != null){
String systemValue = (new
File(document.getDoctype().getSystemId())).getName();
transformer.setOutputProperty(
OutputKeys.DOCTYPE_SYSTEM, systemValue
);
}
编写XML
关于如何编译和运行该程序的说明,可以参考SAX教程中的编译和运行程序。(如果你正在使用该教程,请把程序名Echo替换为TransformationApp。如果你正在编译该示例代码,请使用TransformationApp02。)如果运行了slideSample01. xml上的代码,将看到如下输出:
<?xml version="1.0" encoding="UTF-8"?>
<!-- A SAMPLE set of slides -->
<slideshow author="Yours Truly" date="Date of publication"
title="Sample Slide Show">
<!-- TITLE SLIDE -->
<slide type="all">
<title>Wake up to WonderWidgets!</title>
</slide>
<!-- OVERVIEW -->
<slide type="all">
<title>Overview</title>
<item>Why <em>WonderWidgets</em> are great</item>
<item/>
<item>Who <em>buys</em> WonderWidgets</item>
</slide>
</slideshow>
注意:属性的顺序可能根据所用解析器的不同而不同。
关于配置工厂和处理确认错误的更多信息,请参考将XML数据读入DOM。
编写DOM的子树
操作DOM的子树也是有可能的。让我们在本教程的这一节中试试这一选项。
注意:本节中讨论的代码可以在TransformationApp03.java中找到。其中输出在TransformationLog03.txt中。(可浏览的版本为TransformationLog03.html)。
该过程中的唯一区别在于创建DOMSource时,用的是DOM中的一个节点,而不是整个DOM。第一步,导入所需的类,以获得想要的节点。代码如下:
import org.w3c.dom.Document;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
下一步,找出一个合适的节点。通过添加如下代码来选中第一个<slide>元素。
try {
File f = new File(argv[0]);
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse(f);
// Get the first <slide> element in the DOM
NodeList list = document.getElementsByTagName("slide");
Node node = list.item(0);
最后,构建一个由发自该节点的子树组成的源对象:
DOMSource source = new DOMSource(document);
DOMSource source = new DOMSource(node);
StreamResult result = new StreamResult(System.out);
transformer.transform(source, result);
现在运行该应用,输出如下:
<?xml version="1.0" encoding="UTF-8"?>
<slide type="all">
<title>Wake up to WonderWidgets!</title>
</slide>
清除
现在通过如下操作,来取消在本节中添加的代码(TransformationApp04.java中包含了下面的代码)。
Import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
...
try {
...
// Get the first <slide> element in the DOM
NodeList list = document.getElementsByTagName("slide");
Node node = list.item(0);
...
DOMSource source = new DOMSource(node);
StreamResult result = new StreamResult(System.out);
transformer.transform(source, result);
小结
到此时为止,你已经知道了如何利用转换器编写DOM,并且知道如何将DOM的子树用作转换中的源对象。在下一节中,你将会看到如何利用转换器,从任何可以解析的数据结构中创建XML。
从任意数据结构创建XML
本节将告诉你如何利用XSLT将一个任意的数据结构转换成XML。
本节概要如下:
1. 修改读取数据的现有程序,以便让它生成SAX事件。(该程序仅是一个某种类型的数据过滤器,还是一个真正的解析器,在这里无关紧要)
2. 然后利用SAX“解析器”为该转换构建一个SAXSource。
3. 由于使用的StreamResult对象和在上一个练习中创建的相同,所以可以看到结果。(但要注意可以像创建DOMResult对象一样轻松地在内存中创建一个DOM)
4. 利用转换器对象来进行这个转换,从而将源捆绑到结果中。
首先,需要一个希望转换的数据集以及一个能够读取这些数据的程序。在下面的两节中,将创建一个简单的数据文件和一个读取这个文件的程序。
创建简单文件
首先为一个地址簿创建数据集。如果愿意,可以复制这个过程,或者只是利用PersonalAddressBook.ldif中存储的数据。
下面所示的文件是这样生成的:在Netscape Messenger中创建一个新地址簿,给它一些虚拟数据(一个地址卡片),然后将其导出为LDIF格式。
注意:LDIF表示LDAP数据交换格式(LDAP Data Interchange Format),而LDAP表示轻便目录访问协议(Lightweight Directory Access Protocol)。不过我倾向于认为它是行定界交换格式(Line Delimited Interchange Format),因为这样更能说明问题。
图8-1展示了创建的地址簿条目。
图
8-1 地址簿条目
导出地址簿会生成如下所示的文件。文件中我们关心的部分用黑体表示。
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中找到。输出在AddressBookReaderLog01.txt中。
该程序的文本如下所示。它是一个极其简单的程序,甚至连多个条目的循环都没有,因为毕竟这只是一个演示程序。
import java.io.*;
public class AddressBookReader
{
public static void main(String argv[])
{
// Check the arguments
if (argv.length != 1) {
System.err.println (
"Usage: java AddressBookReader filename");
System.exit (1);
}
String filename = argv[0];
File f = new File(filename);
AddressBookReader01 reader = new AddressBookReader01();
reader.parse(f);
}
/** Parse the input */
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 it's 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();
}
}
void output(String name, String prefix, String line)
{
int startIndex = prefix.length() + 2;
// 2=length of ": "
String text = line.substring(startIndex);
System.out.println(name + ": " + text);
}
}
该程序包含三个方法:
main
main方法从命令行中获得文件名,创建解析器的实例并对其进行设置以便能解析文件。该方法在我们将程序转换到SAX解析器时不起作用。(这就是我们将解析代码放到一个单独的方法中的原因。)
parse
该方法操作main子程序发送给它的那些File对象。可以看到这是多么的简单。在效率方面唯一有用的是BufferedReader的使用,这在操作大文件时很重要。
output
output方法包含某个行结构的逻辑。从右边开始,它采用了三个参数。第一个参数给该方法一个要显示的名称,所以我们可以将html作为一个变量名输出,而不是xmozillausehtmlmail。第二个参数给出存储在文件中的变量名(xmozillausehtmlmail)。第三个参数给出包含数据的行。该子程序然后从行的起始部分去掉该变量名,并输出所要的名称及数据。
运行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 transform中SAXSource对象的基础了。
注意:本节中讨论的代码可以在AddressBookReader02.java中找到。
让我们首先导入所需的的附加类:
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.AttributesImpl;
接着修改应用,以扩展XmlReader。这个改动将应用转换成一个能生成合适SAX事件的解析器。
public class AddressBookReader
implements XMLReader
{
这时,可以删除main方法,因为已经不再需要它了。
public static void main(String argv[])
{
// Check the arguments
if (argv.length != 1) {
System.err.println ("Usage: Java AddressBookReader
filename");
System.exit (1);
}
String filename = argv[0];
File f = new File(filename);
AddressBookReader02 reader = new AddressBookReader02();
reader.parse(f);
}
再添加一些稍后将会用到的全局变量:
public class AddressBookReader
implements XMLReader
{
ContentHandler handler;
// We're not doing namespaces, and we have no
// attributes on our elements.
String nsu = ""; // NamespaceURI
Attributes atts = new AttributesImpl();
String rootElement = "addressbook";
String indent = "\n "; // for readability!
SAX
ContentHandler是将用来获得解析器生成的SAX事件的对象。为了将应用装入XmlReader,需要定义一个setContentHandler方法。Handler变量会包含一个该对象的引用,其中该对象在setContentHandler被调用时被发送出去。
并且,当解析器生成SAX元素事件时,需要提供命名空间和属性信息。但由于这是一个简单的应用,所以可以将它们定义为空值。
还需要为数据结构定义一个根元素(addressbook),并创建一个缩进字符串,以增加可读性。
下一步,修改parse方法,以便它能够将InputSource看作一个参数,而不是将File当作参数。并且还要考虑它可能生成的异常。
public void parse(File f)InputSource input)
throws IOException, SAXException
现在把读取器封装到InputSource对象中,代码如下:
try {
// Get an efficient reader for the file
FileReader r = new FileReader(f);
java.io.Reader r = input.getCharacterStream();
BufferedReader Br = new BufferedReader(r);
注意:下一节中我们将创建输入源对象,其中输入的内容实际上一个缓冲读取器。但AddressBookReader可以被其他人用在行下面的某处。这一步骤保证不管给出的读取器如何,处理都是有效的。
下一步要做的是修改parse方法,以便为文档和根元素的开始生成SAX事件。为此,需要添加下面的代码:
/** Parse the input */
public void parse(InputSource input)
...
{
try {
...
// Read the file and display its contents.
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);
|