簡介
當我們需要轉換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