简介
当我们需要转换XML文件到其它格式文件的时候,XSLT (eXtensible Stylesheet Language for Transformations)是一个很好的选择。但是,有的时候我们需要将一个flat文件或者非XML数据结构转换为XML和其他标记性语言,如果我们能使用XSLT来转换以上的数据结构,那绝对是一件很爽的事情。
问题的答案是可以,我们可以使用SAX来 (Simple API for XML)做这件事情。本文将介绍如何创建一个JAVA类,通过该类转换JAVA的属性文件(*.properties)为XML格式。示例将完全证明这个观点,并且帮助你学习转换任何数据结构到XML文件的技术。
本文分为以下几个部分:
- SAX Parser和Handler Review
- 创建一个自定义的SAX Parser (it's easier than you think)
- The "Echo" Stylesheet
- 使用TrAX (Transformation API for XML) 转换SAX Source
总结
SAX Parser和Handler Review
如果你之前了解SAX,你应该知道SAX是将XML文档作为事件流处理的API,你可能写过handler类来接收这些事件。handler类会在下列这些事件触发时候得到通知:
- Start of Document
- Start of Element
- Characters
- End of Element
- End of Document
handler类会如你所愿响应这些事件,最容易的方法是通过继承DefaultHandler对象实现ContentHandler接口。
使用一个自定义的handler解析一个XML文件,可能需要以下代码:
ContentHandler handler = new YourCustomHandler();
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(file, handler);
SAXParser将执行YourCustomHandler中的回调函数。
创建一个自定义的SAX Parser
在与非XML数据结构打交道的时候,我们需要创建一个Parser用来对已注册的handler类进行SAX事件广播。我们甚至可以不写一个handler类,如果你习惯于动手写handler的话,这样看上去很奇怪。
SAXSource对象,用于表示用于转换的输入内容,需要用到一个关联TrAX API的parser。SAXSource对象创建时候以一个实现XMLReader接口的对象作为参数。这个接口包括几个方法,大多数在我们的例子中还用不到。
下面我们将创建一个基于XMLReader接口的实现,用于转换JAVA属性文件为一个XML事件流。该例子虽然简单,但已经足够证明可以将任意数据结构转换为XML格式。
用于转换的属性文件如下所示:
Font-Size=12pt
Background-Color=White
Foreground-Color=Black
我们看到该文件数据结构实现上是由若干个key value pairs组成,现在我们需要读取该文件,并转换为一系列的SAX事件:
{
private ContentHandler contentHandler = null ;
public ContentHandler getContentHandler()
{
return contentHandler;
}
public void setContentHandler(ContentHandler handler)
{
contentHandler = handler;
}
}
PropertyFileParser实现了XMLReader接口,尽管我们不是必须得自己写一个ContentHandler,但我们必须要为content handlers提供一种机制,用于从parser注册并接收事件。本节中TrAX 将为我们提供这样一个content handler。
我们的主要任务是实现parse() 方法,这是XMLReader接口所要求实现的。这里我们取得InputSource,并通过其载入Properties对象。然后,我们调用我们自定义的parse方法。
SAXException
{
InputStream is = source.getByteStream();
Properties p = new Properties();
p.load(is);
parse(p);
}
自定义的parse方法首先针对文档的根元素利用startDocument()和startElement()事件进行广播。通过迭代器遍历properties组成的enumeration,为每个属性生成startElement(), characters(), endElement()事件。最后,根节点的endElement() 和endDocument()被调用。
{
contentHandler.startDocument();
contentHandler.startElement(namespaceURI,
" Properties " ,
" Properties " , attribs);
Enumeration e = p.propertyNames();
while (e.hasMoreElements())
{
String key = (String)e.nextElement();
String value = (String)p.getProperty(key);
contentHandler.startElement(namespaceURI, key, key, attribs);
contentHandler.characters(value.toCharArray(), 0 ,
value.length());
contentHandler.endElement(namespaceURI, key, key);
}
contentHandler.endElement(namespaceURI, " Properties " ,
" Properties " );
contentHandler.endDocument();
}
为满足XMLReader接口的要求,我们通过空方法来实现其他几个接口方法,对于我们的SAX Parser来说自然是小菜一碟。完整的类请参见:PropertyFileParser.java。
Echo Stylesheet
接下来,我们将使用一个非常简单的Stylesheet对我们parser所关联的XML文档进行输出。该Stylesheet的文件名为echo.xsl,起这个名字的原因是我们对输入文档所作的转换很简单,也就是echo一下。到了这一步剩下的事情就非常简单了,和对XML文件的格式化输出几乎没有什么区别。在这里stylesheet用一种间接的方式扮演了handler的角色,或者说是parser进行事件广播的接收者。
< xsl:stylesheet version ="1.0"
xmlns:xsl ="http://www.w3.org/1999/XSL/Transform"
xmlns:fo ="http://www.w3.org/1999/XSL/Format" >
< xsl:output method ="xml" indent ="yes" />
< xsl:template match ="node()" >
< xsl:copy >
< xsl:apply-templates />
</ xsl:copy >
</ xsl:template >
</ xsl:stylesheet >
我们的"echo" stylesheet完成了一项"deep copy",这个单一的模版针对所有的文档节点,它复制上下文节点并且所有的子节点都执行了<xsl:apply-templates/>.。这种类型的模版针对XML文档进行全局性的修改是很有用的。
以下的代码并没有进行效率上的优化,如果有此需要请参考作者的另外一篇文章:Optimizing Stylesheet Execution With The Transformation API for XML。
{
// construct SAXSource with our custom XMLReader
InputStream props = ClassLoader.getSystemResourceAsStream
( " my.properties " );
InputSource inputSource = new InputSource(props);
XMLReader parser = new PropertyFileParser();
SAXSource saxSource = new SAXSource(parser, inputSource);
// construct a transformer using the echo stylesheet
TransformerFactory factory = TransformerFactory.newInstance();
StreamSource xslSource = new StreamSource( " echo.xsl " );
Transformer transformer = factory.newTransformer(xslSource);
// transform the SAXSource to the result
StreamResult result = new StreamResult( " properties.xml " );
transformer.transform(saxSource, result);
}
使用了TrAX API,main()方法完成了如下几个步骤:
SAXSource被创建,包括两个参数:PropertyFileParser以及input source(保存了需要解析的属性文件)。
利用"echo" stylesheet创建了transformer对象。
SAXSource被转换为一个Result对象。
虽然示例中我们为了输出文件使用了StreamResult,输出的结果也可以是DOM或者其他类型的Result对象。以下是转换后的结果:
< Properties >
< Background-Color > White </ Background-Color >
< Font-Family > Arial </ Font-Family >
< Font-Size > 12pt </ Font-Size >
< Foreground-Color > Black </ Foreground-Color > >
</ Properties >
总结:
转换非XML数据结构到XML是一个很普遍的问题。大多数的自定义方法都比较麻烦,步骤较多。我们通过示例证明使用标准的XML APIs来完成这项工作是完全可行的。我们的方法可以减少非必要的步骤,直接从源格式转化到目标XML格式。通过学习和举一反三,我们很容易发挥SAX和XSLT的效力,实现任意数据结构到XML格式的转换。
示例源码下载
原文作者:Jeff Ryan