Java開發筆記(一百零九)XML報文的定義和解析

前面介紹了JSON格式的報文解析,雖然json串短小精悍,也能有效表達層次結構,可是每一個元素只能找到對應的元素值,不能體現更豐富的樣式特徵。好比某個元素除了要傳輸它的字符串文本,還想傳輸該文本的類型、字體大小、字體顏色等特徵,且這些額外的風格樣式與業務邏輯無關,天然不適合爲它們單獨設立參數字段。假若採用JSON格式定義包括樣式特徵在內的文本元素,要麼摒棄風格樣式這種附加屬性,要麼將風格樣式單列爲專門的字段參數,然而無論哪一種作法,都未能妥善解決附加屬性的表達問題。可見輕量級的JSON格式依然存在力不從心的狀況,爲此人們早早發明了擁有強大表示能力的XML格式,XML的全稱是「Extensible Markup Language」(可擴展標記語言),它不但支持結構化數據的描述,還支持各種附加屬性的定義,很是適合在網絡中傳輸信息。
下面先看一個XML報文格式的購物訂單樣例:html

<?xml version="1.0" encoding="gbk"?>
<order>
	<user_info>
	<name type="string">思無邪</name>
	<address type="string">桃花島水簾洞123號</address>
	<phone type="string">15960238696</phone>
	</user_info>
	<goods_list>
		<goods_item>
			<goods_name type="string">Mate30</goods_name>
			<goods_number type="int">1</goods_number>
			<goods_price type="double">8888</goods_price>
		</goods_item>
		<goods_item>
			<goods_name type="string">格力中央空調</goods_name>
			<goods_number type="int">1</goods_number>
			<goods_price type="double">58000</goods_price>
		</goods_item>
		<goods_item>
			<goods_name type="string">紅蜻蜓皮鞋</goods_name>
			<goods_number type="int">3</goods_number>
			<goods_price type="double">999</goods_price>
		</goods_item>
	</goods_list>
</order>

 

接着對上面的XML樣例庖丁解牛,分析一下XML格式都有哪些特色,分析結果羅列以下:
一、每一個元素依然由參數名稱和參數值組成,參數名稱爲尖括號所包裹,且分爲標記頭與標記尾兩部分,標記尾在尖括號內部多了個斜杆。如此一來,一個字段的完整形式爲「<參數名稱>參數值</參數名稱>」。
二、由於每一個元素都自帶標記頭與標記尾,很容易區分在哪開始在哪結束,因此元素之間無需額外的分隔符,只要有標記頭與標記尾就足夠辨別了。
三、每一個結構也須要專門的標記頭與標記尾,中間再填入若干元素或者其它結構。
四、對於數組形式的數據,XML報文采用多個同名的結構標記並排列舉,表示這裏存在同名結構的數組信息,也可看做是清單信息。
五、XML格式容許在報文開頭的encoding屬性處指定當前報文的字符編碼類型,常見的有漢字內碼規範GBK,以及世界通用編碼規範UTF-8。
六、每一個結構或者元素節點,也支持在標記頭部分填充附加屬性,用於指定參數值之外的特定信息。
大體瞭解了XML報文的格式規範,還得在程序中加以解析才行。傳統的XML解析方式有DOM和SAX兩種,DOM方式會把整個XML報文讀進來,而且全部節點全被自動加載到一個樹狀結構,之後每一個節點值都到該樹狀結構中讀取。SAX方式不會事先讀入整個XML報文,而是根據節點名稱從報文起點開始掃描,一旦找到該節點的標記頭位置,即刻日後尋找該節點的標記尾,那麼節點標記頭尾之間的數據即是節點值了。單就某個節點值的解析過程而言,加載全部節點的DOM方式顯然較費功夫,從頭順序查找的SAX方式執行效率更高。但若要求同時獲取多個節點的數值,則採起樹狀結構遍歷的DOM方式整體性能更加,而每次都從頭找起的SAX方式無疑作了重複勞動。總之兩種方式的解析效果各有優劣,須要按照實際場景決定取捨。
儘管JDK集成了DOM與SAX的解析工具,其中DOM解析工具封裝在包org.w3c.dom中,SAX解析工具封裝在包javax.xml.parsers中,但是它倆用起來着實費勁,解析過程艱深晦澀,實際開發當中基本不予採用。應用比較多的XML解析工具反而是第三方的Dom4j,Dom4j的解析方式遵循DOM規則,但比起Java自帶的DOM工具要易用得多,其性能也很優異,幾乎成爲Java開發必備的XML解析神器了。經過Dom4j解析XML報文的步驟主要有下列五步:
一、建立SAXReader閱讀器對象;
二、把字符串形式的XML報文轉換爲輸入流對象;
三、命令閱讀器對象從輸入流中讀取Document文檔對象;
四、得到文檔對象的根節點Element;
五、從根節點往下依次解析每一個層級的節點值;
在具體的節點解析過程之中,會頻繁調用Element的相關方法,它的經常使用方法說明以下:
getText:得到當前節點的字符串值。
element:得到當前節點下面指定名稱的子節點對象。
elementText:得到當前節點下面指定名稱的子節點值。
elements:得到當前節點下面指定名稱的子節點清單。
attribute:得到當前節點自身指定名稱的屬性對象。
attributeValue:得到當前節點自身指定名稱的屬性值。
attributes:得到當前節點擁有的所有屬性清單。java

仍之前述的XML報文爲例,下面是採用Dom4j解析該XML串的代碼例子:node

	// 經過dom4j解析xml串
	private static GoodsOrder testParserByDom4j(String xml) {
		GoodsOrder order = new GoodsOrder(); // 建立一個購物訂單對象
		// 建立SAXReader閱讀器對象
		SAXReader reader = new SAXReader();
		// 根據字符串構建字節數組輸入流
		try (InputStream is = new ByteArrayInputStream(xml.getBytes(CHARSET))) {
			// 命令閱讀器從輸入流中讀取文檔對象
			Document document = reader.read(is);
			// 得到文檔對象的根節點
			Element root = document.getRootElement();
			// 獲取根節點下面名叫user_info的節點
			Element user_info = root.element("user_info");
			// 獲取user_info節點下面名叫name的節點值
			order.user_info.name = user_info.element("name").getText();
			// 獲取user_info節點下面名叫address的節點值
			order.user_info.address = user_info.element("address").getText();
			// 獲取user_info節點下面名叫phone的節點值
			order.user_info.phone = user_info.element("phone").getText();
			System.out.println(String.format("用戶信息以下:姓名=%s,地址=%s,手機號=%s", 
					order.user_info.name, order.user_info.address, order.user_info.phone));
			// 獲取根節點下面名叫goods_list的節點清單
			List<Element> goods_list = root.element("goods_list").elements();
			for (int i=0; i<goods_list.size(); i++) { // 遍歷商品節點清單
				Element goods_item = goods_list.get(i);
				GoodsItem item = new GoodsItem(); // 建立一項商品對象
				// 獲取當前商品項節點下面名叫goods_name的節點值
				item.goods_name = goods_item.element("goods_name").getText();
				// 獲取當前商品項節點下面名叫goods_number的節點值
				item.goods_number = Integer.parseInt(goods_item.element("goods_number").getText());
				// 獲取當前商品項節點下面名叫goods_price的節點值
				item.goods_price = Double.parseDouble(goods_item.element("goods_price").getText());
				System.out.println(String.format("第%d個商品:名稱=%s,數量=%d,價格=%f", 
						i+1, item.goods_name, item.goods_number, item.goods_price));
				order.goods_list.add(item); // 往商品清單中添加指定商品對象
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return order; // 返回解析後的購物訂單對象
	}

 

運行以上的解析代碼,觀察到如下的購物訂單日誌,可見成功實現了xml串到對象的解析操做:json

用戶信息以下:姓名=思無邪,地址=桃花島水簾洞123號,手機號=15960238696
第1個商品:名稱=Mate30,數量=1,價格=8888.000000
第2個商品:名稱=格力中央空調,數量=1,價格=58000.000000
第3個商品:名稱=紅蜻蜓皮鞋,數量=3,價格=999.000000

除了解析各節點的節點值,Dom4j還能解析各節點的屬性值,若想正常解析指定名稱的屬性值,則需明確下列三個要素:該屬性的上級節點對象、該屬性所在節點的節點名稱,該屬性的屬性名稱。有了這三個要素,便可經過如下方法從指定節點的指定屬性成功獲取屬性值:數組

	// 打印指定節點名稱的指定屬性值
	private static void printValueAndAttr(Element parent, String node_name, String attr_name) {
		// 獲取父節點下面指定名稱的子節點
		Element element = parent.element(node_name);
		// 得到子節點的節點值
		String node_value = element.getText();
		String attr_value = "";
		// 根據屬性名稱獲取子節點的對應屬性對象
		Attribute attr = element.attribute(attr_name);
		if (attr != null) {
			attr_value = attr.getText(); // 獲取該屬性的屬性值
		}
		// 打印子節點的詳細信息,包括節點名稱、節點值、屬性名稱、屬性值
		System.out.println(String.format("節點名稱=%s, 節點值=%s, 屬性名稱=%s, 屬性值=%s", 
				node_name, node_value, attr_name, attr_value));
	}

 

接下來在原先的XML解析代碼裏補充以下的一行屬性解析代碼:網絡

			// 打印user_info節點的name子節點的type屬性值
			printValueAndAttr(user_info, "name", "type");

 

再次運行XML解析代碼,在輸出的購物訂單日誌中觀察到多了下面這行日誌,表示解析到了name節點的type屬性值:dom

節點名稱=name, 節點值=思無邪, 屬性名稱=type, 屬性值=string

  


更多Java技術文章參見《Java開發筆記(序)章節目錄工具

相關文章
相關標籤/搜索