Java XML Document 處理大全
最近的一個案子需要處理大量的 XML 資料,才發現 Java 在這方面的 API 使用起來不是那麼方便,跟 Json 的處理相比大部分都不是很直覺,趁著還有點心得的時候趕緊寫下筆記,期望能將 XML 視為 Json 一樣操作自如
範例資料
<?xml version="1.0"?>
<users>
<user id="1">
<name>Tony</name>
<phone>0123456789</phone>
</user>
<user id="2">
<name>Amy</name>
<phone>9876543210</phone>
</user>
</users>
讀取
首先先讀入 XML 資料到 Java 內建函式庫的 Document
類別,後續的操作都會針對這個物件展開
String xml = "...";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
Document doc = null;
try {
factory.setNamespaceAware(true);
builder = factory.newDocumentBuilder();
doc = builder.parse(new InputSource(new StringReader(xml)));
} catch (Exception e) {
e.printStackTrace();
}
迴圈讀取
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
System.out.println("Node name: " + node.getNodeName());
System.out.println("Node id: " + node.getAttributes().getNamedItem("id").getNodeValue());
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element subEle = (Element) node;
NodeList subList = subEle.getChildNodes();
for (int j = 0; j < subList.getLength(); j++) {
Node subNode = subList.item(j);
System.out.println("Node name: " + subNode.getNodeName());
System.out.println("Node text: " + subNode.getTextContent());
}
}
}
因為只有兩層就先寫成兩個迴圈,按照這個邏輯去遞迴的話就可以遍歷整個 XML 的資料樹
根據 TagName 讀取
只不過通常的使用情境上都是已經知道內容結構,然後去取得指定的資料,那麼就可以改寫成下面的樣子
NodeList nodeList = doc.getElementsByTagName("user");
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
System.out.println("Node name: " +
node.getNodeName());
System.out.println("Node id: " +
node.getAttributes().getNamedItem("id").getNodeValue());
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element subEle = (Element) node;
System.out.println("name: " +
subEle.getElementsByTagName("name").item(0).getTextContent());
System.out.println("phone: " +
subEle.getElementsByTagName("phone").item(0).getTextContent());
}
}
要注意到的是,第一層並不是從 users
開始讀,而是直接抓到 user
那層,這要特別注意如果其他層有一樣的 tagName 會全部收錄進來,如果設計上沒有考量好有可能會有找錯節點的問題
XPath
如果說對於結構已經有事先的了解了,其實這個做法是最推薦的
XPath xPath = XPathFactory.newInstance().newXPath();
String expression = "/users/user";
NodeList nodeList = null;
try {
nodeList = (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
System.out.println("Node name: " +
node.getNodeName());
System.out.println("Node id: " +
node.getAttributes().getNamedItem("id").getNodeValue());
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element subEle = (Element) node;
System.out.println("name: " +
subEle.getElementsByTagName("name").item(0).getTextContent());
System.out.println("phone: " +
subEle.getElementsByTagName("phone").item(0).getTextContent());
}
}
} catch (XPathExpressionException e) {
e.printStackTrace();
}
XPath
是一種 XML 的路徑語言,本來就是用來定位 XML 中的資料位置的,詳細的語法可以參考,也可以到這裡,實驗看看 XPath
的正確性
可以描述完整的路徑去找到需要的節點,比較可以確保不會找錯節點
toString
有些時候從第三方的函式庫會直接收到 Document
的 XML 物件,有些時候需要將其轉換回字串來使用
public String xmlToString(Node doc, Boolean pretty) {
try {
StringWriter sw = new StringWriter();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
if (pretty) {
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
}
transformer.transform(new DOMSource(doc), new StreamResult(sw));
return sw.toString();
} catch (Exception ex) {
ex.printStackTrace()
}
}
如果需要轉換成 Json 或是其他格式,可以參考上面遍歷整個 XML 的方法,再根據屬性去建立整個結構
建立
有時候會需要從其他格式轉換到 XML 來,需要從頭建立整個 Document
的結構,一樣用上面的結構舉例來從頭建立
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
Element usersEle = doc.createElement("users");
Element userTony = doc.createElement("user");
Element tonyName = doc.createElement("name");
Element tonyPhone = doc.createElement("phone");
userTony.setAttribute("id", "1");
tonyName.setTextContent("Tony");
tonyPhone.setTextContent("0123456789");
userTony.appendChild(tonyName);
userTony.appendChild(tonyPhone);
usersEle.appendChild(userTony);
Element userAmy = doc.createElement("user");
Element amyName = doc.createElement("name");
Element amyPhone = doc.createElement("phone");
userAmy.setAttribute("id", "2");
amyName.setTextContent("Amy");
amyPhone.setTextContent("9876543210");
userAmy.appendChild(amyName);
userAmy.appendChild(amyPhone);
usersEle.appendChild(userAmy);
doc.appendChild(usersEle);
System.out.println(xmlToString(doc, true));
刪除
刪除 XML 的節點在內建的 API 中只有提供
public Node removeChild(Node oldChild) throws DOMException;
注意到方法的傳入參數是 Node
也就是說,在刪除之前必須先定位到想刪除的節點,因此操作上可能會像這樣
Node node = doc.getElementsByTagName("user").item(0);
doc.removeChild(node);
結語
整理過後會發現也沒這麼複雜,只是跟 Json 的操作比起來還是繁雜了一點,被各種方便的函式庫寵壞了