Java XML教程(第5章)

來源:互聯網
上載者:User
xml|教程 來源:http://d23xapp2.cn.ibm.com/developerWorks/education/xml/xmljava/tutorial/xmljava-1-1.html

第五章 解析器進階功能


概覽

我們已經討論了使用一個 XML 解析器來處理 XML 文檔的基礎。在本章節,我們將探討一些進階概念。

首先,我們將從頭構建一棵 DOM 樹。換而言之,我們將不需要一個 XML 摘要檔案來建立一個 Document 對象。

然後,我們將向您顯示如何使用解析器來處理包含在一個字串的 XML 文檔。

接著,我們將向您顯示如何操作一棵 DOM 樹。我們將對我們樣本的 XML 文檔操作並對其詩句排序。

最後,我們將展示如何利用如 DOM 和 SAX 標準的介面使得解析器的更改十分容易。我們將向您展示兩個使用不同 XML 解析器的樣本應用。而其中 DOM 和 SAX 代碼沒有改變。

從頭構建一棵 DOM 樹

有時您想要從頭構建一棵 DOM 樹。要完成這個任務,您建立一個 Document 對象,然後對其添加不同的 Node 對象。

您可運行 java domBuilder 來看一個從頭構建一棵 DOM 樹的樣本應用。該應用重新建立了通過對 sonnet.xml 最初解析而構建出的 DOM 樹(但不再建立空格符)。

我們首先建立一個 DocumentImpl 類的執行個體。此類實現 DOM 定義的 Document 介面。
Document doc = (Document)Class.
forName("com.ibm.xml.dom.DocumentImpl").
newInstance();


domBuilder.java (代碼請參考附錄2)

這段代碼不使用一個 XML 文檔來構建一個 DOM 樹。當樹被構建完後,該代碼將樹的內容輸出到標準輸出。

...
<address>
<name>
<title>Mrs.</title>
<first-name>Mary</first-name>
<last-name>McGoon</last-name>
</name>
<street>1401 Main Street</street>
<city>Anytown</city>
<state>NC</state>
<zip>34829</zip>
</address>
<address>
<name>
...

添加 Node 到我們的 Document

現在我們有自己的 Document 對象了,我們開始建立 Node。我們第一個要建立的 Node 是一個 <sonnet> 元素。我們將建立所有的 Node,然後將每個節點添加到其對應的父母。

注意我們使用 setAttribute 方法來對<sonnet>元素設定其 type 屬性的值。
Element root = doc.
createElement("sonnet");
root.setAttribute("type",
h"Shakespearean");
構建您的文檔結構

當我們開始構建我們的 DOM 樹時,我們將需要構建我們文檔的結構。要完成它,我們將需要恰當地使用 appendChild 方法。我們將建立 <author> 元素,然後建立在其下的其它元素,接著使用 appendChild 方法將所有這些元素添加到正確的父母。

注意 createElement 是屬於 Document 類的一個方法。我們的 Document 對象擁有我們在此建立的所有元素。

最終,注意到我們為所有的元素內容建立 Text 節點。Text 節點是元素的子女,而 Text 節點的父母則被添加到對應的父母下。

Element author =
doc.createElement("author");

Element lastName = doc.
createElement("last-name");
lastName.appendChild(doc.
createTextNode("Shakespeare"));
author.appendChild(lastName);

完成我們的 DOM 樹

一旦我們對 <sonnet> 元素添加了所有內容,我們需要將它添加到 Document 對象。我們最後一次調用 appendChild 方法,這次是將子項目添加到 Document 對象上。

記住一個 XML 文檔只能有一個根(root)元素;如果您要向Document添加多個根項目appendChild 將拋出一個異常。

當我們構建好 DOM 樹後,我們構建一個 domBuilder 對象,然後調用它的 printDOMTree 方法來列印 DOM 樹。
Element line14 = doc.
createElement("line");
line14.appendChild(doc.
createTextNode("As any ..."));
text.appendChild(line14);
root.appendChild(text);

doc.appendChild(root);

domBuilder db = new domBuilder();
db.printDOMTree(doc);

使用 DOM 對象來避免解析

您可以想象一個 DOM Document 對象作為一個 XML 文檔的編譯形式。如果您正使用 XML 來在不同方之間傳遞資料,您將可以通過接受和發送 DOM 對象而不是 XML 摘要資料來節約時間。

這是最常見的原因您為何需要從頭來構建一個 DOM 樹。

最壞情況下,您需要在您發送資料前從一棵 DOM 樹建立出 XML 摘要資料,然後在您接受 XML 資料時建立出一棵 DOM 樹。直接使用 DOM 對象將節約大量時間。

一個警告:要注意一個 DOM 對象可能比 XML 摘要資料要大很多。如果您要在一個十分緩慢的連接線路上傳遞資料,發送較小的 XML 摘要資料而重新解析資料要比傳遞大資料更有效。

解析一個 XML 字串

很有可能您需要解析一個 XML 字串。IBM 的 XML4J 解析器支援這個功能,儘管您需要將您的字串轉換成一個 InputSource 對象。

第一步是從您的字串中建立一個 StringReader 對象。一旦完成此步,您可以從 StringReader 建立一個 InputSource 對象。

您可運行 java parseString 來查看代碼的運行結果。在樣本應用中,XML 字串是寫死的(hardcoded);有許多種方法讓您從一個使用者或其它機器獲得 XML 輸入。有了這個技術,您就不再需要將 XML 文檔輸出到一個檔案系統來解析它了。

parseString ps = new parseString();
StringReader sr =
new StringReader("<?xml version=\"1.0\"?>
<a>AlphaBravo...");
InputSource iSrc = new InputSource(sr);
ps.parseAndPrint(iSrc);

parseString.java (參附錄2)

這段代碼展示了如何解析一個包含 XML 文檔的字串。



排列在一棵 DOM 樹中的 Node

為了介紹您如何改變一棵 DOM 樹的結構,我們將修改我們的 DOM 樣本來排列十四行詩的 <line> 元素。有多種 DOM 方法可以用來在 DOM 樹中搬移節點。

要查看代碼的結果,運行 java domSorter sonnet.xml。它並不會改進詩的韻律,但它真確地排列了 <line> 元素。

要開始排列工作,我們將使用 getElementsByTagName 方法來提取在文檔中的所有 <line> 元素。該方法節約了我們編寫代碼來遍曆整個樹的開銷。

if (doc != null)
{
sortLines(doc);
printDOMTree(doc);
}
...
public void sortLines(Document doc)
{
NodeList theLines =
doc.getDocumentElement().
getElementsByTagName("line");
...

domSorter.java (參附錄2)

這段代碼在 XML 文檔中尋找所有的 <line> 元素,然後排序。它展示了如何操作一個 DOM 樹。



提取我們的 <line> 文本

為了簡化代碼,我們建立一個 helper 功能,getTextFromLine,用來提取包含在一對 <line> 元素中的文本。它簡單地找到 <line> 元素的第一個子女,如果其是一個 Text 節點就返回其文本。

該方法返回一個 Java String 因此我們的排序過程可以使用 String.compareTo 方法來決定排序的次序。

該段代碼實際上應該檢查 <line> 所有的子女,因為它們可能包含實體(entity)引用 (例如實體 &miss; 可能替代了文本 "mistress")。我們將把這個改進作為讀者的一個練習。

public String getTextFromLine(Node
lineElement)
{
StringBuffer returnString =
new StringBuffer();
if (lineElement.getNodeName().
equals("line"))
{
NodeList kids = lineElement.
getChildNodes();
if (kids != null)
if (kids.item(0).getNodeType() ==
Node.TEXT_NODE)
returnString.append(kids.item(0).
getNodeValue());
}
else
returnString.setLength(0);

return new String(returnString);
}

文本排序

現在我們有能力從一個給定的 <line> 元素擷取文本,我們可以開始排列資料了。由於我們只有 14 個元素,我們將使用冒泡排序。

冒泡排序演算法是比較兩個相鄰資料值,然後如果它們次序不對就交換它們。要完成交換,我們使用 getParentNode 和 insertBefore 方法。

getParentNode 返回任意 Node 的父母;我們使用這個方法來獲得當前 <line> 的父母 (文檔的一個 <lines> 元素使用 sonnet DTD)。

insertBefore(nodeA, nodeB) 在 nodeB 前插入 nodeA 到 DOM 樹中。insertBefore 最重要的特性是如果 nodeA 已經存在於 DOM 樹中了,它在插入 nodeB 前將刪除該節點。
public void sortLines(Document doc)
{
NodeList theLines =
doc.getDocumentElement().
getElementsByTagName("line");
if (theLines != null)
{
int len = theLines.getLength();
for (int i=0; i < len; i++)
for (int j=0; j < (len-1-i); j++)
if (getTextFromLine(
theLines.item(j)).
compareTo(getTextFromLine(
theLines.item(j+1))) > 0)
theLines.item(j).
getParentNode().insertBefore(
theLines.item(j+1),
theLines.item(j));
}
}

用來操作樹的有用的 DOM 方法

除insertBefore 之外,還有其它的 DOM 方法可用來操作樹。

parentNode.appendChild(newChild)
將一個節點作為給定父母節點的最後子女添加。調用 parentNode.insertBefore(newChild, null) 完成同樣功能。
parentNode.replaceChild(newChild, oldChild)
將 oldChild 用 newChild 來取代。節點 oldChild 必須是 parentNode 的子女。
parentNode.removeChild(oldChild)
將 oldChild 從 parentNode 下刪除。

parentNode.appendChild(newChild);
...
parentNode.insertBefore(newChild,
oldChild);
...
parentNode.replaceChild(newChild,
oldChild);
...
parentNode.removeChild(oldChild);
...

關於樹操作還需注意的事項

如果您需要刪除一個給定節點的所有子女,這要比看上去難多了。這兩段在左邊的範例程式碼看起來可以完成任務。但是,在第二個才能完成。第一個範例程式碼由於 kid 的執行個體資料在 removeChild(kid) 被調用後就改變了而無法完成任務。

換而言之,for 迴圈刪除了 kid,第一個子女,然後檢查 kid.getNextSibling 是否為 null 。由於 kid 剛被刪除,它不再有任何同胞了,因此 kid.getNextSibling 是 null。 for 迴圈只會運行一次。不論 node 有一個或幾千個子女,第一段範例程式碼只是刪除第一個子女。要使用第二段範例程式碼來刪除所有的子節點。
/** Doesn't work **/
for (Node kid = node.getFirstChild();
kid != null;
kid = kid.getNextSibling())
node.removeChild(kid);

/** Does work **/
while (node.hasChildNodes())
node.removeChild(node.getFirstChild());


使用另一個DOM 解析器

儘管我們想象不出一個您為何要改換解析器的理由,您可以使用非 XML4J 的解析器來解析您的 XML 文檔。如果您查看t domTwo.java 的代碼,您將看到更換到 Sun 的 XML 解析器只需要兩個修改。

首先,我們必須載入(import) Sun 公司的類。這很簡單。我們要修改的只是建立 Parser 對象的代碼。如您所見,Sun 的解析器構建過程比較複雜,但餘下的代碼不用修改了。所有的 DOM 代碼不需要任何的修改。

最終,在 domTwo 的不同之處是命令列格式。出於某些原因,Sun 的解析器不能用常用的方法來解析檔案名稱。如果您運行 java domTwo file:///d:/sonnet.xml (當然根據您的系統修改 file URI),您將獲得 domOne 的相同結果。

import com.sun.xml.parser.Parser;
import
com.sun.xml.tree.XmlDocumentBuilder;

...

XmlDocumentBuilder builder =
new XmlDocumentBuilder();
Parser parser =
new com.sun.xml.parser.Parser();
parser.setDocumentHandler(builder);
builder.setParser(parser);
parser.parse(uri);
doc = builder.getDocument();


domTwo.java (參見附錄2)

這段代碼等同於 domOne.java,但它使用 Sun 公司的 XML 解析器而不是 IBM 的。它展示了 DOM 介面的可移植性。



使用一個不同的 SAX 解析器

我們也編寫了 saxTwo.java 來展示如何使用 Sun 公司的 SAX 解析器。與 domTwo 類似,我們有兩個修改之處。第一個是載入(import) Sun 的 Resolver 類而不是 IBM 的 SAXParser 類。

我們需要修改建立 Parser 對象的代碼,然後我們需要根據我們輸入的 URI 建立一個 InputSource 對象。我們要修改的只是建立 parser 的代碼需要被包含在 try 程式碼片段中以捕獲當我們建立 Parser 對象時可能產生的異常。

import com.sun.xml.parser.Resolver;
...

try
{
Parser parser =
ParserFactory.makeParser();
parser.setDocumentHandler(this);
parser.setErrorHandler(this);
parser.parse(Resolver.
createInputSource(new File(uri)));
}

saxTwo.java (見附錄2)

這段代碼等同於 saxOne.java,但它使用 Sun 公司的 XML 解析器而不是 IBM 的。它展示了 SAX 介面的可移植性。


總結

在本章節,我們介紹了一些使用 XML 解析器的進階編程技巧。我們展現了如何直接產生 DOM 樹,如何解析字串而不是檔案,如何在一棵 XML 樹中移動元素以及如何改變解析器而不用影響 DOM 和 SAX 的代碼。

希望您喜歡本教程!

這就是本教程的所有內容了。 我們討論了 XML 應用的基本架構,而且我們也介紹了您如何處理 XML 文檔。以後的教程將介紹構建 XML 應用更多的細節,包括:

使用可視工具來構建 XML 應用
將一個 XML 文檔從一種形式轉換到另一種
為終端使用者或其他進程建立介面,及對後端儲存資料的介面
要獲得更多資訊

如果您想瞭解更多的 XML 知識,可訪問 developerWorks 的 XML 專區。這個網站有範例程式碼、其它的教程、關於 XML 標準的資訊以及其它內容。

最後,我們很願意聽取您的意見!我們設計 developerWorks 是成為開發人員的資源。如果您有任何評價、建議或抱怨,請讓我們知道。

謝謝,---Doug Tidwell 或 developerWorks 中國網站!



相關文章

Beyond APAC's No.1 Cloud

19.6% IaaS Market Share in Asia Pacific - Gartner IT Service report, 2018

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。