精彩的人生

          好好工作,好好生活

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            147 Posts :: 0 Stories :: 250 Comments :: 0 Trackbacks
          在本系列的第一篇文章中,我研究了一些用 Java 編寫的主要的 XML 文檔模型的性能。但是,在開始選擇這種類型的技術時,性能只是問題的一部分。使用方便至少是同樣重要的,并且它已是一個主要理由,來支持使用 Java 特定的模型,而不是與語言無關的 DOM 。

          為切實了解哪個模型真正的作用,您需要知道它們在可用性程度上是如何排名的。本文中,我將嘗試進行這個工作,從樣本代碼開始,來演示如何在每個模型中編碼公共類型的操作。并對結果進行總結來結束本文,而且提出了促使一種表示比另一種更容易使用的一些其它因素。

          請參閱以前的文章(請參閱參考資料或本文“內容”下的便捷鏈接)來獲取這個對比中使用的各個模型的背景資料,包含實際的版本號。還可以參閱“參考資料”一節中關于源代碼下載、到模型主頁的鏈接以及其它相關信息。

          代碼對比
          在對不同文檔表示中用法技術的這些對比中,我將顯示如何在每種模型中實現三種基本操作:

          根據輸入流構建文檔
          遍歷元素和內容,并做一些更改:
          從文本內容中除去前導和尾隨的空白。
          如果結果文本內容為空,就刪除它。
          否則,將它包裝到父元素的名稱空間中一個名為“text”的新元素中。
          將已修改的文檔寫入輸出流

          這些示例的代碼是以我在上篇文章中使用的基準程序為基礎的,并進行了一些簡化。基準程序的焦點是為了顯示每個模型的最佳性能;對于本文,我將嘗試顯示在每種模型中實現操作的最簡便方法。

          我已經將每個模型的示例結構化為兩個獨立的代碼段。第一段是讀取文檔、調用修改代碼和編寫已修改文檔的代碼。第二段是真正遍歷文檔表示和執行修改的遞歸方法。為避免分散注意力,我已在代碼中忽略了異常處理。

          您可以從本頁底部參考資料一節鏈接到下載頁,以獲取所有樣本的完整代碼。樣本的下載版本包括一個測試驅動程序,還有一些添加的代碼用于通過計算元素、刪除和添加的個數來檢查不同模型的操作。

          即使您不想使用 DOM 實現,但還是值得瀏覽下面對 DOM 用法的描述。因為 DOM 示例是第一個示例,所以與后面的模型相比,我用它來探究有關該示例的一些問題和結構的更詳細信息。瀏覽這些內容可以補充您想知道的一些細節,如果直接閱讀其它模型之一,那么將錯過這些細節。

          DOM
          DOM 規范涵蓋了文檔表示的所有類型的操作,但是它沒有涉及例如對文檔的語法分析和生成文本輸出這樣的問題。包括在性能測試中的兩種 DOM 實現,Xerces 和 Crimson,對這些操作使用不同的技術。清單 1 顯示了 Xerces 的頂級代碼的一種形式。

          清單 1. Xerces DOM 頂級代碼
          1 // parse the document from input stream ("in")
          2 DOMParser parser = new DOMParser();
          3 parser.setFeature("http://xml.org/sax/features/namespaces", true);
          4 parser.parse(new InputSource(in));
          5 Document doc = parser.getDocument();

          6 // recursively walk and modify document
          7 modifyElement(doc.getDocumentElement());

          8 // write the document to output stream ("out")
          9 OutputFormat format = new OutputFormat(doc);
          10 XMLSerializer serializer = new XMLSerializer(out, format);
          11 serializer.serialize(doc.getDocumentElement());



          正如我在注釋中指出的,清單 1 中的第一塊代碼(第 1-5 行)處理對輸入流的語法分析,以構建文檔表示。Xerces 定義了 DOMParser 類,以便從 Xerces 語法分析器的輸出構建文檔。InputSource 類是 SAX 規范的一部分,它能適應供 SAX 分析器使用的幾種輸入形式的任何之一。通過單一調用進行實際的語法分析和文檔構造,如果成功完成了這一操作,那么應用程序就可以檢索并使用已構造的 Document。

          第二個代碼塊(第 6-7 行)只是將文檔的根元素傳遞給我馬上要談到的遞歸修改方法。這些代碼與本文中所有文檔模型的代碼在本質上是相同的,所以在剩余的示例中我將跳過它,不再做任何討論。

          第三個代碼塊(第 8-11 行)處理將文檔作為文本寫入輸出流。這里,OutputFormat 類包裝文檔,并為格式化生成的文本提供了多種選項。XMLSerializer 類處理輸出文本的實際生成。

          Xerces 的 modify 方法只使用標準 DOM 接口,所以它還與任何其它 DOM 實現兼容。清單 2 顯示了代碼。

          清單 2. DOM Modify 方法
          1 protected void modifyElement(Element element) {

          2 // loop through child nodes
          3 Node child;
          4 Node next = (Node)element.getFirstChild();
          5 while ((child = next) != null) {

          6 // set next before we change anything
          7 next = child.getNextSibling();

          8 // handle child by node type
          9 if (child.getNodeType() == Node.TEXT_NODE) {

          10 // trim whitespace from content text
          11 String trimmed = child.getNodeValue().trim();
          12 if (trimmed.length() == 0) {

          13 // delete child if nothing but whitespace
          14 element.removeChild(child);

          15 } else {

          16 // create a "text" element matching parent namespace
          17 Document doc = element.getOwnerDocument();
          18 String prefix = element.getPrefix();
          19 String name = (prefix == null) ? "text" : (prefix + ":text");
          20 Element text =
          21 doc.createElementNS(element.getNamespaceURI(), name);

          22 // wrap the trimmed content with new element
          23 text.appendChild(doc.createTextNode(trimmed));
          24 element.replaceChild(text, child);

          25 }
          26 } else if (child.getNodeType() == Node.ELEMENT_NODE) {

          27 // handle child elements with recursive call
          28 modifyElement((Element)child);

          29 }
          30 }
          31 }



          清單 2 中顯示的方法所使用的基本方法與所有文檔表示的方法相同。 通過一個元素調用它,它就依次遍歷那個元素的子元素。如果找到文本內容子元素,要么刪除文本(如果它只是由空格組成的),要么通過與包含元素相同的名稱空間中名為“text”的新元素來包裝文本(如果有非空格的字符)。如果找到一個子元素,那么這個方法就使用這個子元素,遞歸地調用它本身。

          對于 DOM 實現,我使用一對引用:child 和 next 來跟蹤子元素排序列表中我所處的位置。在對當前子節點進行任何其它處理之前,先裝入下個子節點的引用(第 7 行)。這樣做使得我能夠刪除或替代當前的子節點,而不丟失我在列表中的蹤跡。

          當我創建一個新元素來包裝非空白的文本內容(第 16-24 行)時,DOM 接口開始有點雜亂。用來創建元素的方法與文檔關聯并成為一個整體,所以我需要在所有者文檔中檢索當前我正在處理的元素(第 17 行)。我想將這個新元素放置在與現有的父元素相同的名稱空間中,并且在 DOM 中,這意味著我需要構造元素的限定名稱。根據是否有名稱空間的前綴,這個操作會有所不同(第 18-19 行)。利用新元素的限定名稱,以及現有元素中的名稱空間 URI,我就能創建新元素(第 20-21 行)。

          一旦創建了新元素,我只要創建和添加文本節點來包裝內容 String,然后用新創建的元素來替代原始文本節點(第 22-24 行)。

          清單 3. Crimson DOM 頂級代碼
          1 // parse the document from input stream
          2 System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
          3 "org.apache.crimson.jaxp.DocumentBuilderFactoryImpl");
          4 DocumentBuilderFactory dbf = DocumentBuilderFactoryImpl.newInstance();
          5 dbf.setNamespaceAware(true);
          6 DocumentBuilder builder = dbf.newDocumentBuilder();
          7 Document doc = builder.parse(in);

          8 // recursively walk and modify document
          9 modifyElement(doc.getDocumentElement());

          10 // write the document to output stream
          11 ((XmlDocument)doc).write(out);



          清單 3 中的 Crimson DOM 示例代碼使用了用于語法分析的 JAXP 接口。JAXP 為語法分析和轉換 XML 文檔提供了一個標準化的接口。本示例中的語法分析代碼還可以用于 Xerces(對文檔構建器類名稱的特性設置有適當的更改)來替代較早給定的 Xerces 特定的示例代碼。

          在本示例中,我首先在第 2 行到第 3 行中設置系統特性來選擇要構造的 DOM 表示的構建器工廠類(JAXP 僅直接支持構建 DOM 表示,不支持構建本文中討論的任何其它表示)。僅當想選擇一個要由 JAXP 使用的特定 DOM 時,才需要這一步;否則,它使用缺省實現。出于完整性起見,我在代碼中包含了設置這個特性,但是更普遍的是將它設置成一個 JVM 命令行參數。

          接著我在第 4 行到第 6 行中創建構建器工廠的實例,對使用那個工廠實例構造的構建器啟用名稱空間支持,并從構建器工廠創建文檔構建器。最后(第 7 行),我使用文檔構建器來對輸入流進行語法分析并構造文檔表示。

          為了寫出文檔,我使用 Crimson 中內部定義的基本方法。不保證在 Crimson 未來版本中支持這個方法,但是使用 JAXP 轉換代碼來將文檔作為文本輸出的替代方法需要諸如 Xalan 那樣的 XSL 處理器的。那超出了本文的范圍,但是要獲取詳細信息,可以查閱 Sun 中的 JAXP 教程。

          JDOM
          使用 JDOM 的頂級代碼比使用 DOM 實現的代碼稍微簡單一點。為構建文檔表示(第 1-3 行),我使用帶有由參數值禁止驗證的 SAXBuilder。通過使用提供的 XMLOutputter 類,將已修改的文檔寫入輸出流同樣簡單(第 6-8 行)。

          清單 4. JDOM 頂級代碼
          1 // parse the document from input stream
          2 SAXBuilder builder = new SAXBuilder(false);
          3 Document doc = builder.build(in);

          4 // recursively walk and modify document
          5 modifyElement(doc.getRootElement());

          6 // write the document to output stream
          7 XMLOutputter outer = new XMLOutputter();
          8 outer.output(doc, out);



          清單 5 中 JDOM 的 modify 方法也比 DOM 的同一方法簡單。我獲取包含元素所有內容的列表并掃描了這張列表,檢查文本(象 String 對象那樣的內容)和元素。這張列表是“活的”,所以我能直接對它進行更改,而不必調用父元素上的方法。

          清單 5. JDOM modify 方法
          1 protected void modifyElement(Element element) {

          2 // loop through child nodes
          3 List children = element.getContent();
          4 for (int i = 0; i < children.size(); i++) {

          5 // handle child by node type
          6 Object child = children.get(i);
          7 if (child instanceof String) {

          8 // trim whitespace from content text
          9 String trimmed = child.toString().trim();
          10 if (trimmed.length() == 0) {

          11 // delete child if only whitespace (adjusting index)
          12 children.remove(i--);

          13 } else {

          14 // wrap the trimmed content with new element
          15 Element text = new Element("text", element.getNamespace());
          16 text.setText(trimmed);
          17 children.set(i, text);

          18 }
          19 } else if (child instanceof Element) {

          20 // handle child elements with recursive call
          21 modifyElement((Element)child);

          22 }
          23 }
          24 }



          創建新元素的技術(第 14-17 行)非常簡單,而且與 DOM 版本不同,它不需要訪問父文檔。

          dom4j
          dom4j 的頂級代碼比 JDOM 的稍微復雜些,但是它們的代碼行非常類似。這里的主要區別是我保存了用來構建 dom4j 文檔表示的 DocumentFactory(第 5 行),并在輸出已修改的文檔文本之后刷新了 writer(第 10 行)。

          清單 6. dom4j 的頂級代碼
          1 // parse the document from input stream
          2 SAXReader reader = new SAXReader(false);
          3 Document doc = reader.read(in);

          4 // recursively walk and modify document
          5 m_factory = reader.getDocumentFactory();
          6 modifyElement(doc.getRootElement());

          7 // write the document to output stream
          8 XMLWriter writer = new XMLWriter(out);
          9 writer.write(doc);
          10 writer.flush();



          正如您在清單 6 中看到的,dom4j 使用一個工廠方法來構造文檔表示(從語法分析構建)中包含的對象。根據接口來定義每個組件對象,所以實現其中一個接口的任何類型的對象都能包含在表示中(與 JDOM 相反,它使用具體類:這些類在某些情況中可以劃分子類和被繼承,但是在文檔表示中使用的任何類都需要以原始 JDOM 類為基礎)。通過使用不同工廠進行 dom4j 文檔構建,您能獲取不同系列的組件中構造的文檔。

          在樣本代碼(第 5 行)中,我檢索了用于構建文檔的(缺省)文檔工廠,并將它存儲在一個實例變量(m_factory)中以供 modify 方法使用。并不嚴格需要這一步 — 可以在一個文檔中同時使用來自不同工廠的組件,或者可以繞過工廠而直接創建組件的實例 — 但在該例中,我只想創建與在文檔其余部分中使用的同一類型的組件,并且使用相同的工廠來確保完成這個步驟。

          清單 7. dom4j modify 方法
          1 protected void modifyElement(Element element) {

          2 // loop through child nodes
          3 List children = element.content();
          4 for (int i = 0; i < children.size(); i++) {

          5 // handle child by node type
          6 Node child = (Node)children.get(i);
          7 if (child.getNodeType() == Node.TEXT_NODE) {

          8 // trim whitespace from content text
          9 String trimmed = child.getText().trim();
          10 if (trimmed.length() == 0) {

          11 // delete child if only whitespace (adjusting index)
          12 children.remove(i--);

          13 } else {

          14 // wrap the trimmed content with new element
          15 Element text = m_factory.createElement
          16 (QName.get("text", element.getNamespace()));
          17 text.addText(trimmed);
          18 children.set(i, text);

          19 }
          20 } else if (child.getNodeType() == Node.ELEMENT_NODE) {

          21 // handle child elements with recursive call
          22 modifyElement((Element)child);

          23 }
          24 }
          25 }



          清單 7 中 dom4j modify 方法與 JDOM 中使用的方法非常類似。不通過使用 instanceof 運算符來檢查內容項的類型,我可以通過 Node 接口方法 getNodeType 來獲取類型代碼(也可以使用 instanceof,但類型代碼方法看起來更清晰)。通過使用 QName 對象來表示元素名稱和通過調用已保存的工廠的方法來構建元素可以區別新元素的創建技術(第 15-16 行)。

          Electric XML
          清單 8 中 Electric XML(EXML)的頂級代碼是任何這些示例中最簡單的一個,通過單一方法調用就可以讀取和編寫文檔。

          清單 8. EXML 頂級代碼
          1 // parse the document from input stream
          2 Document doc = new Document(in);

          3 // recursively walk and modify document
          4 modifyElement(doc.getRoot());

          5 // write the document to output stream
          6 doc.write(out);



          清單 9 中 EXML modify 方法盡管與 JDOM 一樣,需要使用 instanceof 檢查,但它與 DOM 方法最相似。在 EXML 中,無法創建一個帶名稱空間限定的名稱的元素,所以取而代之,我創建新元素,然后設置其名稱來達到相同的效果。

          清單 9. EXML modify 方法
          1 protected void modifyElement(Element element) {

          2 // loop through child nodes
          3 Child child;
          4 Child next = element.getChildren().first();
          5 while ((child = next) != null) {

          6 // set next before we change anything
          7 next = child.getNextSibling();

          8 // handle child by node type
          9 if (child instanceof Text) {

          10 // trim whitespace from content text
          11 String trimmed = ((Text)child).getString().trim();
          12 if (trimmed.length() == 0) {

          13 // delete child if only whitespace
          14 child.remove();

          15 } else {

          16 // wrap the trimmed content with new element
          17 Element text = new Element();
          18 text.addText(trimmed);
          19 child.replaceWith(text);
          20 text.setName(element.getPrefix(), "text");

          21 }
          22 } else if (child instanceof Element) {

          23 // handle child elements with recursive call
          24 modifyElement((Element)child);

          25 }
          26 }
          27 }



          XPP
          XPP 的頂級代碼(在清單 10 中)是所有示例中最長的一個,與其它模型相比,它需要相當多的設置。

          清單 10. XPP 頂級代碼
          1 // parse the document from input stream
          2 m_parserFactory = XmlPullParserFactory.newInstance();
          3 m_parserFactory.setNamespaceAware(true);
          4 XmlPullParser parser = m_parserFactory.newPullParser();
          5 parser.setInput(new BufferedReader(new InputStreamReader(in)));
          6 parser.next();
          7 XmlNode doc = m_parserFactory.newNode();
          8 parser.readNode(doc);

          9 // recursively walk and modify document
          10 modifyElement(doc);

          11 // write the document to output stream
          12 XmlRecorder recorder = m_parserFactory.newRecorder();
          13 Writer writer = new OutputStreamWriter(out);
          14 recorder.setOutput(writer);
          15 recorder.writeNode(doc);
          16 writer.close();



          因為使用 JAXP 接口,所以我必須首先創建分析器工廠的實例并在創建分析器實例之前啟用名稱空間處理(第 2-4 行)。一旦獲取了分析器實例,我就能將輸入設置到分析器中,并真正構建文檔表示(第 5-8 行),但是這涉及比其它模型更多的步驟。

          輸出處理(第 11-16 行)也涉及比其它模型更多的步驟,主要因為 XPP 需要 Writer 而不是直接將 Stream 作為輸出目標接受。

          清單 11 中 XPP modify 方法盡管需要更多代碼來創建新元素(第 13-21 行),但它與 JDOM 方法最類似。名稱空間處理在這里有點麻煩。我首先必須創建元素的限定名稱(第 15-16 行),然后創建元素,最后在稍后設置名稱和名稱空間 URI(第 18-21 行)。

          清單 11. XPP modify 方法
          1 protected void modifyElement(XmlNode element) throws Exception {

          2 // loop through child nodes
          3 for (int i = 0; i < element.getChildrenCount(); i++) {

          4 // handle child by node type
          5 Object child = element.getChildAt(i);
          6 if (child instanceof String) {

          7 // trim whitespace from content text
          8 String trimmed = child.toString().trim();
          9 if (trimmed.length() == 0) {

          10 // delete child if only whitespace (adjusting index)
          11 element.removeChildAt(i--);

          12 } else {

          13 // construct qualified name for wrapper element
          15 String prefix = element.getPrefix();
          16 String name = (prefix == null) ? "text" : (prefix + ":text");

          17 // wrap the trimmed content with new element
          18 XmlNode text = m_parserFactory.newNode();
          19 text.appendChild(trimmed);
          20 element.replaceChildAt(i, text);
          21 text.modifyTag(element.getNamespaceUri(), "text", name);

          22 }
          23 } else if (child instanceof XmlNode) {

          24 // handle child elements with recursive call
          25 modifyElement((XmlNode)child);

          26 }
          27 }
          28 }



          結束語
          DOM、dom4j 和 Electric XML 都得到這些幾乎同樣易于使用的代碼樣本,其中 EXML 可能最簡單,而 dom4j 受一些小條件限制而較困難。DOM 提供了與語言無關的非常實在的好處,但是如果你只使用 Java 代碼,那么通過與 Java 特定的模型相比較,它看上去有點麻煩。我認為這表明 Java 特定的模型通常成功地實現簡化 Java 代碼中的 XML 文檔處理這個目標。

          超越基礎:真實世界可用性
          代碼樣本顯示 JDOM 和 EXML 為基本文檔操作(使用元素、屬性和文本)提供了簡單和清晰的接口。根據我的經驗,它們的方法并不能很好地完成處理整個文檔表示的編程任務。要完成這些類型的任務,DOM 和 dom4j 使用的組件方法 — 其中從屬性到名稱空間的所有文檔組件實現一些公共接口 — 工作得更好。
          相關的例子是最近我為 JDOM 和 dom4j 實現的 XML 流型(XML Streaming (XMLS) )編碼。這個代碼遍歷整個文檔并編碼每個組件。JDOM 實現比 dom4j 實現復雜得多,主要是因為 JDOM 使用一些沒有公共接口的獨特類來表示每個組件。

          因為 JDOM 缺少公共接口,所以即使處理 Document 對象的代碼與處理 Element 對象的代碼都有一些諸如子組件那樣相同類型的組件,但是它們必須有所不同。還需要特殊方法來檢索與其它類型的子組件相對的 Namespace 組件。甚至當處理被認為是內容的子組件類型時,您需要在組件類型上使用多個帶 instanceof 檢查的 if 語句,而不是使用一條更清晰更快速的 switch 語句。

          具有諷刺意味的可能是 JDOM 的最初目標之一是利用 Java Collection 類,這些類本身在很大程度上以接口為基礎。庫中接口的使用增加了許多靈活性,而這是以增加了一些復雜性為代價的,并且這對于為重用而設計的代碼來說,通常是一個很好的折衷。這可能還主要歸功于 dom4j,它達到一個成熟并且穩定的狀態,比 JDOM 要快得多。

          盡管如此,對于使用多種語言的開發人員來說,DOM 仍是一個非常好的選擇。DOM 實現廣泛應用于多種編程語言。它還是許多其它與 XML 相關的標準的基礎,所以即使您使用 Java 特定的模型,也還有一個您逐步熟悉 DOM 所需要的好機會。因為它正式獲得 W3C 推薦(與基于非標準的 Java 模型相對),所以在某些類型的項目中可能也需要它。

          就使用方便這一范疇而言,在 JDOM、dom4j 和 Electric XML 這三個主要競爭者中,dom4j 與其它兩個的區別在于它使用帶有多個繼承層的基于接口的方法。這會使得遵循 API JavaDocs 更為困難些。例如,您正在尋找的一個方法(例如 content(),在我們 dom4j 的 modify 方法示例的第 3 行中使用的)可能是 Element 擴展的 Branch 接口的一部分,而不是 Element 接口本身的一部分。盡管如此,這種基于接口的設計添加了許多靈活性(請參閱側欄超越基礎:真實世界可用性)。考慮到 dom4j 的性能、穩定性和特性設置的優點,您應把它當作多數項目中的一個有力的候選者。

          在任一 Java 特定的文檔模型之中,JDOM 可能擁有最廣泛的用戶基礎,并且它的確是使用起來最簡單的模型之一。盡管如此,作為項目開發的一個選擇,它還是必須容忍 API 的不固定性和從一個版本到下一個版本的更新,在性能對比中它也表現得很糟糕。基于當前實現,我愿為著手新項目的人們推薦 dom4j,而不是 JDOM。

          除了 XPP 以外,EXML 比其它任何模型占用的資源都要少得多,并且考慮到 EXML 易于使用的優點,您應肯定會認為它適用于 jar 文件大小很重要的應用程序。但是,EXML 的 XML 支持的局限性和受限的許可證,以及在較大文件上所表現出的相對拙劣的性能,不得不在許多應用程序中放棄使用它。

          XPP 在語法分析和編寫文本文檔時需要更多步驟,并且在處理名稱空間時也需要更多步驟。如果 XPP 打算添加一些便利的方法來處理其中一些常見情況,那么在對比中它可能會更勝一籌。正如它現在所表現的,上篇文章中性能方面的領先者卻成了本文中的可用性方面的失敗者。盡管如此,因為 XPP 性能方面的優勢,所以對于需要較小的 jar 文件大小的應用程序還是值得將它作為 EXML 的替代方法。

          下一次...
          到目前為止在我寫的兩篇文章中,涉及到用 Java 編寫的 XML 文檔模型的性能和可用性。在本系列的后兩篇文章中,我將討論用 Java 技術進行 XML 數據綁定的方法。這些方法與文檔模型的方法有許多相似處,但是它們更進一步將 XML 文檔映射到實際應用程序數據結構中。我們將看到這一操作在使用的簡便性和提高性能方面是如何做得如此好的。

          回到 developerWorks,檢查 Java 代碼的 XML 數據綁定的實質。同時,您可以通過下面鏈接的論壇,給出您對本文的評論和問題。

          參考資料

          單擊本文頂部或底部的討論,參與本文的論壇。
          如果您需要背景資料,嘗試 developerWorks 的 XML Java 編程 教程、理解 SAX 教程和 理解 DOM 教程。
          從下載頁面下載本文中使用的測試程序和文檔模型庫。
          查找有關 Java APIs for XML Processing (JAXP) 或者閱讀 JAXP Tutorial。
          獲取作者有關 XML Streaming 的著作的詳細信息,它作為程序間傳送 XML 文檔的 Java 序列化的另一種選擇。
          回顧作者以前的文章:XML in Java: Document models, part 1。
          根據 Tony Darugar 的團隊對幾個大型 XML 項目的分析,參考他對 Effective DOM with Java 的建議。
          Java 的 XML 文檔模型:
          Xerces Java
          Crimson
          JDOM
          dom4j
          Electric XML
          XML Pull Parser (XPP)



          原文:http://www.daima.com.cn/Info/127/Info37907/
          posted on 2006-04-03 10:30 hopeshared 閱讀(548) 評論(0)  編輯  收藏 所屬分類: Java
          主站蜘蛛池模板: 宜兰市| 霍山县| 万宁市| 延川县| 平度市| 甘孜| 富平县| 永济市| 宜昌市| 沁源县| 方山县| 海兴县| 永仁县| 雅安市| 衡南县| 获嘉县| 石城县| 车致| 郧西县| 古交市| 治多县| 扬中市| 黄大仙区| 黔西| 南安市| 砚山县| 交城县| 砀山县| 城口县| 堆龙德庆县| 湘阴县| 冕宁县| 中卫市| 额敏县| 香港| 唐山市| 开化县| 婺源县| 凉城县| 大兴区| 三亚市|