簡介
當我們需要轉換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檔案,可能需要以下代碼:
File f = new File( " test.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-Family=Arial
Font-Size=12pt
Background-Color=White
Foreground-Color=Black
我們看到該檔案資料結構實作上是由若幹個key value pairs組成,現在我們需要讀取該檔案,并轉換為一系列的SAX事件:
public class PropertyFileParser implements XMLReader
{
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方法。
public void parse(InputSource source) throws IOException,
SAXException
{
InputStream is = source.getByteStream();
Properties p = new Properties();
p.load(is);
parse(p);
}
自定義的parse方法首先針對文檔的根元素利用startDocument()和startElement()事件進行廣播。通過疊代器周遊properties組成的enumeration,為每個屬性生成startElement(), characters(), endElement()事件。最後,根節點的endElement() 和endDocument()被調用。
private void parse(Properties p) throws SAXException
{
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進行事件廣播的接收者。
<? xml version="1.0" encoding="UTF-8" ?>
< 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。
public static void main(String[] args) throws Exception
{
// 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對象。以下是轉換後的結果:
<? xml version="1.0" encoding="UTF-8" ?>
< 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
原文出處:Transforming Flat Files To XML With SAX and XSLT