国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)

2019-11-18 12:46:57
字體:
供稿:網(wǎng)友


  摘要
  
  數(shù)據(jù)轉(zhuǎn)換總是難于實(shí)現(xiàn)的.XSLT (Extensible Stylesheet Language Transformations) 規(guī)范一個(gè)極好xml轉(zhuǎn)換工具,但是它僅能處理輸入數(shù)據(jù)是XML數(shù)據(jù)的情況, 而對于復(fù)雜轉(zhuǎn)換經(jīng)常是困難的,甚至是不可能的. 本文闡述了在將XML數(shù)據(jù)提供給XSLT轉(zhuǎn)換器之前如何使用SAX對其進(jìn)行”預(yù)處理 “. 預(yù)處理器能夠處理大多數(shù)復(fù)雜的任務(wù),從而為XSLT器減少了不少負(fù)擔(dān).本文附有幾個(gè)功能齊全的例子代碼 (可以從此處下載). 例子中使用的實(shí)施框架很容應(yīng)用到許多不同的轉(zhuǎn)換解決方案中. (5,000 字; 2005年9月5日)
  
  在一個(gè)需要將原始的帳單數(shù)據(jù)轉(zhuǎn)換為不同的帳單布局展現(xiàn)的項(xiàng)目中, 我曾經(jīng)被要求做簡單的數(shù)據(jù)轉(zhuǎn)換.看了問題的簡介后,我建議使用XSLT (Extensible Stylesheet Language Transformations).
  
  我進(jìn)一步研究需求后發(fā)現(xiàn)問題沒有我預(yù)想的那么簡單.雖然輸入數(shù)據(jù)是易于治理的, 需要做簡單轉(zhuǎn)換的數(shù)據(jù)不能用一組靜態(tài)的XSLT樣式表單描述.一部分需要轉(zhuǎn)換的數(shù)據(jù)是動(dòng)態(tài)的,而且存儲(chǔ)在兩個(gè)分離的數(shù)據(jù)庫中. 此外,為了呈現(xiàn)帳單的布局程序不得不對從兩個(gè)數(shù)據(jù)庫中取出的輸入數(shù)據(jù)做相對復(fù)雜的計(jì)算. XSLT解決方案被淡忘了.
  
  這個(gè)例子的核心問題是動(dòng)態(tài)數(shù)據(jù)需要直接轉(zhuǎn)換. 在理想情況下,我們從來不會(huì)面對這個(gè)問題. 輸入數(shù)據(jù)的預(yù)備和 輸入數(shù)珠的轉(zhuǎn)換應(yīng)該是清楚的分離開來的,從而所有需要轉(zhuǎn)換的數(shù)據(jù)能夠包含在一個(gè)單獨(dú)的XSLT模板中. 不幸的是,現(xiàn)實(shí)項(xiàng)目的需求有時(shí)候是相當(dāng)?shù)钠娈?
  
  本文給出了上面描述的問題的一個(gè)解決方案.我將通過例子來展示如何利用 SAX(Simple API for XML) 來提高XSLT的適用性. 另外我將展示在輸入數(shù)據(jù)和期望的輸出數(shù)據(jù)都不是XML時(shí)如何使用XSLT.
  
  XSLT簡介
  
  XSLT 是一種轉(zhuǎn)換XML數(shù)據(jù)的編程語言. XSLT 表單樣式可以用來將XML文檔另外一種XML格式的文檔, 實(shí)際上可以轉(zhuǎn)換成任意其它格式. 但是XSLT可能不是一種簡單易學(xué)的語言, 尤其對于那些對類似java語言比較熟悉的人來說,它以一種強(qiáng)大而靈活的方式來完成相對復(fù)雜的數(shù)據(jù)轉(zhuǎn)換.假如你對XSLT還不是很熟悉, 有很多優(yōu)秀的教程可用,例如, the XML Bible 的第17章.
  
  盡管XSLT是一種好的語言,但是用它來完成有些任務(wù)是困難的,甚至是不可能的. 對于必須對輸入的XML文檔的幾個(gè)元素做合并計(jì)算的這種轉(zhuǎn)換通常是可能行的, 但是通常極為難寫. 假如需要轉(zhuǎn)換的直接數(shù)據(jù)本身是動(dòng)態(tài)的,那么單獨(dú)使用XSLT就不夠了. XSLT模板本質(zhì)上是靜態(tài)的.盡管可以動(dòng)態(tài)的重新產(chǎn)生模板,我仍然不認(rèn)為這是一個(gè)可行的解決方案.(如有不同觀點(diǎn)請不吝賜教.)
  
  試驗(yàn)了各種想法之后,我得出結(jié)論,用XSLT來完成復(fù)雜數(shù)據(jù)轉(zhuǎn)換的最簡單的辦法是在將輸入數(shù)據(jù)提交給XSLT轉(zhuǎn)換器處理之前對輸入數(shù)據(jù)進(jìn)行巧妙的預(yù)處理. 這聽起來很令人費(fèi)解,而且效率不高, 但是結(jié)果證實(shí)使用SAX處理輸入的XML數(shù)據(jù)是相當(dāng)?shù)暮唵屋p易.
  
  SAX 是一種事件驅(qū)動(dòng)的用來解析XML文檔的接口. 當(dāng)SAX解析器處理XML數(shù)據(jù)時(shí),它產(chǎn)生解析器可以識(shí)別的XML元素的”回調(diào)(callback)”通知.例如,當(dāng)解析器碰到XML開始標(biāo)簽時(shí),它產(chǎn)生一個(gè)回調(diào)事件startElement.標(biāo)簽的名字和其它相關(guān)信息作為回調(diào)參數(shù)發(fā)送出去.當(dāng)需要高效地界些XML時(shí),應(yīng)該使用SAX. 關(guān)于更多的SAX信息請看Sun's tutorial on JAXP. 本文中,在將數(shù)據(jù)提交給XSLT轉(zhuǎn)換器之前我使用SAX來修改事件流.
  
  SAX 和XSLT兩者都包含在JAXP (Java API for XML PRocessing) API中, J2SE 1.4版以后的版本中已經(jīng)包含這個(gè)API.
  
  例子概要
  
  這部分介紹文章中的例子和SAX與XSLT結(jié)合的可能性.
  
  執(zhí)行這些例子
  
  假如你對執(zhí)行這些例子不感愛好,跳過這一部分. 但是本文嚴(yán)重依靠這些例子代碼,因此,建議您最好看看這些代碼.這些例子已經(jīng)在Windows環(huán)境的J2SE 1.4.2 下測試過. 執(zhí)行這些應(yīng)用不需要引入其它API包. 但是本文假設(shè)您使用Ant 編譯工具.假如您不愿意使用Ant, 那么你仍然可以編譯并執(zhí)行這些例子, 但是要額外多做一點(diǎn)點(diǎn)工作.
  
  具體的描述放在README.txt文件中, 它可以從下載的szip文件中取得. 您只要解開這個(gè)壓縮包并設(shè)置好相關(guān)變量(在README.txt中有說明),就可以使用下邊的Ant 命令了:
  
  ·ant build:刪除編譯過程產(chǎn)生的文件,重新編譯所有代碼
  
  ·ant clean:刪除編譯過程產(chǎn)生的文件
  
  ·ant example1: 執(zhí)行例子1
  
  ·ant example2: 執(zhí)行例子2
  
  ·ant example2b: 執(zhí)行例子2b (例子2的一個(gè)變種)
  
  ·ant example3: 執(zhí)行例子3
  
  ·ant example4: 執(zhí)行例子4
  
  例子1概要
  
  盡管例子1是一個(gè)基本的XSLT轉(zhuǎn)換器, 但是介紹這個(gè)例子是有必要的, 因?yàn)樗呛罄m(xù)例子被編譯的基礎(chǔ). 圖 1 展現(xiàn)了例子1的概念視圖.
  
 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖一)
點(diǎn)擊查看大圖

  圖 1. 例子一個(gè)的概念視圖.點(diǎn)擊看全圖.

  
  輸入數(shù)據(jù)(1.1) 為私有XML數(shù)據(jù), 它模擬了客戶訂單報(bào)告. 數(shù)據(jù)大致如下:
  
  <?xml version="1.0"?><ORDER_INFO>  <CUSTOMER GROUP="exclusive">   <ID>234</ID>     <SERVICE_ORDERS>      <ORDER>        <PRODUCT_ID>1231</PRODUCT_ID>          <PRICE>100</PRICE>        <TIMESTAMP>2004-06-05:14:40:05</TIMESTAMP>      </ORDER>      <ORDER>        <PRODUCT_ID>2001</PRODUCT_ID>          <PRICE>20</PRICE>        <TIMESTAMP>2004-06-12:15:00:44</TIMESTAMP>      </ORDER>     </SERVICE_ORDERS>  </CUSTOMER>...
  
  例子 1的完整輸入數(shù)據(jù)在<EXAMPLE_ROOT>/input/orderInfo_1.1.xml文件中. 從此, <EXAMPLE_ROOT> 指代您解壓本文例子的那個(gè)目錄.
  
  圖1的XSLT 模板 (1.2)是一個(gè)將輸入數(shù)據(jù)轉(zhuǎn)換為Html格式的常規(guī)XSLT 樣式表單. XSLT模板大致如下:
  
  <?xml version="1.0"?>
  
  <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  
  <xsl:output omit-xml-declaration="yes"/>
  
  <xsl:template match="/">
  
  <xsl:apply-templates select="ORDER_INFO"/>
  
  </xsl:template>
  
  <xsl:template match="ORDER_INFO">
  
  <HTML>
  
  <HEAD>
  
  <TITLE>Customers' Order information</TITLE>
  
  </HEAD>
  
  <BODY>
  
  <H1>Customers' Order information</H1>
  
  <xsl:apply-templates select="CUSTOMER"/>
  
  <xsl:apply-templates select="PRICE_SUMMARY"/>
  
  </BODY>
  
  </HTML>
  
  </xsl:template>...
  
  完整的模板在<EXAMPLE_ROOT>/template/transform_1.2.xml文件中. 當(dāng)您執(zhí)行例子1時(shí), 程序?qū)⑤敵鑫募懙?lt;EXAMPLE_ROOT>/output/result_1.3.html文件中.
  
  為了讓讀者熟悉XSLT, 例子1應(yīng)該是一個(gè)很簡單的好的編程練習(xí).這個(gè)XSLT 模板僅僅包含格式化輸出的必要數(shù)據(jù),而不包含任何復(fù)雜計(jì)算. 我們繼續(xù)看更有挑戰(zhàn)的例子2.
  
  例子2概要
  
  例子2可能是本文最有趣的一個(gè)例子.這個(gè)例子實(shí)際上由兩個(gè)不同的轉(zhuǎn)換組成(轉(zhuǎn)換2a和轉(zhuǎn)換2b), 我們分別考慮這兩部分,首先我們來看轉(zhuǎn)換2a. 例子2的概念視圖視圖如圖2所示.
  
<a href="/uploadImages/2007-5-2/2007529494597871.gif" target="_blank"> 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖二)</a>
  點(diǎn)擊看大圖
  圖 2. 例子2的概念視圖. 點(diǎn)擊看全圖.

  
  輸入數(shù)據(jù) (1.1) 和 XSLT模板(1.2) 與例子1的相應(yīng)部分對應(yīng).在XSLT轉(zhuǎn)換被應(yīng)用之前,輸入數(shù)據(jù)通過由一組JAVA類組成的預(yù)處理器,預(yù)處理器通過SAX的時(shí)間過濾特性來操作XML數(shù)據(jù). 數(shù)據(jù)源(datasource)是一組實(shí)現(xiàn)了一種虛數(shù)據(jù)源的JAVA類. 在真實(shí)的應(yīng)用程序中, 數(shù)據(jù)源可能是數(shù)據(jù)庫接口. 引入這個(gè)虛數(shù)據(jù)庫是為了以一種極為簡單模式來展示從外部數(shù)據(jù)源取回的動(dòng)態(tài)XML數(shù)據(jù). 我希望讓這例子盡可能的輕易安裝和執(zhí)行, 因此沒有實(shí)現(xiàn)任何真實(shí)的數(shù)據(jù)庫連接――希望虛數(shù)據(jù)源實(shí)現(xiàn)可以給你這個(gè)概念.
  
  在執(zhí)行例子時(shí),程序(假如mode參數(shù)設(shè)置為debug)回顯來自預(yù)處理器的XML數(shù)據(jù)給標(biāo)準(zhǔn)輸出流(System.out). 這些數(shù)據(jù)類似預(yù)處理器的輸出數(shù)據(jù), 現(xiàn)在它是轉(zhuǎn)換器的輸入數(shù)據(jù).回顯預(yù)處理器的輸出數(shù)據(jù)是一種方便的調(diào)試預(yù)處理轉(zhuǎn)換過程的方式. 本文稍后討論這個(gè)特性的實(shí)現(xiàn).
  
  在執(zhí)行轉(zhuǎn)換2a時(shí), 下面數(shù)據(jù)被回顯到屏幕:
  
  <ORDER_INFO>
  
  <CUSTOMER GROUP="exclusive">
  
  <ID>     Jill   </ID>
  
  <SERVICE_ORDERS>
  
  <ORDER>
  
  <PRODUCT_ID>
  
  Doohickey
  
  </PRODUCT_ID>
  
  <PRICE>        100      </PRICE>
  
  </ORDER>...
  
  假如您把這個(gè)數(shù)據(jù)和原始輸入數(shù)據(jù)(<EXAMPLE_ROOT>/input/orderInfo_1.1.xml)比較一下, 將會(huì)注重到在數(shù)據(jù)的開始部分有如下不同:
  
  ·第一個(gè)CUSTOMER/ID元素的值從234變?yōu)镴ill.
  
  ·第一個(gè) CUSTOMER/SERVICE_ORDERS/ORDER/PRODUCT_ID元素的值從1231變?yōu)镈oohickey.
  
  ·TIMESTAMP元素被去除掉了.
  
  預(yù)處理器已經(jīng)用從內(nèi)部模擬數(shù)據(jù)庫來的數(shù)據(jù)替換了CUSTOMER/ID和 CUSTOMER/SERVICE_ORDERS/ORDER/PRODUCT_ID元素的值. 同時(shí)它過濾掉了TIMESTAMP元素和它的值. 這些修改后的數(shù)據(jù)現(xiàn)在提供給XSLT轉(zhuǎn)換器作為輸入.
  
  轉(zhuǎn)換 2a的輸出文件是<EXAMPLE_ROOT>/output/result_2.1.html.
  
  在您執(zhí)行轉(zhuǎn)化2b時(shí), 咋一看回顯的屏幕的數(shù)據(jù)與轉(zhuǎn)換2a的回顯數(shù)據(jù)很相似. 不同在于預(yù)處理器在最后部分插入了PRICE_SUMMARY元素:
  
  <PRICE_SUMMARY>
  
  <PRODUCT>
  
  <NAME>
  
  Doohickey
  
  </NAME>
  
  <SUM>      110     </SUM>
  
  </PRODUCT>
  
  <PRODUCT>
  
  <NAME>      Nose Cleaner     </NAME>
  
  <SUM>      10     </SUM>
  
  </PRODUCT>
  
  <PRODUCT>
  
  <NAME>
  
  Raccoon     </NAME>
  
  <SUM>      40     </SUM>
  
  </PRODUCT>
  
  </PRICE_SUMMARY>
  
  </ORDER_INFO>
  
  這個(gè)例子的目的是用來證實(shí)預(yù)處理器也可以用來引入新的XML元素, 元素的值可以直接來自輸入的XML數(shù)據(jù)的計(jì)算, 也可以使用一些來自于外部數(shù)據(jù)源的補(bǔ)充數(shù)據(jù).
  
  例子2b 的輸出文件是 <EXAMPLE_ROOT>/output/result_2.1b.html.
  
  這些例子為什么令人感愛好?在概念層面上,這個(gè)數(shù)據(jù)轉(zhuǎn)換方法似乎并沒有什么創(chuàng)新. 令人感愛好的是使用SAX對輸入的XML數(shù)據(jù)做微小的動(dòng)態(tài)的改善是相對輕易實(shí)現(xiàn)的, 而這可是使本來使用純XSLT無法實(shí)現(xiàn)的轉(zhuǎn)換成為可能. 另一方面, 僅僅使用SAX來實(shí)現(xiàn)整個(gè)轉(zhuǎn)換是有可能的,但是有點(diǎn)單調(diào)乏味. 在對復(fù)雜數(shù)據(jù)的實(shí)施轉(zhuǎn)換時(shí)使用SAX和XSLT相結(jié)合幾乎無所不能.
  
  在"深入研究例子2"部分,我會(huì)討論如何擴(kuò)展帶有預(yù)處理器的XSLT轉(zhuǎn)換器的模式. 你將會(huì)看到,只要稍微修改一下本文的例子代碼,它就可以有很多不同的用途.
  
  例子3和例子4提升了例子2對非XML數(shù)據(jù)的讀寫能力. 假如你對這些不感愛好,可以直接例子1和例子2的實(shí)施細(xì)節(jié)部分.
  
  例子3概要
  
  有時(shí)候, 輸入數(shù)據(jù)來自幾個(gè)不同的數(shù)據(jù)源;有時(shí)候,這些數(shù)據(jù)并非都時(shí)XML格式的數(shù)據(jù).例子3說明了對于非XML數(shù)據(jù)如何使用SAX產(chǎn)生事件, 從而使應(yīng)用XSLT成為可能. 例子3的概念視圖如圖3.
  
<a href="/uploadImages/2007-5-2/2007529494642024.gif" target="_blank"> 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖三)</a>
  點(diǎn)擊看大圖
  圖 3. 例子3的概念視圖. 點(diǎn)擊看全圖.

  
  例子3的輸入數(shù)據(jù)大致如下:
  
  3exclusive:2342Order:1231Price:100Timestamp:2004-06-05:14:40:05Order:2001Price:20Timestamp:2004-06-12:15:00:44...
  
  例子3的完整輸入文件是<EXAMPLE_ROOT>/input/orderInfoAsText_3.1.txt.
  
  XML產(chǎn)生器讀取這些數(shù)據(jù)并產(chǎn)生SAX事件,用以匹配前面例子(例子1和例子2)的XML輸入文檔. 從而預(yù)處理器接收到的數(shù)據(jù)與例子2的預(yù)處理器接收到的輸入數(shù)據(jù)是相似的.轉(zhuǎn)換的其余部分與轉(zhuǎn)換2b類似. 結(jié)果輸出文件(<EXAMPLE_ROOT>/input/result_3.2.html) is also similar to 與轉(zhuǎn)換2b的輸出文件 (file <EXAMPLE_ROOT>/output/result_2.1b.html)也是相似的.
  
  例子4概要
  
  XML并非總是我們希望得到的輸出格式. 此例類似前例,但是它產(chǎn)生的是一個(gè)純文本文件的輸出而不是XML文件T. 見圖4,它是個(gè)概念視圖.
  
<a href="/uploadImages/2007-5-2/2007529494698811.gif" target="_blank"> 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖四)</a>
  點(diǎn)擊看大圖
  圖 4. 例子4的概念視圖4. 點(diǎn)擊可見完整尺寸的圖片

  
  輸入數(shù)據(jù)跟例子3相同(<EXAMPLE_ROOT>/input/orderInfoAsText_3.1.txt), 而XSLT 模板不同. 既然讀者現(xiàn)在已經(jīng)熟悉XSLT, XSLT轉(zhuǎn)換也可以用來產(chǎn)生非XML數(shù)據(jù). XSLT 模板 (4.1) 是個(gè)用來將輸入數(shù)據(jù)轉(zhuǎn)換為文本根是的規(guī)則的XSLT 樣式表單, 模板大致如下:
  
  <?xml version="1.0"?>
  
  <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  
  <xsl:output method="text" omit-xml-declaration="yes"/>
  
  <xsl:template match="/">
  
  <xsl:apply-templates select="ORDER_INFO"/>
  
  </xsl:template>
  
  <xsl:template match="ORDER_INFO">Customers' Order information
  
  <xsl:apply-templates select="CUSTOMER"/>
  
  <xsl:apply-templates select="PRICE_SUMMARY"/>
  
  </xsl:template>
  
  <xsl:template match="CUSTOMER">Customer id:<xsl:value-of select="ID"/>
  
  Customer group is '<xsl:value-of select="@GROUP"/>'
  
  <xsl:apply-templates select="SERVICE_ORDERS"/>
  
  </xsl:template>...
  
  完整的轉(zhuǎn)換模板<EXAMPLE_ROOT>/template/transform_4.1.xml文件中. 當(dāng)運(yùn)行例子4的時(shí)候,程序?qū)⑤敵鰧懺?lt;EXAMPLE_ROOT>/output/result_4.2.txt中
  
  除了使用XSLT模板以外,例子4的代碼和例子3的代碼是一樣的.
  
  代碼查看
  
  讓我們看看代碼的實(shí)現(xiàn)
  
  Example 1
  
  Example 1的Transformer實(shí)體由class Example1 ,ExampleTester和Example接口實(shí)現(xiàn)。Class ExampleTester解析輸入?yún)?shù),創(chuàng)建Example1的實(shí)例,Example1實(shí)現(xiàn)了Example接口,并且調(diào)用doTransform()方法。圖例5是Example的類圖。綠色框內(nèi)的是J2SE的標(biāo)準(zhǔn)庫。如StreamSource,StreamResult和javax.xml.transform.stream
  
 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖五)
  Figure 5. Class diagram of Example 1. Click on thumbnail to view full-sized image.

  
  Example1 的doTransform() 方法 in Example1 比較有趣. 讓我們仔細(xì)閱讀:
  
  1. public doTransform(String _inputFileName,      String _transformerFileName,      String _outputFileName) {2.   try {3.    initTransformer(_transformerFileName);4.   } catch (TransformerConfigurationException tce) {5.     // omitted for clarity6.   } 7.   Source myXMLSource = getInputSourceObject(_inputFileName);8.   Result myResult = getResultObject(_outputFileName)9.   try {   10.    myTransformer.transform(myXMLSource, myResult); 11.  } catch (TransformerException te) {12.    // omitted for clarity 13.  }14. }
  
  doTransform() 方法有3個(gè)參數(shù):
  
  ·_inputFileName: 讀入xml數(shù)據(jù)的源文件名
  
  ·_transformerFileName: XSLT 文件名
  
  ·_outputFileName: 轉(zhuǎn)換XML數(shù)據(jù)后輸出的文件名
  
  在第3行, 調(diào)用initTransformer() 方法來創(chuàng)建XSLT 轉(zhuǎn)換器(transformer). 類 myXMLSource 是需要被轉(zhuǎn)換的數(shù)據(jù)源. 在第7行, 調(diào)用getInputSource() 來創(chuàng)建myXMLSource 實(shí)例. 源數(shù)據(jù)轉(zhuǎn)換后寫入myResult 對象,在第8行, getResultObject() 方法根據(jù)傳入的參數(shù)_outputFileName 來創(chuàng)建輸出文件. 做好這些預(yù)備步驟后,轉(zhuǎn)換工作在第10行完成:
  
  myTransformer.transform(myXMLSource, myResult);
  
  開始進(jìn)入第2個(gè)例子前,考慮一下getInputSourceObject()方法創(chuàng)建的source實(shí)例:
  
  Source myXMLSource = getInputSourceObject(_inputFileName);
  
  這是 XSLT 轉(zhuǎn)換器的數(shù)據(jù)源. 3個(gè)類實(shí)現(xiàn)了Source接口: StreamSource, SAXSource, and DOMSource. 這篇文章只用了 StreamSource 和 SAXSource (see Figure 6).
  
<a href="/uploadImages/2007-5-2/2007529494789829.gif" target="_blank"> 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖六)</a>
  點(diǎn)擊看大圖
  Figure 6. XSLT sources. Click on thumbnail to view full-sized image.
  

  Example 1中, getInputSourceObject() 方法返回一個(gè) StreamSource對象. 數(shù)據(jù)來自文件 (<EXAMPLE_ROOT>/input/orderInfo_1.1.xml) 。
  
  下個(gè)例子的XML數(shù)據(jù)依然來自文件,不同的是使用了SAX事件流處理。 實(shí)際上,Example 1的轉(zhuǎn)換器接受事件流比接受文件流不會(huì)有多大麻煩。 然而在下一個(gè)例子中,整個(gè)思想基于捕捉,操控SAX事件流的能力
  
  A deeper look into Example 2
  
  Example 2's 預(yù)處理的實(shí)現(xiàn)包含 (in Transformation 2a) 類 Example2 和包 myutil.里的類
  
  類 Example2 繼續(xù)了Example1. 該類重載了 getInputSourceObject()方法,聲明的命令工廠的名字. 先仔細(xì)研究getInputSourceObject():
  
  1. protected Source getInputSourceObject(String _pathName) throws FileNotFoundException {
  
  2. InputSource inputSource = getInputSourceFromFile(_pathName);
  
  3. XMLReader xmlFilter = getFilteringReader();
  
  4. SAXSource saxSource = new SAXSource(xmlFilter, inputSource);
  
  5. return saxSource;
  
  6. }
  
  第2行, getInputSourceFromFile() 方法從文件中創(chuàng)建InputSource 實(shí)例 ,第4行, SAXSource 實(shí)例被InputSource 和 XMLReader創(chuàng)建。 XMLReader 實(shí)例的創(chuàng)建值得關(guān)注,讓我們進(jìn)入getFilteringReader 方法看看:
  
  1. private XMLReader getFilteringReader() {
  
  2.   XMLReader myReader = getReader();
  
  3.   XMLFilterImpl xmlFilter = new ModifyingXMLSource(myReader, FACTORY_NAME);
  
  4.   if ((MODE != null) && (MODE.equals("debug"))) {
  
  5.    xmlFilter = new XMLPrinter(xmlFilter);
  
  6.   }
  
  7.   return xmlFilter;8. }
  
  第2行, XMLReader 被創(chuàng)建. 這是缺省的reader,它解析原始輸入數(shù)據(jù)并發(fā)送SAX事件到內(nèi)容處理器―― 內(nèi)容處理器在這里就是XSLT轉(zhuǎn)換器。第3行創(chuàng)建了 ModifyingXMLSource實(shí)例, 它是一個(gè)SAX事件過濾器的實(shí)現(xiàn),是這篇文檔的核心.第 5行,第2個(gè)事件過濾器,XMLPrinter被創(chuàng)建。 Figure 7 顯示結(jié)果對象的結(jié)構(gòu)。
  
 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖七)
點(diǎn)擊查看大圖

  Figure 7. Object diagram of Example 2. Click on thumbnail to view full-sized image.
  

  SAXSource現(xiàn)在運(yùn)行后,SAX事件先被ModifyingXMLSource 對象處理, 而后決定是否下一個(gè)事件過濾器XMLPrinter處理. 你會(huì)看到, ModifyingXMLSource 也可生成并轉(zhuǎn)發(fā)新事件。XMLPrinter 轉(zhuǎn)發(fā)所有事件到最終目標(biāo)XSLT transformer. Figure 8 顯示了這個(gè)class的結(jié)構(gòu)。
  
<a href="/uploadImages/2007-5-2/2007529494878685.gif" target="_blank"> 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖八)</a>
  點(diǎn)擊看大圖
  Figure 8. Class diagram of Example 2. Click on thumbnail to view full-sized image.
  

  為了明白fillter的功能,讓我們看看XMLPrinter 的代碼, XMLPrinter打印XML 數(shù)據(jù)到標(biāo)準(zhǔn)輸出流并且轉(zhuǎn)發(fā)SAX事件到他的父類。他的結(jié)構(gòu)如下:
  
  public class XMLPrinter extends XMLFilterImpl {
  
  private CharArrayWriter contents = new CharArrayWriter();
  
  private String indent = "";
  
  public XMLPrinter(XMLReader _reader)
  
  {   super(_reader);    };
  
  public void startElement(String _uri,
  
  String _localName,
  
  String _qName,
  
  Attributes _atts) throws SAXException
  
  {   // Print the indentation    // Print start tag    // Increase the indent
  
  super.startElement(_uri, _localName, _qName, _atts);
  
  }  public void characters(char[] _ch,
  
  int _start,
  
  int _length) throws SAXException {
  
  contents.write(_ch, _start, _length);
  
  super.characters(_ch, _start, _length);  };
  
  public void endElement(String _uri,
  
  String _localName,
  
  String _qName) throws SAXException {
  
  XMLPrinter 繼續(xù)了 XMLFilterImpl, XMLFilterImpl 提供了全部SAX事件回調(diào)方法的默認(rèn)實(shí)現(xiàn)。 假如你不重載任何方法,事件流通過這個(gè)過濾器不會(huì)有任何改變。 XMLPrinter 包含方法 startElement(), characters(), 和 endElement(), 這些是打印XML文檔基本內(nèi)容到標(biāo)準(zhǔn)輸出流的必需的方法。 當(dāng)碰到XML元素的標(biāo)簽時(shí), startElement()總是被調(diào)用(例如, <TIMESTAMP>). 碰到XML元素的內(nèi)容時(shí),characters() 方法被調(diào)用 (對如 TIMESTAMP 元素, 內(nèi)容就是 2004-06-05:14:40:05). 碰到XML元素的結(jié)束標(biāo)簽時(shí), endElement() 被調(diào)用。
  
  例如,在XMLPrinter, 可打印的XML開始標(biāo)簽被構(gòu)建和打印在startElement()方法內(nèi),注重在每個(gè)方法的結(jié)束時(shí),父類的相應(yīng)方法總是被調(diào)用。 因此,整個(gè)事件流保持不變。
  
  對于ModifyingXMLSource, 象 XMLPrinter一樣, 重載 了startElement(), characters(), and endElement()方法. 這個(gè)類使用回調(diào)方法startElement(), characters(), or endElement()處理接受到的SAX事件。它查找Command 對象來處理事件,大部分的邏輯處理在分開的command類里實(shí)現(xiàn),這樣可以保持 ModifyingXMLSource 小而簡單。
  
  讓我們看看Commands 怎樣和XML的元素映射. command-to-element 的映射在工廠類里完成。 創(chuàng)建 ModifyingXMLSource 時(shí),初始化了一個(gè) command 工廠. 在Example 2 (Transformation 2a), 這個(gè)工廠時(shí)Example2CommandFactory 類。
  
  1. public Example2CommandFactory() {
  
  2.   CustomerIdCommand myCustomerIdCommand = new CustomerIdCommand();
  
  3.   commands.put("/ORDER_INFO/CUSTOMER/ID", myCustomerIdCommand);
  
  4.   FilterCommand myFilterCommand = new FilterCommand();
  
  5.   commands.put(     "/ORDER_INFO/CUSTOMER/SERVICE_ORDERS/ORDER/TIMESTAMP",     myFilterCommand);
  
  6.   ProductIdCommand myProductIdCommand = new ProductIdCommand();
  
  7.   commands.put(     "/ORDER_INFO/CUSTOMER/SERVICE_ORDERS/ORDER/PRODUCT_ID",     myProductIdCommand);
  
  8. }
  
  第3行, 元素Customer的子元素ID 映射到 CustomerIdCommand 處理,第5行 TIMESTAMP 元素映射到 FilterCommand, 第7行 PRODUCT_ID元素映射到 ProductIdCommand. 全部 command 類實(shí)現(xiàn)了Command 接口:
  
  1. public interface Command {
  
  2.   public void reset();
  
  3.   public Object getResult();
  
  4.   public void startElement(String _uri,
  
  String _localName,
  
  String _qName,
  
  Attributes _atts,
  
  XMLFilterImpl _caller,
  
  DefaultHandlerInterface _default) throws SAXException;
  
  5.   public void characters(char[] ch,
  
  int start,
  
  int length,
  
  XMLFilterImpl _caller,
  
  DefaultHandlerInterface _default) throws SAXException;
  
  6.   public void endElement(String _uri,
  
  String _localName,
  
  String _qName,
  
  XMLFilterImpl _caller,
  
  DefaultHandlerInterface _default) throws SAXException;
  
  7. }
  
  Commands 可以用來收集和合并數(shù)據(jù),所以重置command的狀態(tài)顯然是需要的。 The getResult() 可以 method被其他command調(diào)用,用于交換信息 。有了startElement(), characters(), and endElement() (Lines 4, 5, and 6), ModifyingXMLSource 可以很輕易委托 SAX 事件給Command對象處理。
  
  ModifyingXMLSource 通過工廠獲得commands, 如他的 startElement()方法所示:
  
  1. public void startElement(String _uri,
  
  String _localName,
  
  String _qName,
  
  Attributes _atts) throws SAXException {
  
  2.   tagIdentifier += ("/" + _localName);
  
  3.   try {
  
  4.    currentCommand = factory.getCommand(tagIdentifier);
  
  5.    currentCommand.startElement(_uri, _localName, _qName, _atts,     this, this);
  
  6.   } catch (SAXException sax) {...
  
  characters() 和 endElement() 方法 遵循同一個(gè)原理, tagIdentifier() 跟蹤XML事件流中元素,用于獲取正確的Command。
  
  讓我們首先看看 FilterCommand 類, 當(dāng) tagIdentifier 值等于/ORDER_INFO/CUSTOMER/SERVICE_ORDERS/ORDER/TIMESTAMP, 工廠類factory 返回 FilterCommand對象. FilterCommand's startElement(), characters(), 和 endElement() 方法什么也沒處理,NullCommand一樣,<TIMESTAMP>元素隨他的內(nèi)容一起原樣慮過
  
  開始查看CustomerIdCommand 和 ProductIdCommand前, 看看Figure 9里的類的結(jié)構(gòu) (注重圖中CustomerIdCommand 省略了).
  
<a href="/uploadImages/2007-5-2/2007529494885166.gif" target="_blank"> 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖九)</a>
  點(diǎn)擊看大圖
  Figure 9. Class diagram of Example
  

  2, continued. Click on thumbnail to view full-sized image.
  
  CustomerIdCommand 和 ProductIdCommand commands 從datasource (參看 Figure 2)中接受數(shù)據(jù).下列代碼演示了怎樣把ID映射成名字字符串 :
  
  1. public void characters(char[] ch,     int start,     int length,     XMLFilterImpl _caller,     DefaultHandlerInterface _default) throws SAXException {
  
  2.   contents.write(ch, start, length);
  
  3. };
  
  4. public void endElement(String _uri,
  
  String _localName,
  
  String _qName,
  
  XMLFilterImpl _caller,
  
  DefaultHandlerInterface _default) throws SAXException {
  
  5.   idString = contents.toString();
  
  6.   DataaccessorFactory myFactory =
  
  DataAccessorFactory.getFactory();
  
  7.   DataAccessor myAccessor = myFactory.getDataAccessor();
  
  8.   idString = myAccessor.getProductName(idString);
  
  9.   _default.defaultCharactersHandler(idString.toCharArray(), 0,     idString.length());
  
  10.  _default.defaultEndElementHandler(_uri, _localName, _qName);11. };
  
  竅門在于characters() 方法沒有馬上調(diào)用 defaultCharactersHandler, 因此原始數(shù)據(jù)沒有被轉(zhuǎn)發(fā)(forWord)。 在endElement() 方法, defaultCharactersHandler() 和 defaultElementHandler() 被調(diào)用called. defaultCharactersHandler()調(diào)用時(shí) is 傳如了來自DataAccessor的改變的值called with a modified value received from DataAccessor. DataAccessor 接口interface如下:
  
  1. package myutil.dataAccess;
  
  2. public interface DataAccessor {
  
  3.   public String getCustomerName(String _customerId);
  
  4.   public String getProductName(String _productId);
  
  5. }
  
  getProductName()根據(jù)給定的參數(shù)返回產(chǎn)品名,在這個(gè)例子中時(shí)參數(shù)是1231。
  
  CustomerIdCommand與 ProductIdCommand類似,不同點(diǎn)在于它調(diào)用的是getCustomerName()而不是getProductName()。
  
  Example 2 的第2部分是Transformation 2b, 它添加小結(jié)信息到XML數(shù)據(jù)中 (PRICE_SUMMARY 元素). Example2b 如下:
  
  1. public class Example2b extends Example2 {
  
  2.   public Example2b() {
  
  3.    super();
  
  4.    FACTORY_NAME = "Example2b";
  
  5.   }
  
  6. }
  
  正如你看到,他的美麗的設(shè)計(jì),在于只要繼續(xù)Example2,賦給command工廠一個(gè)新的名字 。
  
  在 CommandFactory's getInstance()方法中,工廠名 Example2b 映射到Example2bCommandFactory 對象。 Example2bCommandFactory 與Example2CommandFactory 類似。不同點(diǎn)在于Example2bCommandFactory'的構(gòu)造函數(shù)包含如下新行:
  
  1. PriceCollectorCommand myPriceCollectorCommand =    new PriceCollectorCommand(myProductIdCommand);
  
  2. commands.put("/ORDER_INFO/CUSTOMER/SERVICE_ORDERS/ORDER/PRICE",    myPriceCollectorCommand);
  
  3. commands.put("/ORDER_INFO",    new PriceSummaryPrintingCommand(myPriceCollectorCommand));
  
  PriceCollectorCommand從輸入XML數(shù)據(jù)中收集全部 PRICE 元素。 PriceSummaryPrintingCommand 要從PriceCollectorCommand收集Price信息并輸出成XML格式。所以,PriceSummaryPrintingCommand 引用PriceCollectorCommand對象在他的構(gòu)造函數(shù)中 (第3行). 圖 10 為 Example 2b的類圖。
  
 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖十)
點(diǎn)擊查看大圖

  Figure 10. Class diagram of Example
  

  2, Transformation 2b. Click on thumbnail to view full-sized image.
  
  注重僅僅新的Command類顯示在圖10中。 實(shí)際上 Example2bCommandFactory與Example2CommandFactory一樣, 有相同的Command 依靠。
  
  讓我們深入查看一個(gè) Command 類. 下面是PriceCollectorCommand 的部分代碼:
  
  1. public void endElement(String _uri,
  
  String _localName,
  
  String _qName,
  
  XMLFilterImpl _caller,
  
  DefaultHandlerInterface _default) throws SAXException {
  
  2.   String productIdAsKey = (String)myProductIdCommand.getResult();
  
  3.   String priceAsString = contents.toString();
  
  4.   Integer price = priceToNumber(priceAsString);
  
  5.   if (price != null) {
  
  6.    addPriceToHashMap(productIdAsKey, price);
  
  7.   }
  
  8.   _default.defaultEndElementHandler(_uri, _localName, _qName);
  
  9. };
  
  在endElement() 方法中,addPriceToHashMap() 方法存入了每種產(chǎn)品的總值,詳情請看PriceCollectorCommand.java的代碼。
  
  同樣,PriceSummaryPrintingCommand 的endElement() 方法的代碼是令人感愛好的:
  
  1. public void endElement(String _uri,
  
  String _localName,
  
  String _qName,
  
  XMLFilterImpl _caller,
  
  DefaultHandlerInterface _default) throws SAXException {
  
  2.   insertSummary(_default, _uri);
  
  3.   _default.defaultEndElementHandler(_uri, _localName, _qName);
  
  4. };
  
  在insertSummary() 方法中, PriceSummaryPrintingCommand 在默認(rèn)的結(jié)束元素處理事件前插入他的XML 內(nèi)容 。insertSummary() 從PriceCollectorCommand 的 getResult() 方法中接受價(jià)格匯總信息,然后根據(jù)hashmap的內(nèi)容激發(fā)SAX事件。 Command 類使用了ModifyingXMLSource的默認(rèn)方法 (defaultstartElementHandler(), defaultCharactersHandler(), 和 defaultEndElementHandler()) 來激發(fā)新的SAX事件.詳見 PriceSummaryPrintingCommand.java.
  
  深入查看Example3
  
  Example 3可以從非XML數(shù)據(jù)生成新的XML元素,這不是一個(gè)生疏的主意。他的竅門是有了我們自己的XML reader (見 Figure 11).
  
 使用SAX和XSLT實(shí)現(xiàn)復(fù)雜數(shù)據(jù)轉(zhuǎn)換(組圖)(圖十)
  Figure 11. Class diagram of Example 3
  

  類Example3很簡單:
  
  1. import org.xml.sax.XMLReader;
  
  2. import org.xml.sax.SAXException;
  
  3. import org.xml.sax.helpers.XMLReaderFactory;
  
  4. public class Example3 extends Example2 {
  
  5.   public Example3() {
  
  6.    super();
  
  7.    FACTORY_NAME = "Example2b";
  
  8.   }
  
  9.   protected XMLReader getReader() {
  
  10.    XMLReader myReader = new Example3Reader();
  
  11.    return myReader;
  
  12.  }
  
  13. }
  
  在這個(gè)類里,只有重載的 getReader() 方法是新的。 因?yàn)檩斎霐?shù)據(jù)不是XML數(shù)據(jù),所以我們需要使用自己定制的reader, 它在第10行創(chuàng)建。
  
  Example3Reader 需要實(shí)現(xiàn)XMLReader接口的全部方法。然而,接口中的大部分方法可以虛擬實(shí)現(xiàn)(原文為dummy implementation, 理解為方法體內(nèi)直接返回)。值得關(guān)注的是parse(), setContentHandler(), 和 getContentHandler(). Example3Reader包含 一個(gè)成員類 ContentHandler ,變量名為myHandler. setContentHandler() 是他的設(shè)置方法:
  
  1. public void setContentHandler(ContentHandler _handler) {
  
  2.   myHandler = _handler;
  
  3. }
  
  ContentHandler 提供了產(chǎn)生SAX事件回調(diào)方法。Transformer 在XSLT轉(zhuǎn)換開始前調(diào)用setContentHandler() 方法。在這個(gè)例子中,調(diào)用發(fā)生在 Example1的 doTransform() 方法, 更進(jìn)一步說, 是 Transformer的 transform() 方法中。
  
  設(shè)置 ContentHandler后, Transformer 調(diào)用 Example3Reader的 parse() 方法. 這個(gè)方法實(shí)現(xiàn)了自有的解析器來解析文本格式數(shù)據(jù)。 解析的原理是XML文檔在ContentHandler 方法中構(gòu)建。在我們的簡單的例子中, 只需要下列5個(gè)回調(diào)方法:
  
  ·startDocument():XML 文檔的開始
  
  ·startElement(String, String, String, Attributes): XML 元素開始
  
  ·endElement(String, String, String): XML 元素結(jié)束
  
  ·characters(char[], int, int): XML 元素的內(nèi)容
  
  ·endDocument():XML 文檔結(jié)束
  
  如下面的調(diào)用與price元素對應(yīng):
  
  myHandler.startElement("", "<PRICE>", "<PRICE>", new AttributesImpl());// Converting String "20" to char array char[] myChArray = new char[255];"20".getChars(0, 2, myChArray, 0);// Conversion donemyHandler.characters(myChArray, 0, 2);myHandler.endElement("", "<PRICE>", "<PRICE>");
  
  對應(yīng)的 XML 元素:
  
  <PRICE>20</PRICE>
  
  總結(jié)
  
  這篇文章主要介紹怎樣使用SAX和XSLT完成復(fù)雜的數(shù)據(jù)轉(zhuǎn)換,用SAX處理XML數(shù)據(jù)進(jìn)行“預(yù)處理“,而后XSLT進(jìn)行轉(zhuǎn)換. Example 1 介紹了基本的XSLT 轉(zhuǎn)換器. Example 2 演示了怎樣操控XML數(shù)據(jù),并提供給XSLT轉(zhuǎn)換器。 Example 3 演示了怎樣從非XML數(shù)據(jù)生成XML數(shù)據(jù),進(jìn)而應(yīng)用XSLT轉(zhuǎn)換處理,Example 4 演示了怎樣使用XSLT生成非XML數(shù)據(jù)。
  
  結(jié)合 SAX 和 XSLT 是完成復(fù)雜數(shù)據(jù)轉(zhuǎn)換工作的強(qiáng)有力的工具。 它還可以動(dòng)態(tài)的改變轉(zhuǎn)換規(guī)則.另一方面,這中技術(shù)可能被濫用,因?yàn)榧词乖诓粦?yīng)該使用它的情況下的轉(zhuǎn)換引擎中,它很輕易隱藏商業(yè)邏輯, 例如,很少推薦在整合層(integration layer)包含任何商業(yè)邏輯 。
  
  當(dāng)不再使用XSLT模板包含全部的轉(zhuǎn)換邏輯,維護(hù)變得困難。 可以編寫command代碼,使轉(zhuǎn)換邏輯的數(shù)據(jù)來自屬性文件,因此避免每次轉(zhuǎn)換規(guī)則變了后需要重新編輯。盡管如此, 讓事情保持可控的最好方法還是有文檔說明,并隨時(shí)保持文檔更新。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 新兴县| 濮阳市| 岑巩县| 深泽县| 佛冈县| 合水县| 浙江省| 临朐县| 确山县| 鄂尔多斯市| 密云县| 工布江达县| 莱州市| 安康市| 漳平市| 博兴县| 阳城县| 南阳市| 甘洛县| 房产| 富锦市| 偃师市| 阳曲县| 克什克腾旗| 锡林浩特市| 丰城市| 井冈山市| 彝良县| 桂林市| 原平市| 简阳市| 瑞昌市| 五河县| 台安县| 页游| 科技| 忻州市| 葵青区| 板桥市| 中方县| 剑河县|