相關閱讀
1. 操作XML文檔概述
1.1 如何操作XML文檔
XML文檔也是數據的一種,對數據的操作也不外乎是“增刪改查”。也被大家稱之爲“CRUD”
- C:Create
- R:Retrieve
- U:Update
- D:Delete
1.2 XML解析技術
XML解析方式分爲兩種:DOM(Document Object Model)和SAX(Simple API for XML)。這兩種方式不是針對Java語言來解析XML的技術,而是跨語言的解析方式。例如DOM還在Javascript中存在!
DOM是W3C組織提供的解析XML文檔的標準接口,而SAX是社區討論的產物,是一種事實上的標準。
DOM和SAX只是定義了一些接口,以及某些接口的缺省實現,而這個缺省實現只是用空方法來實現接口。一個應用程序如果需要DOM或SAX來訪問XML文檔,還需要一個實現了DOM或SAX的解析器,也就是說這個解析器需要實現DOM或SAX中定義的接口。提供DOM或SAX中定義的功能。
2. 解析原理
2.1 DOM解析原理
使用DOM要求解析器把整個XML文檔裝載到一個Document對象中。Document對象包含文檔元素,即根元素,根元素包含N多個子元素…
一個XML文檔解析後對應一個Document對象,這說明使用DOM解析XML文檔方便使用,因爲元素與元素之間還保存着結構關係。
優先:使用DOM,XML文檔的結構在內存中依然清晰。元素與元素之間的關係保留了下來!
缺點:如果XML文檔過大,那麼把整個XML文檔裝載進內存,可能會出現內存溢出的現象!
2.2 設置Java最大內存
運行Java程序,指定初始內存大小,以及最大內存大小。
java -Xms20m -Xmx100m MyClass
2.3 SAX解析原理
DOM會一行一行的讀取XML文檔,最終會把XML文檔所有數據存放到Document對象中。SAX也是一行一行的讀取XML文檔,但是當XML文檔讀取結束後,SAX不會保存任何數據,同時整個解析XML文檔的工作也就結束了。
但是,SAX在讀取一行XML文檔數據後,就會給感興趣的用戶一個通知!例如當SAX讀取到一個元素的開始時,會通知用戶當前解析到一個元素的開始標籤。而用戶可以在整個解析的過程中完成自己的業務邏輯,當SAX解析結束,不會保存任何XML文檔的數據。
優先:使用SAX,不會佔用大量內存來保存XML文檔數據,效率也高。
缺點:當解析到一個元素時,上一個元素的信息已經丟棄,也就是說沒有保存元素與元素之間的結構關係,這也大大限制了SAX的使用範圍。如果只是想查詢XML文檔中的數據,那麼使用SAX是最佳選擇!
2.4 SAX解析過程
採用事件驅動,邊讀邊解析:從上到下,一行一行的解析,解析到某一個對象,把對象名稱返回
使用SAX方式不會曹成內存溢出,實現查詢;使用SAX方式不能實現增刪改操縱
使用DOM方式解析XML時,如果文件過大,會造成內存溢出,優點是DOM方式很方便的實現增刪改操作
3. 解析器概述
3.1 什麼是XML解析器
DOM、SAX都是一組解析XML文檔的規範,其實就是接口,這說明需要有實現者能使用,而解析器就是對DOM、SAX的實現了。一般解析器都會實現DOM、SAX兩個規範!
Crimson(sun):JDK1.4之前,Java使用的解析器。性能效差,可以忘記它了!
Xerces(IBM):IBM開發的DOM、SAX解析器,現在已經由Apache基金會維護。是當前最爲流行的解析器之一!在1.5之後,已經添加到JDK之中,也是JAXP的默認使用解析器,但不過在JDK中的包名與Xerces不太一樣。例如:org.apache.xerces包名改爲了com.sun.org.apache.xerces.internal包名,也就是說JDK1.5中的Xerces是被包裝後的XML解析器,但二者區別很小。
Aelfred2(dom4j):DOM4J默認解析器,當DOM4J找不到解析器時會使用他自己的解析器。
4. JAXP概述
4.1 什麼是JAXP
JAXP是由Java提供的,用於隱藏底層解析器的實現。Java要求XML解析器去實現JAXP提供的接口,這樣可以讓用戶使用解析器時不依賴特定的XML解析器。
JAXP本身不是解析器(不是Xerces),也不是解析方式(DOM或SAX),它只是讓用戶在使用DOM或SAX解析器時不依賴特定的解析器。
當用戶使用JAXP提供的方式來解析XML文檔時,用戶無需編寫與特定解析器相關的代碼,而是由JAXP通過特定的方式去查找解析器,來解析XML文檔。
4.2 JAXP對DOM的支持
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("src/students.xml");
在javax.xml.parsers包中,定義了DOM解析器工廠類DocumentBuilderFactory,用於產生DOM解析器。DocumentBuilderFactory是一個抽象類,它有一個靜態方法newInstance(),可以返回一個本類的實例對象。其實該方法返回的是DocumentBuilderFactory類的子類的實例(即工廠實例對象)。那麼這個子類又是哪個子類呢?其實這個子類是由XML解析器提供商提供的,不同的廠商提供的工廠類對抽象工廠的實現是不同的。然後由工廠實例創建解析器對象。
那麼newInstance()這個方法又是如果找到解析器提供商的工廠類的呢?此方法使用下面有序的查找過程來確定要加載的DocumentBuilderFactory實現類:
使用javax.xml.parsers.DocumentBuilderFactory系統屬性。如果設置了這個系統屬性的值,那麼newInstance()方法就以這個屬性的值來構造這個工廠的實例。通過下面的方法可以設置這個系統屬性值。
System.setProperty(“javax.xml.parsers.DocumentBuilderFactory”, “工廠實現類名字”);
我們不建議大家用上面的方法來硬編碼這個系統屬性的值,如果這樣設置,假如將來需要更換解析器,就必需修改代碼。
如果你沒有設置上面的系統屬性,newInstance()方法就會採用下面的途徑來查找抽象工廠的實現類。第二個途徑在查找JRE下的lib子目錄下的jaxp.properties文件。如果這個文件存在,那麼就讀取這個文件。我們可以在%JAVA_HOME%\jre\lib\目錄下創建一個jaxp.properties文件。在這個文件中給出一個鍵值對。如下所示:
javax.xml.parsers.DocumentBuilderFactory=工廠實現類名字
這個key名字必須是javax.xml.parsers.DocumentBuilderFactory,而相對應的值也必須設置類路徑。
如果通過前兩種途徑下沒有找到工廠的實現類,那麼就需要使用服務API。這個服務API實際上是查找一個JAR文件的META-INF\ services\ javax.xml.parsers.DocumentBuilderFactory這個文件(該文件無擴展名)。如果找到了這個文件,就以這個文件的內容做爲工廠實現類。這種方式被大多數解析器提供商所採用。在它們發佈的解析器JAR包中往往會找到上述文件。然後在這個文件當中指定自己解析器的工廠類的名字。我們只需要把這個JAR文件的路徑寫到類路徑(classpath)中就可以了。但要注意的是,如果在你的classpath中有多個解析器的JAR包路徑,這時以前面的類路徑優先。
如果說在前三種途徑中都沒有找到工廠實現類,那麼就使用平臺缺省工廠實現類。
在JAXP的早期的版本(5.0以前)中,除了JAXP API外,還包含了一個叫做Crimson的解析器。從JAXP1.2開始,Sun公司對Apache的Xerces解析器重新包裝了一下,並將org.apache.xerces包名改爲了com.sun.org.apache.xerces.internal,然後在JAXP的開發包中一起提供,作爲缺省的解析器。我們所使用的JDK1.5中包含的缺省解析器就是被重新包裝過後的Xerces解析器。
在獲取到某個特定解析器廠商的DocumentBuilderFactory後,那麼這個工廠對象創建出來的解析器對象當然就是自己廠商的解析器對象了。
4.3 JAXP對SAX的支持
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse("src/students.xml", new DefaultHandler() {
public void startDocument() throws SAXException {
System.out.println("解析開始");
}
public void endDocument() throws SAXException {
System.out.println("解析結束");
}
public void processingInstruction(String target, String data)
throws SAXException {
System.out.println("處理指令");
}
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
System.out.println("元素開始:" + qName);
}
public void characters(char[] ch, int start, int length)
throws SAXException {
System.out.println("文本內容:" + new String(ch, start, length).trim());
}
public void endElement(String uri, String localName, String qName)
throws SAXException {
System.out.println("元素結束:" + qName);
}
});
JAXP對SAX的支持與JAXP對DOM的支持是相同的,這裏就不在贅述!
5. JDOM和DOM4J
5.1 JDOM和DOM4J概述
JDOM和DOM4J都是針對Java解析XML設計的方式,它們與DOM相似。但DOM不是隻針對Java,DOM是跨語言的,DOM在Javascript中也可以使用。而JDOM和DOM4J都是專業爲Java而設計的,使用JDOM和DOM4J,對Java程序員而言會更加方便。
5.2 JDOM和DOM4J比較
JDOM與DOM4J相比,DOM4J完勝!!!所以,我們應該在今後的開發中,把DOM4J視爲首選。
在2000年,JDOM開發過程中,因爲團隊建議不同,分離出一支隊伍,開發了DOM4J。DOM4J要比JDOM更加全面。
5.3 DOM4J查找解析器的過程
DOM4J首先會去通過JAXP的查找方法去查找解析器,如果找到解析器,那麼就使用之;否則會使用自己的默認解析器Aelfred2。
DOM4J對DOM和SAX都提供了支持,可以把DOM解析後的Document對象轉換成DOM4J的Document對象,當然了可以把DOM4J的Document對象轉換成DOM的Document對象。
DOM4J使用SAX解析器把XML文檔加載到內存,生成DOM對象。當然也支持事件驅動的方式來解析XML文檔。
6. XML解析之JAXP(DOM)
6.1 JAXP獲取解析器
6.1.1 JAXP相關包
- JAXP相關開發包:javax.xml
- DOM相關開發包:org.w3c.dom
- SAX相關開發包:org.xml.sax
6.1.2 JAXP與DOM、SAX解析器的關係
JAXP的作用只是爲了讓使用者不依賴某一特定DOM、SAX的解析器實現,當使用JAXP API時,使用者直接接觸的就是JAXP API,而不用接觸DOM、SAX的解析器實現API。
6.1.3 JAXP獲取DOM解析器
當我們需要解析XML文檔時,首先需要通過JAXP API解析XML文檔,獲取Document對象。然後用戶就需要使用DOM API來操作Document對象了。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("src/students.xml");
6.1.4 JAXP保存Document
當我們希望把Document保存到文件中去時,可以使用Transformer對象的transform()方法來完成。想獲取Transformer對象,需要使用TransformerFactory對象。
與JAXP獲取DOM解析器一樣,隱藏了底層解析器的實現。也是通過抽象工廠來完成的,這裏就不在贅述了。
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
trans.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
trans.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "students.dtd");
trans.setOutputProperty(OutputKeys.INDENT, "yes");
Source source = new DOMSource(doc);
Result result = new StreamResult(xmlName);
transformer.transform(source, result);
Transformer類的transform()方法的兩個參數類型爲:Source和Result,DOMSource是Source的實現類,StreamResult是Result的實現類。
6.1.5 JAXP創建Document
有時我們需要創建一個Document對象,而不是從XML文檔解析而來。這需要使用DocumentBuider對象的newDocument()方法。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
doc.setXmlVersion("1.0");
doc.setXmlStandalone(true);
6.1.6 學習DOM之前,先寫兩個方法
- Document getDocument(String xmlName):通過xmlName獲取Document對象;
- void saveDocument(Document doc, String xmlName):保存doc到xmlName文件中。
7. DOM API概述
7.1 Document對應XML文檔
無論使用什麼DOM解析器,最終用戶都需要獲取到Document對象,一個Document對象對應整個XML文檔。也可以這樣說,Document對象就是XML文檔在內存中的表示形式。
通常我們最爲“關心”的就是文檔的根元素。所以我們必須要把Document獲取根元素的方法記住:Element getDocumentElement()。然後通過根元素再一步步獲取XML文檔中的數據。
7.2 DOM API中的類
在DOM中提供了很多接口,用來描述XML文檔中的組成部分。其中包括:文檔(Document)、元素(Element)、屬性(Attr)、文本(Text)、註釋(Comment)、CDATA段(CDATASection)等等。無論是哪種XML文檔組成部分,都是節點(Node)的子接口。
7.3 Node方法介紹
Node基本方法:
String getNodeName()
獲取當前節點的名字。如果當前節點是Element,那麼返回元素名稱。如果當前節點是Text那麼返回#text。如果當前節點是Document那麼返回#documentString getNodeValue()
獲取當前節點的值。只有文本節點有值,其它節點的值都爲nullString getTextContent()
獲取當前節點的文本字符串。如果當前節點爲Text,那麼獲取節點內容。如果當前節點爲Element,那麼獲取元素中所有Text子節點的內容。例如當前節點爲:<name>zhangSan</name>,那麼本方法返回zhangSan。如果當前節點爲:<student><name>zhangSan</name><age>23</age><sex>male</sex></student>,那麼本方法返回zhangSan23male。short getNodeType()
獲取當前節點的類型。Node中有很多short類型的常量,可以通過與這些常量的比較來判斷當前節點的類型。if(node.getNodeType() == Node.ELEMENT_NODE);
Node獲取子節點和父節點方法,只有Document和Element才能使用這些方法:
返回值 | 方法 | 說明 |
---|---|---|
NodeList | getChildNodes() | 獲取當前節點的所有子節點 |
Node | getFirstNode() | 獲取當前節點的第一個子節點 |
Node | getLastNode() | 獲取當前節點的最後一個子節點 |
Node | getParentNode() | 獲取當前節點的父節點。注意Document的父節點爲null |
NodeList表示節點列表,它有兩個方法:
返回值 | 方法 | 說明 |
---|---|---|
int | getLength() | 獲取集合長度 |
Node | item(int index) | 獲取指定下標的節點 |
Node獲取弟兄節點的方法,只有Element才能使用這些方法:
- Node getNextSibling():獲取當前節點的下一個兄弟節點;
- Node getPreviousSibling():獲取當前節點的上一個兄弟節點。
Node添加、替換、刪除子節點方法:
Node appendChild(Node newChild)
把參數節點newChild添加到當前節點的子節點列表的末尾處。返回值爲被添加的子節點newChild對象,方便使用鏈式操作。如果newChild在添加之前已經在文檔中存在,那麼就是修改節點的位置了;Node insertBefore(Node newChild, Node refNode)
把參數節點newChild添加到當前節點的子節點refNode之前。返回值爲被添加的子節點newChild對象,方便使用鏈式操作。如果refNode爲null,那麼本方法與appendNode()方法功能相同。如果newChild節點在添加之前已經在文檔中存在,那麼就是修改節點的位置了。Node removeNode(Node oldChild)
從當前節點中移除子元素oldChild。返回值爲被添加的子節點oldChild對象,方便使用鏈式操作。Node replaceNode(Node newChild, Node oldChild)
將當前節點的子節點oldChild替換爲newChild。
Node獲取屬性集合方法,只有Element可以使用:
- NamedNodeMap getAttributes():返回當前節點的屬性集合。
NamedNodeMap表示屬性的集合,方法如下:
Node的判斷方法:
- boolean hasChildNodes():判斷當前節點是否有子節點;
- boolean hasAttribute():判斷當前節點是否有屬性。
7.4 Docment方法介紹
創建節點方法:
獲取子元素方法:
Element getElementById(String elementId)
通過元素的ID屬性獲取元素節點,如果沒有DTD指定屬性類型爲ID,那麼這個方法將返回null;NodeList getElementsByTagName(String tagName)
獲取指定元素名稱的所有元素;Element getDocumentElement():獲取文檔元素,即獲取根元素。
文檔聲明相關方法:
返回值 | 方法 | 說明 |
---|---|---|
String | getXmlVersion() | 獲取文檔聲明的version屬性值 |
String | getXmlEncoding() | 獲取文檔聲明的encoding屬性值 |
String | getXmlStandalone() | 獲取文檔聲明的standalone屬性值 |
void | setXmlVersion() | 設置文檔聲明version屬性值 |
void | setXmlStandalone() | 設置文檔聲明standalone屬性值 |
7.5 Element方法介紹
獲取方法:
NodeList getElementsByTagName(String tagName):
獲取當前元素的指定元素名稱的所有子元素;String getTagName()
獲取當前元素的元素名。調用元素節點的getNodeName()也是返回名;
屬性相關方法:
返回值 | 方法 | 說明 |
---|---|---|
String | getAttribute(String name) | 獲取當前元素指定屬性名的屬性值 |
Attr | getAttributeNode(String name) | 獲取當前元素指定屬性名的屬性節點 |
boolean | hasAttribute(String name) | 判斷當前元素是否有指定屬性 |
void | removeAttribute(String name) | 移除當前元素的指定屬性 |
void | removeAttributeNode(Attr attr) | 移除當前元素的指定屬性 |
void | setAttribute(String name, String value) | 爲當前元素添加或修改屬性 |
Attr | setAttributeNode(Attr attr) | 爲當前元素添加或修改屬性,返回值爲添加的屬性 |
7.6 Attr方法介紹
返回值 | 方法 | 說明 |
---|---|---|
String | getName() | 獲取當前屬性節點的屬性名 |
String | getValue() | 獲取當前屬性節點的屬性值 |
void | setValue(String value) | 設置當前屬性節點的屬性值 |
boolean | isId() | 判斷當前屬性節點是否爲ID類型屬性 |
實現代碼
/**
* 實現jaxp操作xml
*
*/
public class TestJaxp {
public static void main(String[] args) throws Exception {
//selectAll();
//selectSin();
//addSex();
//modifySex();
//delSex();
listElement();
}
//遍歷節點,把所有元素名稱打印出來
public static void listElement() throws Exception {
/*
* 1、創建解析器工廠
* 2、根據解析器工廠創建解析器
* 3、解析xml,返回document
*
* ====使用遞歸實現=====
* 4、得到根節點
* 5、得到根節點子節點
* 6、得到根節點子節點的子節點
* */
//創建解析器工廠
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
//創建解析器
DocumentBuilder builder = builderFactory.newDocumentBuilder();
//得到document
Document document = builder.parse("src/person.xml");
//編寫一個方法實現遍歷操作
list1(document);
}
//遞歸遍歷的方法
private static void list1(Node node) {
//判斷是元素類型時候纔打印
if(node.getNodeType() == Node.ELEMENT_NODE) {
System.out.println(node.getNodeName());
}
//得到一層子節點
NodeList list = node.getChildNodes();
//遍歷list
for(int i=0;i<list.getLength();i++) {
//得到每一個節點
Node node1 = list.item(i);
//繼續得到node1的子節點
//node1.getChildNodes()
list1(node1);
}
}
//刪除<sex>nan</sex>節點
public static void delSex() throws Exception {
/*
* 1、創建解析器工廠
* 2、根據解析器工廠創建解析器
* 3、解析xml,返回document
*
* 4、獲取sex元素
* 5、獲取sex的父節點
* 6、刪除使用父節點刪除 removeChild方法
*
* 7、回寫xml
* */
//創建解析器工廠
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
//創建解析器
DocumentBuilder builder = builderFactory.newDocumentBuilder();
//得到document
Document document = builder.parse("src/person.xml");
//得到sex元素
Node sex1 = document.getElementsByTagName("sex").item(0);
//得到sex1父節點
Node p1 = sex1.getParentNode();
//刪除操作
p1.removeChild(sex1);
//回寫xml
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(document), new StreamResult("src/person.xml"));
}
//修改第一個p1下面的sex內容是nan
public static void modifySex() throws Exception {
/*
* 1、創建解析器工廠
* 2、根據解析器工廠創建解析器
* 3、解析xml,返回document
*
* 4、得到sex item方法
* 5、修改sex裏面的值 setTextContent方法
*
* 6、回寫xml
* */
//創建解析器工廠
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
//創建解析器
DocumentBuilder builder = builderFactory.newDocumentBuilder();
//得到document
Document document = builder.parse("src/person.xml");
//得到sex
Node sex1 = document.getElementsByTagName("sex").item(0);
//修改sex值
sex1.setTextContent("nan");
//回寫xml
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(document), new StreamResult("src/person.xml"));
}
//在第一個p1下面(末尾)添加 <sex>nv</sex>
public static void addSex() throws Exception {
/*
* 1、創建解析器工廠
* 2、根據解析器工廠創建解析器
* 3、解析xml,返回document
*
* 4、得到第一個p1
* - 得到所有p1,使用item方法下標得到
* 5、創建sex標籤 createElement
* 6、創建文本 createTextNode
* 7、把文本添加到sex下面 appendChild
* 8、把sex添加到第一個p1下面
*
* 9、回寫xml
* */
//創建解析器工廠
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
//創建解析器
DocumentBuilder builder = builderFactory.newDocumentBuilder();
//得到document
Document document = builder.parse("src/person.xml");
//得到所有的p1
NodeList list = document.getElementsByTagName("p1");
//得到第一個p1
Node p1 = list.item(0);
//創建標籤
Element sex1 = document.createElement("sex");
//創建文本
Text text1 = document.createTextNode("nv");
//把文本添加到sex1下面
sex1.appendChild(text1);
//把sex1添加到p1下面
p1.appendChild(sex1);
//回寫xml
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(document), new StreamResult("src/person.xml"));
}
//查詢xml中第一個name元素的值
public static void selectSin() throws Exception {
/*
* 1、創建解析器工廠
* 2、根據解析器工廠創建解析器
* 3、解析xml,返回document
*
* 4、得到所有name元素
* 5、使用返回集合,裏面方法 item,下標獲取具體的元素
* 6、得到具體的值,使用 getTextContent方法
*
* */
//創建解析器工廠
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
//創建解析器
DocumentBuilder builder = builderFactory.newDocumentBuilder();
//解析xml,得到document
Document document = builder.parse("src/person.xml");
//得到所有的name元素
NodeList list = document.getElementsByTagName("name");
//使用下標 得到第一個元素
Node name1 = list.item(1);
//得到name裏面的具體的值
String s1 = name1.getTextContent();
System.out.println(s1);
}
//查詢所有name元素的值
private static void selectAll() throws Exception {
//查詢所有name元素的值
/*
* 1、創建解析器工廠
* 2、根據解析器工廠創建解析器
* 3、解析xml返回document
*
* 4、得到所有的name元素
* 5、返回集合,遍歷集合,得到每一個name元素
* */
//創建解析器工廠 atl / : 代碼提示
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
//創建解析器
DocumentBuilder builder = builderFactory.newDocumentBuilder();
//解析xml返回document
Document document = builder.parse("src/person.xml");
//得到name元素
NodeList list = document.getElementsByTagName("name");
//遍歷集合
for(int i=0;i<list.getLength();i++) {
Node name1 = list.item(i); //得到每一個name元素
//得到name元素裏面的值
String s = name1.getTextContent();
System.out.println(s);
}
}
}
8. SAX
8.1 SAX解析原理
首先我們想一下,DOM解析器是不是需要把XML文檔遍歷一次,然後把每次讀取到的數據轉換成節點對象(到底哪一種節點對象,這要看解析時遇到了什麼東西)保存起來,最後生成一個Document對象返回。也就是說,當你調用了builder.parse(“a.xml”)後,這個方法就會把XML文檔中的數據轉換成節點對象保存起來,然後生成一個Document對象。這個解析XML文檔的過程在parse()方法調用結束後也就結束了。我們的工作是在解析之後,開始對Document對象進行操作。
但是SAX不同,當SAX解析器的parse()方法調用結束後,不會給我們一個Document對象,而是什麼都不給。SAX不會把XML數據保存到內存中,如果我們的解析工作是在SAX解析器的parse()方法調用結束後開始,那麼就已經晚了!!!這說明我們必須在SAX解析XML文檔的同時完成我們的工作。
SAX解析器在解析XML文檔的過程中,讀取到XML文檔的一個部分後,會調用ContentHandler(內容處理器)中的方法。例如當SAX解析到一個元素的開始標籤時,它會調用ContentHandler的startElement()方法;在解析到一個元素的結束標籤時會調用ContentHandler的endElement()方法。
ContentHandler是一個接口,我們的工作是編寫該接口的實現類,然後創建實現類的對象,在SAX解析器開始解析之前,把我們寫的內容處理類對象交給SAX解析器,這樣在解析過程中,我們的內容處理中的方法就會被調用了。
8.2 獲取SAX解析器
與DOM相同,你應該通過JAXP獲取SAX解析器,而不是直接使用特定廠商的SAX解析器。JAXP查找特定廠商的SAX解析器實現的方式與查找DOM解析器實現的方式完全相同,這裏就不在贅述了。
SAXParserFactory factory = SAXParserFactory.newInstance();
javax.xml.parsers.SAXParser parser = factory.newSAXParser();
parser.parse("src/students.xml", new MyContentHandler());
上面代碼中,MyContentHandler就是我們自己需要編寫的ContentHandler的實現類對象。
8.3 內容處理器
org.xml.sax.ContentHandler中的方法:
org.xml.sax.helpers.DefualtHandler對ContentHandler做了空實現,所以我們可以自定義內容處理器時可以繼承DefaultHandler類。
8.4 SAX應用
8.5 測試SAX
public class SAXTest {
@Test
public void testSAX() throws ParserConfigurationException, SAXException, IOException {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse("src/students.xml", new MyContentHandler());
}
private static class MyContentHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
System.out.println("開始解析...");
}
@Override
public void endDocument() throws SAXException {
System.out.println("解析結束...");
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
System.out.println(qName + "元素解析開始");
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
System.out.println(qName + "元素解析結束");
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
String s = new String(ch, start, length);
if(s.trim().isEmpty()) {
return;
}
System.out.println("文本內容:" + s);
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {
}
@Override
public void processingInstruction(String target, String data)
throws SAXException {
System.out.println("處理指令");
}
}
}
8.6 使用SAX打印XML文檔
public class SAXTest2 {
@Test
public void testSAX() throws
ParserConfigurationException, SAXException, IOException {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse("src/students.xml", new MyContentHandler());
}
private static class MyContentHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
System.out.println("<?xml version='1.0' encoding='utf-8'?>");
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
StringBuilder sb = new StringBuilder();
sb.append("<").append(qName);
for(int i = 0; i < atts.getLength(); i++) {
sb.append(" ");
sb.append(atts.getQName(i));
sb.append("=");
sb.append("'");
sb.append(atts.getValue(i));
sb.append("'");
}
sb.append(">");
System.out.print(sb);
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
System.out.print("</" + qName + ">");
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
System.out.print(new String(ch, start, length));
}
}
}
9. DOM4J
9.1 DOM4J是什麼
DOM4J是針對Java開發人員專門提供的XML文檔解析規範,它不同與DOM,但與DOM相似。DOM4J針對Java開發人員而設計,所以對於Java開發人員來說,使用DOM4J要比使用DOM更加方便。
DOM4J對DOM和SAX提供了支持,使用DOM4J可以把org.dom4j.document轉換成org.w3c.Document,DOM4J也支持基於SAX的事件驅動處理模式。
使用者需要注意,DOM4J解析的結果是org.dom4j.Document,而不是org.w3c.Document。DOM4J與DOM一樣,只是一組規範(接口與抽象類組成),底層必須要有DOM4J解析器的實現來支持。
DOM4J使用JAXP來查找SAX解析器,然後把XML文檔解析爲org.dom4j.Document對象。它還支持使用org.w3c.Document來轉換爲org.dom4j.Docment對象。
9.2 DOM4J中的類結構
在DOM4J中,也有Node、Document、Element等接口,結構上與DOM中的接口比較相似。但還是有很多的區別:
在DOM4J中,所有XML組成部分都是一個Node,其中Branch表示可以包含子節點的節點,例如Document和Element都是可以有子節點的,它們都是Branch的子接口。
Attribute是屬性節點,CharacterData是文本節點,文本節點有三個子接口,分別是CDATA、Text、Comment。
9.3 DOM4J獲取Document對象
使用DOM4J來加載XML文檔,需要先獲取SAXReader對象,然後通過SAXReader對象的read()方法來加載XML文檔:
SAXReader reader = new SAXReader();
//reader.setValidation(true);
Document doc = reader.read("src/students.xml");
9.4 DOM4J保存Document對象
保存Document對象需要使用XMLWriter對象的write()方法來完成,在創建XMLWriter時還可以爲其指定XML文檔的格式(縮進字符串以及是否換行),這需要使用OutputFormat來指定。
doc.addDocType("students", "", "students.dtd");
OutputFormat format = new OutputFormat("\t", true);
format.setEncoding("UTF-8");
XMLWriter writer = new XMLWriter(new FileWriter(xmlName), format);
writer.write(doc);
writer.close();
9.5 DOM4J創建Document對象
DocumentHelper類有很多的createXXX()方法,用來創建各種Node對象。
Document doc = DocumentHelper.createDocument();
10. Document操作
10.1 遍歷students.xml
涉及的相關方法:
返回值 | 方法 | 說明 |
---|---|---|
Element | getRootElement() | Document的方法,用來獲取根元素 |
List | elements() | Element的方法,用來獲取所有子元素 |
String | attributeValue(String name) | Element的方法,用來獲取指定名字的屬性值 |
Element | element(String name) | Element的方法,用來獲取第一個指定名字的子元素 |
Element | elementText(String name) | Element的方法,用來獲取第一個指定名字的子元素的文本內容 |
分析步驟:
- 獲取Document對象;
- 獲取root元素;
- 獲取root所有子元素
- 遍歷每個student元素;
- 打印student元素number屬性;
- 打印student元素的name子元素內容;
- 打印student元素的age子元素內容;
- 打印student元素的sex子元素內容。
10.2 給學生元素添加< score>子元素
涉及的相關方法:
返回值 | 方法 | 說明 |
---|---|---|
Element | addElement(String name) | Element的方法,爲當前元素添加指定名字子元素。返回值爲新建元素對象 |
void | setText(String text) | Element的方法,爲當前元素設置文本內容 |
分析步驟:
- 獲取Document對象;
- 獲取root對象;
- 獲取root所有子元素;
- 遍歷所有學生子元素;
- 創建<score>元素,爲<score>添加文本內容;
- 把<score>元素添加到學生元素中。
- 保存Document對象。
10.3 爲張三添加friend屬性,指定爲李四學號
涉及方法:
- addAttribute(String name, String value):Element的方法,爲當前元素添加屬性。
分析步驟:
- 獲取Document對象;
- 獲取root對象;
- 獲取root所有子元素;
- 創建兩個Element引用:zhangSanEle、liSiEle,賦值爲null;
- 遍歷所有學生子元素;
- 如果zhangSanEle和liSiEle都不是null,break;
- 判斷當前學生元素的name子元素文本內容是zhangSan,那麼把當前學生元素賦給zhangSanEle;
- 判斷當前學生元素的name子元素文本內容是liSi,那麼把當前學生元素賦給liSiEle。
- 判斷zhangSanEle和liSiEle都不爲null時:
- 獲取liSiEle的number屬性。
- 爲zhangSanEle添加friend屬性,屬性值爲liSi的number屬性值。
- 保存Document對象。
10.4 刪除number爲ID_1003的學生元素
涉及方法:
- boolean remove(Element e):Element和Document的方法,移除指定子元素;
- Element getParent():獲取父元素,根元素的父元素爲null。
分析步驟:
- 獲取Document對象;
- 獲取root對象;
- 獲取root所有子元素;
- 遍歷所有學生子元素;
- 判斷當前學生元素的number屬性是否爲ID_1003;
- 獲取當前元素的父元素;
- 父元素中刪除當前元素;
- 保存Document對象.
10.5 通過List生成Document並保存
涉及方法:
- DocumentHelper.createDocument():創建Document對象;
- DocumentHelper.createElement(String name):創建指定名稱的Element元素。
分析步驟:
- 創建Document對象;
- 爲Document添加根元素;
- 循環遍歷學生集合List;
- 把當前學生對象轉換成Element元素;
- 把Element元素添加到根元素中;
- 保存Document對象。
把學生轉換成Element步驟分析:
- 創建Element對象;
- 爲Element添加number屬性,值爲學生的number;
- 爲Element添加name子元素,文本內容爲學生的name;
- 爲Element添加age子元素,文本內容爲學生的age;
- 爲Element添加sex子元素,文本內容爲學生的sex。
10.6 新建趙六學生元素,插入到李四之前
涉及方法:
- int indexOf(Node node):Branch的方法,查找指定節點,在當前Branch的子節點集合中的下標位置。
分析步驟:
- 創建趙六學生對象;
- 通過學生對象創建趙六學生元素;
- 通過名稱查找李四元素;
- 查看李四元素在其父元素中的位置;
- 獲取學生子元素List;
- 將趙六元素插入到List中。
通過名字查找元素:
- 獲取Document;
- 獲取根元素;
- 獲取所有學生元素;
- 遍歷學生元素;
- 獲取學生元素name子元素的文本內容,與指定名稱比較;
- 返回當前學生元素。
10.7 其它方法介紹
Node接口
Branch接口,實現了Node接口
Document
返回值 | 方法聲明 | 功能描述 |
---|---|---|
Element | getRootElement() | 獲取根元素 |
void | setRootElement() | 設置根元素 |
String | getXmlEncoding() | 獲取XML文檔的編碼 |
void | setXmlEncoding() | 設置XML文檔的編碼 |
Element方法:
DocumentHelper靜態方法介紹:
返回值 | 方法 | 說明 |
---|---|---|
Document | createDocument() | 創建Dcoument對象 |
Element | createElement(String name) | 創建指定名稱的元素對象 |
Attribute | createAttrbute(Element owner, String name, String value) | 創建屬性對象 |
Text | createText(String text) | 創建文本對象 |
Document | parseText(String text) | 通過給定的字符串生成Document對象 |
public class StuService {
//查詢 根據id查詢學生信息
public static Student getStu(String id) throws Exception {
/*
* 1、創建解析器
* 2、得到document
*
* 3、獲取到所有的id
* 4、返回的是list集合,遍歷list集合
* 5、得到每一個id的節點
* 6、id節點的值
* 7、判斷id的值和傳遞的id值是否相同
* 8、如果相同,先獲取到id的父節點stu
* 9、通過stu獲取到name age值
*
* */
//創建解析器
SAXReader saxReader = new SAXReader();
//得到document
Document document = saxReader.read("src/student.xml");
//獲取所有的id
List<Node> list = document.selectNodes("//id");
//創建student對象
Student student = new Student();
//遍歷list
for (Node node : list) { //node是每一個id節點
//得到id節點的值
String idv = node.getText();
//判斷id是否相同
if(idv.equals(id)) {
//得到id的父節點 stu
Element stu = node.getParent();
//通過stu獲取name和age
String namev = stu.element("name").getText();
String agev = stu.element("age").getText();
student.setId(idv);
student.setName(namev);
student.setAge(agev);
}
}
return student;
}
//增加
public static void addStu(Student student) throws Exception {
/*
* 1、創建解析器
* 2、得到document
* 3、獲取到根節點
* 4、在根節點上面創建stu標籤
* 5、在stu標籤上面依次添加id name age
* 6、在id name age上面依次添加值
*
* 7、回寫xml
* */
//創建解析器
SAXReader saxReader = new SAXReader();
//得到document
Document document = saxReader.read("src/student.xml");
//得到根節點
Element root = document.getRootElement();
//在根節點上面添加stu
Element stu = root.addElement("stu");
//在stu標籤上面依次添加id name age標籤
Element id1 = stu.addElement("id");
Element name1 = stu.addElement("name");
Element age1 = stu.addElement("age");
//在id name age上面依次添加值
id1.setText(student.getId());
name1.setText(student.getName());
age1.setText(student.getAge());
//回寫xml
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter xmlWriter = new XMLWriter(
new FileOutputStream("src/student.xml"), format);
xmlWriter.write(document);
xmlWriter.close();
}
//刪除 根據學生的id刪除
public static void delStu(String id) throws Exception {
/*
* 1、創建解析器
* 2、得到document
*
* 3、獲取到所有的id
* 使用xpath //id 返回 list集合
* 4、遍歷list集合
* 5、判斷集合裏面的id和傳遞的id是否相同
* 6、如果相同,把id所在的stu刪除
*
* */
//創建解析器
SAXReader saxReader = new SAXReader();
//得到document
Document document = saxReader.read("src/student.xml");
//獲取所有的id xpath: //id
List<Node> list = document.selectNodes("//id");
//遍歷list集合
for (Node node : list) { //node是每一個id的元素
//得到id的值
String idv = node.getText();
//判斷idv和傳遞的id是否相同
if(idv.equals(id)) { //id相同
//得到stu節點
Element stu = node.getParent();
//獲取stu的父節點
Element student = stu.getParent();
//刪除stu
student.remove(stu);
}
}
//回寫xml
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter xmlWriter = new XMLWriter(
new FileOutputStream("src/student.xml"), format);
xmlWriter.write(document);
xmlWriter.close();
}
}
11. Schema
我們學習Schema的第一目標是:參照Schema的要求可以編寫XML文檔;
第二目標是:可以自己來定義Schema文檔。
11.1 Schema是什麼
XML文檔的約束,用來替代DTD。
DTD文檔不是XML語法,而Schema本身也是XML文檔,這對解析器來說不用再去處理非XML的文檔了;
DTD只能表述平臺線束,而Schema本身也是XML,所以可以描述結構化的約束信息。
DTD不只約束元素或屬性的類型,但Schema可以。例如讓age屬性的取值在0~100之間。
Schema文檔的擴展名爲xsd,即XML Schema Definition。
11.2 爲students.xml編寫DTD
< !ELEMENT students (student+)>
< !ELEMENT student (name,age,sex)>
< !ELEMENT name (#PCDATA)>
< !ELEMENT age (#PCDATA)>
< !ELEMENT sex (#PCDATA)>
< !ATTLIST student number CDATA #REQUIRED>
11.3 爲students.xml編寫schema
<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="students" type="studentsType"/>
<xsd:complexType name="studentsType">
<xsd:sequence>
<xsd:element name="student" type="studentType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="studentType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="age">
<xsd:simpleType>
<xsd:restriction base="xsd:integer">
<xsd:maxInclusive value="100"/>
<xsd:minInclusive value="0"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="sex">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="男"/>
<xsd:enumeration value="女"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="number" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>
參照Schema編寫XML文檔
我們參照上面的Schema文檔編寫一個studens.xml文件
<?xml version="1.0" encoding="utf-8" standalone="no" ?>
<students xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="students.xsd">
<student number="ID_1001">
<name>張三</name>
<age>23</age>
<sex>男</sex>
</student>
<student number="ID_1002">
<name>李四</name>
<age>32</age>
<sex>女</sex>
</student>
<student number="ID_1003">
<name>王五</name>
<age>50</age>
<sex>男</sex>
</student>
</students>
名稱空間相關內容
XSD文檔中是創建元素和屬性的地方;
XML文檔中是使用元素和屬性的地方。
所以在XML文檔中需要說明使用了哪些XSD文檔。
11.4 什麼是名稱空間
名稱空間是用來處理XML元素或屬性的名字衝突問題。你可以理解爲Java中的包!包的作用就是用來處理類的名字衝突問題。
注意:XML與Java有很大區別,雖然都是處理名字衝突問題,但語法上是有很大區別的。例如在Java中可以使用import來導入類,但你一定要保存你導入的類已經在類路徑(classpath)中存在。使用package爲當前Java文件中所有類聲明名。但XML的名稱空間要比Java複雜很多。
我們在下面講解XML名稱空間時,會使用Java包的概念來理解XML的名稱空間的概念,所以現在大家就要注意這麼幾個特性:
- import:導包,聲明名稱空間;
- package:定義包,指定目標名稱空間;
- classpath:添加到類路徑,關聯XSD文件
11.5 聲明名稱空間(導包)
無論是在XML中,還是在XSD中,都需要聲明名稱空間。這與Java中使用import來導包是一個道理。當然,前提是有包(創建類是使用了package)纔可以導,沒包就不能導了。如果被定義的元素在聲明時沒有指定目標名稱空間,那麼就是在無名稱空間中,那麼我們在使用這些在無名稱空間中的元素時,就不用再去聲明名稱空間了。
聲明名稱空間使用xmlns,例如:xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”。這表示聲明瞭一個名稱空間,相當與Java中的import。但是,Java中的import的含義是在下面使用的類,如果沒有給出包名,那麼就是import導入的這個類。而xmlns表示,下面使用xsi爲前綴的元素或屬性,都是來自http://www.w3.org/2001/XMLSchema-instance名稱空間。也就是說給名稱空間起了一個簡稱,這就相當於我們稱呼“北京傳智播客教育科技有限公司”爲“傳智”一樣。“傳智”就是簡稱。
例如在XSD文件中,xmlns:xsd=”http://www.w3.org/2001/XMLSchema”就是聲明名稱空間,而這個名稱空間是W3C的名稱空間,無需關聯文件就可以直接聲明!在XSD文件中所有使用xsd爲前面的元素和屬性都是來自http://www.w3.org/2001/XMLSchema名稱空間。
名稱空間命名:一般名稱空間都是以公司的URL來命名,即網址!當然也可以給名稱空間命名爲aa、bb之類的名字,但這可能會導致名稱空間的重名問題。
前綴命名:前綴的命名沒有什麼要求,但一般對http://www.w3.org/2001/XMLSchema名稱空間的前綴都是使用xs或xsd。http://www.w3.org/2001/XMLSchema-instance的前綴使用xsi。
在XML文檔中聲明xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”名稱空間的目的是使用xsi中的一個屬性:xsi:noNamespaceSchemaLocation,它是用W3C提供的庫屬性,用來關聯XSD文件用的。當然,它只能關聯那些沒有“目標名稱空間”的XSD文件。下面會講解目標名稱空間!
11.6 默認名稱空間
所謂默認名稱空間就是在聲明名稱空間時,不指定前綴,也可以理解爲前綴爲空字符串的意思。這樣定義元素時,如果沒有指定前綴的元素都是在使用默認名稱空間中的元素。
xmlns=”http://www.itcast.cn”
當在文檔中使用時,那麼元素就是http://www.itcast.cn名稱空間中聲明的元素。
注意:沒有指定前綴的屬性不表示在默認名稱空間中,而是表示沒有名稱空間。也就是說,默認名稱空間不會涉及到屬性,只對元素有效!
12. XPath(擴展)
12.1 什麼是XPath
XPath即爲XML路徑語言(XML Path Language),它是一種用來確定XML文檔中某部分位置的語言,使用XPath可以直接獲取到某個元素。XPath基於XML的樹狀結構,提供在數據結構樹中找尋節點的能力。起初 XPath 的提出的初衷是將其作爲一個通用的、介於XPointer與XSL間的語法模型。但是 XPath 很快的被開發者採用來當作小型查詢語言。
- 第一種形式,/AAA/DDD/BBB: 表示一層一層的,AAA下面 DDD下面的BBB
- 第二種形式,//BBB: 表示和這個名稱相同,表示只要名稱是BBB,都得到
- 第三種形式,/*: 所有元素
- 第四種形式,BBB[1]: 表示第一個BBB元素
BBB[last()]:表示最後一個BBB元素
- 第五種形式,//BBB[@id]: 表示只要BBB元素上面有id屬性,都得到
- 第六種形式,//BBB[@id=’b1’] 表示元素名稱是BBB,在BBB上面有id屬性,並且id的屬性值是b1
12.2 DOM4J對XPath的支持
默認的情況下,dom4j不支持xpath,如果想要在dom4j裏面是有xpath,第一步需要,引入支持xpath的jar包,使用 jaxen-1.1-beta-6.jar
在dom4j裏面提供了兩個方法,用來支持xpath
- List selectNodes(String xpathExpression):在當前節點中查找滿足XPath表達式的所有子節點;
- Node selectSingleNode(String xpathExpression):在當前節點中查找滿足XPath表達式的第一個子節點;
- String valueOf(String xpathExpression):在當前節點中查找滿足XPath表達式的第一個子節點的文本內容;
public class Dom4jUtils {
public static final String PATH = "src/p1.xml";
//返回document
public static Document getDocument(String path) {
try {
//創建解析器
SAXReader reader = new SAXReader();
//得到document
Document document = reader.read(path);
return document;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//回寫xml的方法
public static void xmlWriters(String path,Document document) {
try {
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter xmlWriter = new XMLWriter(new FileOutputStream(path), format);
xmlWriter.write(document);
xmlWriter.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
public class TestDom4jXpath {
public static void main(String[] args) throws Exception {
// test1();
test2();
}
//使用xpath實現:獲取第一個p1下面的name的值
public static void test2() throws Exception {
/*
* 1、得到document
* 2、直接使用selectSingleNode方法實現
* - xpath : //p1[@id1='aaaa']/name
* */
//得到document
Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
//直接使用selectSingleNode方法實現
Node name1 = document.selectSingleNode("//p1[@id1='aaaa']/name"); //name的元素
//得到name裏面的值
String s1 = name1.getText();
System.out.println(s1);
}
//查詢xml中所有name元素的值
public static void test1() throws Exception {
/*
* 1、得到document
* 2、直接使用selectNodes("//name")方法得到所有的name元素
*
* */
//得到document
Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
//使用selectNodes("//name")方法得到所有的name元素
List<Node> list = document.selectNodes("//name");
//遍歷list集合
for (Node node : list) {
//node是每一個name元素
//得到name元素裏面的值
String s = node.getText();
System.out.println(s);
}
}
}