Vincent.Chan‘s Blog

          導航

          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          公告

          本博客僅為收集留待閱讀。在此對原作者表示感謝。
          ---------------------------

          留言簿(11)

          隨筆分類

          文章分類

          隨筆檔案

          文章檔案

          相冊

          閱讀排行榜

          評論排行榜

          常用鏈接

          統計

          積分與排名

          網站

          最新評論

          使用 XML: 處理指令和參數::添加多個樣式表支持

          級別: 初級

          Benoit Marchal, 顧問, Pineapplesoft

          2001 年 9 月 01 日

          這個月,我們不辭辛勞的專欄作家將多個樣式表的支持添加到 XM 內容管理項目中。 在這樣做時,他涉及到了 TrAX URIResolver 并編寫偽屬性的解析器。如往常一樣,可在 developerWorks 開放源碼專區獲得完整的源代碼。

          在“使用 XML”專欄文章中,Beno?t Marchal 每個月都報告其關于一個或多個開放源碼 XML 開發項目的進展。 您可以隨著他的進展,遵循他的設計決策和編碼選擇,也可以提出一些建議以及在您自己的項目中重用該開放源代碼。

          繼續有 關 XM 的工作。這個月,我已經添加了多個樣式表支持,因此可以解決讀者發來的最常見建議。 我還添加了一個將一些參數傳遞到樣式表的選項,它使 XM 基本的發布功能更完美。 在開始繼續開發更高級的功能期間,我已經包括了一個目錄閱讀器。(您可以下載有關這篇文章以及以前專欄文章的所有相關代碼; 請參閱副欄 獲取代碼。)

          多個樣式表

          XM 的頭兩個版本沿用“用一種模式來套用所有”策略,最初看上去似乎是一個好主意,但經過證實它是無效的。 更明確地講,到現在為止,XM 只識別一種樣式表 rules.xsl 。正如本系列的第一篇文章中說明的那樣, 我原先認為我本可以使用不同的 XSLT 模板來選擇樣式的變更:

          												<xsl:template match="db:article">
          <!-- rules for an article here -->
          </xsl:template>
          <xsl:template match="xm:Directory">
          <!-- rules for a directory here -->
          </xsl:template>

          然 而,我自己在 ananas.org(我完全用 XM 維護的網站)上的經歷說明了我最初的計劃不能很好地工作。 我還收到一些讀者的建議,建議我解決那些他們察覺是缺陷的問題。最后, 當我開始著手內容生成(稍后將在本文中介紹)時,更覺得有必要添加多個樣式表的支持了。

          獲取代碼

          跟往常一樣, 可以從 CVS 資源庫(請參閱 參考資料)下載該專欄文章的代碼??梢詮耐粋€地方下載 ZIP 文件。 這個月,下載文件包括樣本 .xml 和 .xsl 文件。

          處理指令

          您可能記起,易用性是我優先使用 XM 的原因之一, 明確地講,我不希望使用配置文件或“構建腳本”來選擇哪個樣式表適用于哪個地方(有關這一需求的完整討論, 請閱讀 使用 XML:將 XSLT 用于內容管理)。

          讀者建議用巧妙的命名約定來選擇樣式表,但對于解決方案的最好提議來自一位同事, 他提醒我使用 xml-stylesheet 處理指令。

          如果您不熟悉 xml-stylesheet ,則可以看于 1999 年 7 月發表的小的 W3C 建議書,其中介紹了它,xml-stylesheet 是通過 Internet Explorer 5.0 而得到普及。處理指令將樣式表(XSL 或 CSS)與 XML 文檔相關聯。 例如:

          												<?xml version="1.0"?>




          <?xml-stylesheet href="classic.xsl" type="text/xml"?>
          <?xml-stylesheet href="funky.xsl" type="text/xml" alternate="yes"?>



          <article>
          <articleinfo>
          <title>ananas.org</title>
          <!-- rest of the document goes here -->


          一般而言,處理指令對應用程序特定的數據進行編碼。您已經熟悉了處理指令,因為大多數 XML 文檔都是以 XML 聲明開始, 該聲明本身就是特殊的處理指令。一條處理指令包含一個目標(在上面示例中, xml-stylesheet ),后跟數據。 用 <??> 定界符將處理指令包起來。目標確定應用程序, 而對應用程序不能識別的目標,其會忽略這些處理指令。

          數據格式完全是自由的。XML 不指定將什么東西放進去(當然,除了 XML 聲明)。 事實上,由于歷史原因,處理指令可以包含 PostScript 圖像或腳本等……但決不包含標記。

          象聲明一樣, xml-stylesheet 有特殊地位,因為它是由 W3C 定義的。 它必須出現在文檔的開始(即,在第一個元素之前),并且包含幾個所謂的 偽屬性。這個數據之所以稱為偽屬性, 是因為其語法與 XML 屬性相似。

          最重要的偽屬性是 href ,它包含指向樣式表的 URI。其它有用的偽屬性有 typealternate 。 type 是樣式表的 MIME 類型, 它用來區別 CSS 和 XSL。如果有多個 xml-stylesheet 指令, 則 alternate 表明哪一個替代主樣式表。處理器應該用備用樣式表列表提示用戶。 然而,因為 XM 以批處理方式工作,所以它使用不同的策略,完全忽略備用樣式表。

          雖然 xml-stylesheet 是一種 W3C 標準,但 TrAX 處理器忽略它,除非另行告知。 應用程序必須明確地調用 getAssociatedStylesheet() 來檢索處理指令,如下:

          												Source document = new StreamSource(file),
          stylesheet = factory.getAssociatedStylesheet(document,null,null,null);

          if(null != stylesheet)
          transformer.transform(document,new StreamResult(System.out));
          else
          throw new XMException("Cannot find the style sheet");

          然而, getAssociatedStylesheet() 帶來 XM 必須避免的兩個問題。首先, getAssociatedStylesheet() 使高速緩存常用樣式表變得困難。其次,它假設樣式表存儲在與文檔相同的目錄中。 我喜歡將樣式表存儲在不同的目錄中,因為我發現,如果樣式表都被分組在一個目錄中,可以易于維護和共享樣式表。

          還傳遞樣式表參數

          選擇樣式表僅僅完成了解決方案的一半。 通常,我希望做一些小小的變動,而不必編寫新的樣式表。對于這種情況,我喜愛的解決方案是使用參數, 如清單 1 所示:


          清單 1:樣本參數
          												<xsl:stylesheet ...>

          <xsl:param name="sponsor" select="'none'"/>

          <xsl:template match="articleinfo">




          <xsl:if test="$sponsor='dw'">


          <center>
          <a >
          <img align="middle" width="136" height="24" border="0"
          alt="developerWorks" src="!images/buttons/dw.gif"/>
          </a>
          </center>
          </xsl:if>
          <xsl:apply-templates/>
          </xsl:template>



          還有,如何傳遞這些參數?W3C 沒有提議一種機制,所以定義新的處理指令似乎也就不足為奇了。XM 可以識別 xm-xsl-paramxml-stylesheet 。 xm-xsl-param 的語法與其它處理指令相似,并且使用兩個偽屬性 namevalue

          												<?xml version="1.0"?>




          <?xm-xsl-param name="sponsor" value="dw"?>


          <article>
          <articleinfo>
          <title>XM</title>



          顯而易見,TrAX 不支持 xm-xsl-param ,但因為我已經確定 XM 需要替換 getAssociatedStylesheet() ,所以解析 xm-xsl-param 的工作不是很多。

          但這不是意味著要對文檔解析兩次嗎?一次用于處理指令,另一次是使用 XSLT 處理器。實際上, 解析兩次不會引起更多麻煩,因為處理指令必須出現在文檔開始,所以 XM 只是重新解析文檔的一小部分。

          ProcessingInstructionHandler 和 PseudoAttributeTokenizer

          ProcessingInstructionHandler 是一種 SAX ContentHandler ,它抽取 xml-stylesheetxm-xsl-param

          處理程序截取 4 個事件。 setDocumentLocator()startDocument() 用于初始化。 大多數工作都發生在 processingInstruction() 中。至于 startElement() , 它用來停止解析,因為它標記這個開始的結束。要停止解析, startElement() 拋出一個異常。 這種作法近乎黑客所使用的手段,這是有爭議的;異常一般用于報告錯誤,然而 startElement() 中沒有錯誤,但 SAX 沒有提供更“光明正大”的解決方案來停止解析。

          雖然偽屬性的語法與 XML 屬性相類似,但 SAX 解析器不對它們進行譯碼。XM 使用它自己的解析器 PseudoAttributeTokenizer 來對偽屬性進行譯碼。

          PseudoAttributeTokenizer 每次掃描緩沖區一個字符,查找偽屬性。 它使用一種典型的算法,這種算法可以在每本編譯器書籍中找到。 如果您不熟悉方面的內容,那么我推薦您閱讀以 Pascal 聞名的 Niklaus Wirth 的 Compiler Construction(請參閱 參考資料)。

          要簡化該代碼, getc() 方法返回緩沖區中的下一個字符,而 putc() 替換緩沖區中 getc() 下一次調用的字符。

          PseudoAttributeTokenizer 的公用接口由三個方法組成: hasMoreTokens() 測試緩沖區中是否還有偽屬性, nextName() 返回下一個名稱, nextValue() 返回下一個值。

          讓我們研究一下 nextName() 。它通過調用 eatSpaces() 來除去前導空格。接下來, 只要它發現有數字或字母,就一直循環下去,并將字符累積在變量( token )中。因為名稱只包含數字和字母, 所以任何其它字符都可以表示這個循環的結束。 nextName() 特別關注讀入緩沖區的、返回的最后一個字符, 其中,它將用于 nextValue() 。


          清單 2:nextName() 示例
          												public String nextName()
          throws SAXParseException
          {
          token.setLength(0);
          int c = eatSpaces();
          for(;;)
          if(c == -1)
          throw new SAXParseException(UNEXPECTED_EOS,locator);
          // strictly speaking a name cannot start with a digit...
          else if(!Character.isLetterOrDigit((char)c) && c != '-')
          {
          putc(); // put it back for the next call
          return token.length() == 0 ? null : token.toString();
          }
          else
          {
          token.append((char)c);
          c = getc();
          }
          }

          nextValue()nextName() 相似,但它先識別等號字符(由 nextName() 將它留在緩沖區中)和引號字符。 nextValue() 還譯碼預先定義的實體( < 、 > 以及類似的)。

          有了 tokenizer,就很容易譯碼處理指令。以下代碼摘自 ProcessingInstructionHandler ,它用來識別 xml-stylesheetxm-xsl-param 的代碼與這類似:


          清單 3:ProcessingInstructionHandler 摘錄
          												if(target.equals("xml-stylesheet"))
          {
          String href = null,
          type = null;
          boolean alternate = false;
          PseudoAttributeTokenizer tokenizer =
          new PseudoAttributeTokenizer(data,locator);
          while(tokenizer.hasMoreTokens())
          {
          String name = tokenizer.nextName(),
          value = tokenizer.nextValue();
          if(name.equals("href"))
          href = value;
          else if(name.equals("alternate"))
          alternate = value.equals("yes");
          else if(name.equals("type"))
          type = value.trim();
          // ignore the media attribute...
          }
          if(type != null && href != null && !alternate &&
          (type.equals("text/xsl") || type.equals("text/xml") ||
          type.equals("application/xml+xslt")))
          {
          this.href = href;
          params.clear();
          readParams = true;
          }
          else
          readParams = false;
          }

          請記住,XM 會忽略備用樣式表。W3C 建議書考慮到 HTTP,所以提供了優先于備用樣式表的缺省樣式表。XM 使用與這相同的規則, 應用它自己的缺省樣式表,而不考慮備用樣式表。

          TemplatesManager

          由于 XM 可使用多個樣式表, 所以對高速緩存的邏輯進行了改進。這是由 TemplatesManager 來負責。當 StylingMover 請求 Templates 對象時,從高速緩存(如果該對象在其中)檢索該對象。如果該對象不在其中, 則 TemplatesManager 裝入樣式表并將其放入高速緩存。本質上, TemplatesManager 是包含 java.util.Map 的封裝器,并包含用于返回 Transformer 對象的附加方法。

          正如前面所解釋的那樣,XM 不會將文檔和樣式表混在一起。它使用兩個目錄:文檔目錄和規則目錄。TrAX 提供 URIResolver 接口以控制 XSLT 處理器如何裝入文件。XSLT 處理器的 URIResolver 與 SAX 解析器的 EntityResolver 相似; 當該處理器裝入已導入的樣式表(通過 xsl:importxsl:include 元素)或文檔(通過 document() 函數)時, 該處理器調用它的 resolve() 方法。

          TemplatesManager 使用內部類 ReferenceResolver ,該類從規則目錄裝入樣式表:


          清單 4:ReferenceResolver 示例
          												protected class ReferenceResolver
          implements URIResolver
          {
          protected File rulesDir;

          public ReferenceResolver(File rulesDir)
          {
          this.rulesDir = rulesDir;
          }

          public Source resolve(String href,String base)
          {
          if(href.endsWith(".xsl"))
          {
          File file = new File(rulesDir,href);
          if(file.exists())
          return new StreamSource(file);
          }
          return null;
          }
          }

          StylingMover

          當然,我已經把 StylingMover 改寫成新類。它現在用 ProcessingInstructionHandler 處理程序來解析文檔。 它使用處理結果來選擇樣式表并指定參數,如 清單 5 所示。 特別要注意 try/catch 語句;因為 startElement() 使用特殊異常來停止解析,所以代碼必須識別那不是一個錯誤。





          回頁首


          自動生成內容

          到目前為止, 有關 XM 的工作已經涉及了基本發布特性。雖然它們很重要,但我相信 XM 的真正價值體現在從一開始我就縈繞在腦際的自動內容生成。 簡而言之,這個想法是讓 XM 為您生成 XML 文檔。

          例如,許多網站都包含下載區。如果經常更改文件列表, 則要維護一個帶總是最新列表的 XML 文檔是困難的。最好使用一種軟件來自動生成列表。 該文檔可能類似于清單 6。同樣可以從 SQL 數據庫、郵箱或者甚至遠程網站生成文檔!


          清單 6:由 XM 讀取的目錄
          												<?xml version="1.0" encoding="UTF-8"?>
          <xm:Directory xmlns:xm="http://www.ananas.org/2001/XM/Walk/Directory">
          <xm:File isDirectory="false" isFile="true" isHidden="false" canRead="true"
          isMarked="false" lastModified="2001-07-07T18:21:10" canWrite="true"
          length="749">NotImplementedException.java</xm:File>
          <xm:File isDirectory="false" isFile="true" isHidden="false" canRead="true"
          isMarked="false" lastModified="2001-07-20T11:49:42" canWrite="true"
          length="6229">ContentHandlerExtractor.java</xm:File>
          <xm:File isDirectory="false" isFile="true" isHidden="false" canRead="true"
          isMarked="false" lastModified="2001-09-05T07:10:10" canWrite="true"
          length="2351">JAXPHelper.java</xm:File>
          </xm:Directory>

          上個月的專欄文章中介紹了 Mover ,其用于簡化添加自動內容生成的過程。 這個月,我已經在代碼中預先包含了目錄生成,并打算下個月再講述它。同時, 如果您對此感興趣,可回顧一下 DirectoryReader 、 WalkHandlerWalkMover 。





          回頁首


          輪到您了

          目前,我正在用 XM 維護兩個網站:ananas.org 和一個內部網。從使用 XM 而得到的實際經驗對于確定如何更改軟件十分有用。 歡迎您的加入,希望您下載 XM 副本,嘗試它,來構建您自己的網站。 請在 ananas-discussion 郵件列表報告您的發現(請參閱 參考資料)。

          我已經將 ananas.org 網站的代碼(.xml 文檔和 .xsl 樣式表)添加到 CVS 資源庫中, 您可以從那出發來設計您自己的網站。

          如果安裝了早期版本的 XM,則需要更新軟件以利用這個月的改進:將 rules.xsl 文件重命名為 default.xsl ,并將它移到 rules 目錄。 這與用于選擇樣式表的新標準匹配。





          回頁首


          參考資料

          • 您可以參閱本文在 developerWorks 全球站點上的 英文原文.

          • 參與本文的 論壇

          • 可以從 ananas.org下載該項目的代碼。在那里有到 developerWorks 上的 CVS 資源庫以及 ananas-discussion 郵件列表的鏈接。 我希望您加入列表,并就該項目,提出您的想法。

          • 如果想要 ZIP 文件,也可以獲得它。

          • XM 將 XalanXerces-J分別用作 XSLT 處理器和 XML 解析器。最初,IBM(和Lotus)開發了這兩個工具,后來將代碼贈予 Apache Foundation。

          • XML Extender for DB2與 DirectoryReader 類似,但它用于 DB2 數據庫。它允許您將數據庫作為 XML 文檔訪問……可以用 XSLT 來轉換 XML 文檔。

          • Niklaus Wirth 的 Compiler Construction(ISBN 0-2014-0353-6)是對解析的最好介紹之一。共 180 頁,可以很快讀完。

          • 在 developerWorks XML 專區中查找更多的 XML 參考資料。




          回頁首


          關于作者

          Author photo

          Beno?t Marchal 是比利時納慕爾的顧問和作家。他是 XML by ExampleApplied XML SolutionsXML and the Enterprise的作者。他是 Gamelan 的專欄作家。有關他最新項目的詳細信息,可在 marchal.com上找到。可以通過 bmarchal@pineapplesoft.com與 Beno?t 聯系。

          posted on 2006-03-21 23:45 Vincent.Chen 閱讀(496) 評論(0)  編輯  收藏 所屬分類: XML

          主站蜘蛛池模板: 宝丰县| 闽侯县| 锡林浩特市| 鄱阳县| 洛浦县| 阿克陶县| 文水县| 舒兰市| 永丰县| 龙海市| 平武县| 广河县| 邵东县| 三原县| 县级市| 成安县| 应城市| 东港市| 娄烦县| 五寨县| 安宁市| 公安县| 南和县| 井陉县| 英超| 富顺县| 五常市| 神农架林区| 游戏| 邛崃市| 苏尼特左旗| 黄骅市| 天气| 南乐县| 开鲁县| 望谟县| 五大连池市| 新绛县| 明水县| 汶上县| 电白县|