Java主要提供了三种XML的解析和节点遍历模型,它们分别是DOM(最常用)、SAX(最高效)和XPath(最直接)。我们这里主要是通过实例的方式一步一步来了解其中主要的两种模型DOM和XPath。
1. 最简单的XML:
1 //1. 这个例子是通过Element的TagName(标签名)来直接获取元素对象实例的。
2 //然后再通过该Element访问其包含的数据。
3 //2. 这个例子中<RootElement>、<FirstElement>和<SecondElement>都被称为Element。
4 //3. "I am the first Node"和"I am the second Node"被称为Text。
5 //文件Test.xml的样例数据
6 //<RootElement>
7 // <FirstElement>I am the first Node</FirstElement>
8 // <SecondElement>I am the second Node</SecondElement>
9 //</RootElement>
10 public static void main(String[] args) {
11 // 取得DocmentBuilderFactory对象
12 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
13 // 取得DocumentBuilder对象
14 DocumentBuilder builder = null;
15 try {
16 builder = factory.newDocumentBuilder();
17 } catch (ParserConfigurationException e) {
18 }
19 // 取得Document对象
20 Document doc = null;
21 try {
22 doc = builder.parse(new File("D:/test.xml"));
23 } catch (SAXException e) {
24 } catch (IOException e) {
25 }
26
27 NodeList nl = doc.getElementsByTagName("RootElement");
28 for (int i = 0; i < nl.getLength(); i++) {
29 Element e = (Element) nl.item(i);
30 System.out.println(e.getElementsByTagName("FirstElement").item(0)
31 .getFirstChild().getNodeValue());
32 System.out.println(e.getElementsByTagName("SecondElement")
33 .item(0).getFirstChild().getNodeValue());
34 }
35 }
36 //1. 没有再通过指定标签名这种特化的方式获取Element节点,而是通过一种
37 //更为泛化的方式去获取Element节点下的子节点,这种方式和普通树的遍历
38 //极为相似。
39 //2. 尽管同为Node节点(Node的实现类),但是仍然可以通过node.getNodeType()
40 //函数获的Node节点的类型,如Node.ELEMENT_NODE、Node.ATTRIBUTE_NODE等。
41 //文件Test.xml的样例数据
42 //<RootElement>
43 // <FirstElement>I am the first Node</FirstElement>
44 // <SecondElement>I am the second Node</SecondElement>
45 //</RootElement>
46 public static void main(String[] args) {
47 String filename = "D:/Test.xml";
48 try {
49 System.out.println(filename + " elementCount: " + getElementCount(filename));
50 } catch (Exception e) {
51 }
52 }
53 /* 调用 getElementCount(Node),Node是节点 */
54 public static int getElementCount(String fileName) throws Exception {
55 Node node = readFile(new File(fileName));
56 return getElementCount(node); // 统计Elements
57 }
58 /* 解析文档,返回 Document */
59 public static Document readFile(File file) throws Exception {
60 Document doc;
61 try {
62 /* 首先获得一个 DocumentBuilderFactory 的实例 */
63 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
64 /* 创建一个 DocumentBuilder 的实例 */
65 DocumentBuilder db = dbf.newDocumentBuilder();
66 /* 解析文档 */
67 doc = db.parse(file);
68 /* 返回一个 Document 实例 */
69 return doc;
70 } catch (SAXParseException ex) {
71 throw (ex);
72 } catch (SAXException ex) {
73 Exception x = ex.getException(); // get underlying Exception
74 throw ((x == null) ? ex : x);
75 }
76 }
77 /*
78 * 使用DOM方法来统计 ELEMENTS
79 */
80 public static int getElementCount(Node node) {
81 /* 如果node为空的话,然回0 */
82 if (null == node)
83 return 0;
84 int sum = 0;
85 // 首先,第一个是节点的根,判断一下是不是ELEMENT
86 boolean isElement = (node.getNodeType() == Node.ELEMENT_NODE);
87 // 如果节点的根是ELEMENT节点,那sum的初始值就是1
88 if (isElement)
89 sum = 1;
90 // 发挥节点的所有子节点
91 NodeList children = node.getChildNodes();
92 // 没有任何子节点的情况下,返回当前值
93 if (null == children)
94 return sum;
95 // 遍历节点的所有子节点
96 for (int i = 0; i < children.getLength(); i++)
97 sum += getElementCount(children.item(i));
98 return sum;
99 }
2. 一个相对完整的通过DOM模型遍历XML所有节点的例子:
1 public class MyTest {
2 //以下为该示例的样例数据
3 //该例子仅为样例代码,有些实现部分为hardcode,只是为了演示XML各种节点的访问方式。
4 //<attributes>
5 // <!--Comment1-->
6 // <attribute1 name="Technology" type="C" size="100" allow_multi_val="N" />
7 // <attribute2 name="Mfg_Area" type="C" size="100" allow_multi_val="N" />
8 // <attribute3 name="Product_Family" type="C" size="100" allow_multi_val="N"/>
9 // <![CDATA[ cdatatest1 ]]>
10 // <attributeWithText>This is test data</attributeWithText>
11 //</attributes>
12 public static void main(String[] args) {
13 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
14 factory.setIgnoringElementContentWhitespace(true);
15 // 取得DocumentBuilder对象
16 DocumentBuilder builder = null;
17 try {
18 builder = factory.newDocumentBuilder();
19 } catch (ParserConfigurationException e) {
20 }
21 ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
22 PrintStream ps = new PrintStream(bout);
23 ps.println("<attributes>");
24 ps.println("<!--Comment1-->");
25 ps.println("<![CDATA[ cdatatest1 ]]>");
26 ps.println("<attribute1 name=\"Technology\" type=\"C\" " +
27 "size=\"100\" isnull=\"true\" allow_multi_val=\"N\" />");
28 ps.println("<attribute2 name=\"Mfg_Area\" type=\"C\" " +
29 "size=\"100\" isnull=\"true\" allow_multi_val=\"N\" />");
30 ps.println("<attribute3 name=\"Product_Family\" type=\"C\" " +
31 "size=\"100\" isnull=\"true\" allow_multi_val=\"N\" />");
32 ps.println("<attributeWithText>This is test data</attributeWithText>");
33 ps.print("</attributes>");
34 ps.flush();
35 ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
36 // 取得Document对象
37 try {
38 doc = builder.parse(bin);
39 } catch (SAXException e) {
40 e.printStackTrace();
41 } catch (IOException e) {
42 }
43 // 1. 首先获取根节点
44 Element rootElement = doc.getDocumentElement();
45 printElement(rootElement);
46 System.out.printf("</%s>",rootElement.getTagName());
47 }
48 static void printIndent() {
49 for (int i = 0; i < indentLevel; ++i)
50 System.out.print("\t");
51 }
52 static void printElement(Element element) {
53 printIndent();
54 System.out.printf("<%s",element.getTagName());
55 //注意这里的Attribute被存储在Map里面,因此他是基于AtrributeName排序的。
56 //5. 获取属性节点列表,并遍历Element的属性列表
57 NamedNodeMap nodeMap = element.getAttributes();
58 for (int i = 0; i < nodeMap.getLength(); ++i) {
59 Node node = nodeMap.item(i);
60 if (i == 0)
61 System.out.print(" ");
62 //6. 获取每个属性的Name和Value。
63 System.out.printf("%s = %s ", node.getNodeName(), node.getNodeValue());
64 if (i == nodeMap.getLength() - 1)
65 System.out.print(" />");
66 }
67 String text = element.getTextContent();
68 if (nodeMap.getLength() == 0)
69 System.out.print(">");
70 if (element != doc.getDocumentElement()) {
71 if (!text.trim().isEmpty()) {
72 System.out.print(text);
73 System.out.printf("</%s>",element.getTagName());
74 }
75 }
76 System.out.println();
77 // 2. 获取子节点列表
78 NodeList nodeList = element.getChildNodes();
79 indentLevel++;
80 for (int i = 0; i < nodeList.getLength(); ++i) {
81 // 3. 遍历子节点
82 Node node = nodeList.item(i);
83 // 4. 获取节点类型
84 if (node.getNodeType() == Node.ELEMENT_NODE) {
85 printElement((Element) node);
86 } else {
87 printOtherNode(node);
88 }
89 }
90 indentLevel--;
91 }
92 static void printOtherNode(Node node) {
93 if (node.getNodeType() == Node.COMMENT_NODE) {
94 printIndent();
95 System.out.printf("<!--%s-->\n",node.getNodeName());
96 } else if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
97 printIndent();
98 System.out.printf("<![CDATA[ %s ]]>\n",node.getNodeName());
99 }
100 }
101 private static Document doc;
102 private static int indentLevel = 0;
103 }
3. 通过XPath的方式访问XML中的节点:
XPath语言的基本语法如下:
1) /gridbag/row: 表示根元素gridbag的子元素中所有的row元素。
2) /gridbag/row[1]: 使用[]选定一组元素中的特定元素,这里选择的是第一个子元素。
3) /gridbag/row[1]/cell[1]/@anchor: 描述了第一行第一个单元格的anchor属性。
4) /gridbag/row/cell/@anchor: 描述了作为根元素gridbag的子元素的那些行元素中的所有单元格的anchor属性节点。
5) count(/gridbag/row): XPath还提供了一些有用的函数,如count。
在Java SE5中增加了一个API来计算XPath表达式,需要先从XPathFactory创建一个XPath对象,如:
XPathFactory xpFactory = XPathFactory.newInstance();
XPath path = xpFactory.newXPath();
然后再调用evaluate方法来计算XPath表达式,如:
String username = path.evaluate("/gridbag/row[1]/@anthor",doc);
这种形式的evaluate()方法将返回一个字符串,也可以返回一组子节点,如:
NodeList nodes = path.evaluate("/gridbag/row",doc,XPathConstants.NODESET);
如果只有一个节点,则可以用NODE常量来代替NODESET,如:
Node node = path.evaluate("/gridbag/row[1]",doc,XPathConstants.NODE);
如果结果是一个数字,则使用XPathConstants.NUMBER,如:
int count = ((Number)path.evaluate("count(/gridbag/row)",doc,XPathConstants.NUMBER)).intValue();
以下代码仅列出比较常用的基于XPath查询XML的使用方式。
1 public class MyTest {
2 // <projects>
3 // <project id = "BP001">
4 // <name>Banking Project</name>
5 // <start-date>Jan 10 1999</start-date>
6 // <end-date>Jan 10 2003</end-date>
7 // </project>
8 // <project id = "TP001">
9 // <name>Telecommunication Project</name>
10 // <start-date>March 20 1999</start-date>
11 // <end-date>July 30 2004</end-date>
12 // </project>
13 // <project id = "PP001">
14 // <name>Portal Project</name>
15 // <start-date>Dec 10 1998</start-date>
16 // <end-date>March 10 2006</end-date>
17 // </project>
18 // </projects>
19 public static void main(String[] args) {
20 XPathReader reader = new XPathReader("D:/Test.xml");
21 //获取projects的第一个名字为project的子节点中属性名称为id的值。
22 String expression = "/projects/project[1]/@id";
23 System.out.println(reader.read(expression, XPathConstants.STRING));
24 //获取projects的第二个名字为project的子节点的name子节点的文本
25 expression = "/projects/project[2]/name";
26 System.out.println(reader.read(expression, XPathConstants.STRING));
27 //找到所有名字为project的节点,再搜索他们的子节点,找到名字为name的子节点,
28 //如果该name子节点的文本值为Telecommunication Project,那么包含该name的
29 //project节点将为符合该xpath查询条件的节点,之后在获取他的name子节点。
30 expression = "//project[name=\"Telecommunication Project\"]/name";
31 System.out.println(reader.read(expression, XPathConstants.STRING));
32 //获取projects的第三个名字为project的子节点的全部
33 expression = "/projects/project[3]";
34 NodeList thirdProject = (NodeList) reader.read(expression, XPathConstants.NODESET);
35 traverse(thirdProject);
36 }
37
38 private static void traverse(NodeList rootNode) {
39 for (int index = 0; index < rootNode.getLength(); index++) {
40 Node aNode = rootNode.item(index);
41 if (aNode.getNodeType() == Node.ELEMENT_NODE) {
42 NodeList childNodes = aNode.getChildNodes();
43 if (childNodes.getLength() > 0) {
44 System.out.println("Name:" + aNode.getNodeName() + ",Value:"
45 + aNode.getTextContent());
46 }
47 traverse(aNode.getChildNodes());
48 }
49 }
50 }
51 }
52
53 class XPathReader {
54 private String xmlFile;
55 private Document xmlDocument;
56 private XPath xPath;
57 public XPathReader(String xmlFile) {
58 this.xmlFile = xmlFile;
59 initObjects();
60 }
61 private void initObjects() {
62 try {
63 //先将XML文件解析为DOM模型,并存储在内存中。
64 xmlDocument = DocumentBuilderFactory.newInstance()
65 .newDocumentBuilder().parse(xmlFile);
66 //实例化XPath对象
67 xPath = XPathFactory.newInstance().newXPath();
68 } catch (IOException ex) {
69 } catch (SAXException ex) {
70 } catch (ParserConfigurationException ex) {
71 }
72 }
73 public Object read(String expression, QName returnType) {
74 try {
75 //如果该表达式会被反复执行,那么先将其编译之后再执行可以提高效率。
76 XPathExpression xPathExpression = xPath.compile(expression);
77 //returnType表示evaluate将返回的节点类型。
78 return xPathExpression.evaluate(xmlDocument, returnType);
79 } catch (XPathExpressionException ex) {
80 return null;
81 }
82 }
83 }
4. 构造XML的DOM模型,同时输出该DOMTreeModel到XML文件。
1 public class MyTest {
2 public static void main(String[] args) {
3 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
4 Element root = null, attr = null,subAttr = null;
5 try {
6 factory.setIgnoringElementContentWhitespace(true);
7 DocumentBuilder db = factory.newDocumentBuilder();
8 Document xmldoc = db.parse(new File("D:/Test.xml"));
9 root = xmldoc.getDocumentElement();
10 //1. 给原有DOMTreeModel添加新节点
11 //创建子Element
12 attr = xmldoc.createElement("attribute4");
13 subAttr = xmldoc.createElement("subAttribute");
14 //给Element设置文本数据
15 subAttr.setTextContent("Hello, I am sub-attribute.");
16 attr.appendChild(subAttr);
17 //给Element添加属性数据
18 attr.setAttribute("attrFirstName", "attrFirstValue");
19 root.appendChild(attr);
20 output(xmldoc, null);
21 output(xmldoc, "D:/Test1_Edited.xml");
22 //通过xpath查找某个节点都是输出找到的子节点。
23 attr = (Element)selectSingleNode("/attributes/attribute3",root);
24 //修改找到节点的其中一个属性
25 attr.getAttributeNode("name").setNodeValue("Hello");
26 output(attr,null);
27 root.getElementsByTagName("attribute3").item(1).setTextContent("This is other test");
28 output(root,null);
29 //删除其中一个子节点
30 root.removeChild(root.getElementsByTagName("attributeWithText").item(0));
31 output(root,null);
32 //通过xpath方式获取一组节点,之后在逐个删除
33 NodeList attrs = selectNodes("/attributes/attribute3", root);
34 for (int i = 0; i < attrs.getLength(); ++i)
35 attrs.item(i).getParentNode().removeChild(attrs.item(i));
36 root.normalize();
37 output(root,null);
38 } catch (ParserConfigurationException e) {
39 } catch (SAXException e) {
40 } catch (IOException e) {
41 }
42 }
43
44 public static void output(Node node, String filename) {
45 TransformerFactory transFactory = TransformerFactory.newInstance();
46 try {
47 Transformer transformer = transFactory.newTransformer();
48 // 设置各种输出属性
49 transformer.setOutputProperty("encoding", "gb2312");
50 transformer.setOutputProperty("indent", "yes");
51 DOMSource source = new DOMSource();
52 // 将待转换输出节点赋值给DOM源模型的持有者(holder)
53 source.setNode(node);
54 StreamResult result = new StreamResult();
55 if (filename == null) {
56 // 设置标准输出流为transformer的底层输出目标
57 result.setOutputStream(System.out);
58 } else {
59 result.setOutputStream(new FileOutputStream(filename));
60 }
61 // 执行转换从源模型到控制台输出流
62 transformer.transform(source, result);
63 } catch (TransformerConfigurationException e) {
64 } catch (TransformerException e) {
65 } catch (FileNotFoundException e) {
66 }
67 }
68
69 public static Node selectSingleNode(String express, Object source) {
70 Node result = null;
71 XPathFactory xpathFactory = XPathFactory.newInstance();
72 XPath xpath = xpathFactory.newXPath();
73 try {
74 //对于只是执行单次的xpath查询,可以不通过compile而直接执行。
75 result = (Node) xpath.evaluate(express, source, XPathConstants.NODE);
76 } catch (XPathExpressionException e) {
77 }
78 return result;
79 }
80
81 public static NodeList selectNodes(String express, Object source) {
82 NodeList result = null;
83 XPathFactory xpathFactory = XPathFactory.newInstance();
84 XPath xpath = xpathFactory.newXPath();
85 try {
86 result = (NodeList) xpath.evaluate(express, source, XPathConstants.NODESET);
87 } catch (XPathExpressionException e) {
88 }
89 return result;
90 }
91 }