Java

XML 基础

XML 是一个带有开始和结束标记的分层标记语言,每一个标记(标签)内可以包含零个或更多数据。下面是一个 XML 的简单示例片段:

  1. <xml>
  2. <node>
  3. <element>Penguin</element>
  4. </node>
  5. </xml>

在这个 自我描述的(self-descriptive) 例子中,XML 解析器使用了以下几个概念:
◈ 文档(Document):<xml> 标签标志着一个 文档 的开始,</xml> 标签标志着这个文档的结束。
◈ 节点(Node):<node> 标签代表了一个 节点。
◈ 元素(Element):<element>Penguin</element> 中,从开头的 < 到最后的 > 表示了一个 元素。
◈ 内容(Content):在 <element> 元素里,字符串 Penguin 就是 内容。
只要了解了以上几个概念,就可以开始编写、解析 XML 文件了。

创建一个示例配置文件

要学习如何解析 XML 文件,只需要一个极简的示例文件就够了。假设现在有一个配置文件,里面保存的是关于一个图形界面窗口的属性:

  1. <xml>
  2. <window>
  3. <theme>Dark</theme>
  4. <fullscreen>0</fullscreen>
  5. <icons>Tango</icons>
  6. </window>
  7. </xml>

创建一个名为 ~/.config/DemoXMLParser 的目录:

  1. $ mkdir ~/.config/DemoXMLParser

在 Linux 中,~/.config 目录是存放配置文件的默认位置,这是在 自由桌面工作组🔗 specifications.freedesktop.org 的规范中定义的。如果正在使用一个不遵守 自由桌面工作组(Freedesktop)标准的操作系统,仍然可以使用这个目录,只不过需要自己创建这些目录了。
复制 XML 的示例配置文件,粘贴并保存为 ~/.config/DemoXMLParser/myconfig.xml 文件。

使用 Java 解析 XML

刚开始先不要太关注依赖导入和异常捕获这些,可以先尝试用 javax 和 java.io 包里的标准 Java 扩展来实例化一个解析器。如果使用了 IDE,它会提示导入合适的依赖。

  1. Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
  2. File configFile = new File(configPath.toString(), "myconfig.xml");
  3. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  4. DocumentBuilder builder = null;
  5. builder = factory.newDocumentBuilder();
  6. Document doc = null;
  7. doc = builder.parse(configFile);
  8. doc.getDocumentElement().normalize();

这段示例代码使用了 java.nio.Paths 类来找到用户的主目录,然后在拼接上默认配置文件的路径。接着,它用 java.io.File 类来把配置文件定义为一个 File 对象。
紧接着,它使用了 javax.xml.parsers.DocumentBuilderjavax.xml.parsers.DocumentBuilderFactory 这两个类来创建一个内部的文档构造器,这样 Java 程序就可以导入并解析 XML 数据了。
最后,Java 创建一个叫 doc 的文档对象,并且把 configFile 文件加载到这个对象里。通过使用 org.w3c.dom 包,它读取并规范化了 XML 数据。
基本上就是这样啦。理论上来讲,已经完成了数据解析的工作。可是,如果不能够访问数据的话,数据解析也没有多少用处。所以,再来写一些查询,从配置中读取重要的属性值。

使用 Java 访问 XML 的值

从已经读取的 XML 文档中获取数据,其实就是要先找到一个特定的节点,然后遍历它包含的所有元素。通常会使用多个循环语句来遍历节点中的元素,但是为了保持代码可读性,会尽可能少地使用循环语句:

  1. NodeList nodes = doc.getElementsByTagName("window");
  2. for (int i = 0; i < nodes.getLength(); i++) {
  3. Node mynode = nodes.item(i);
  4. System.out.println("Property = " + mynode.getNodeName());
  5. if (mynode.getNodeType() == Node.ELEMENT_NODE) {
  6. Element myelement = (Element) mynode;
  7. System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
  8. System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
  9. System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
  10. }
  11. }

这段示例代码使用了 org.w3c.dom.NodeList 类,创建了一个名为 nodes 的 NodeList 对象。这个对象包含了所有名字匹配字符串 window 的子节点,实际上这样的节点只有一个,因为本文的示例配置文件中只配置了一个。
紧接着,它使用了一个 for 循环来遍历 nodes 列表。具体过程是:根据节点出现的顺序逐个取出,然后交给一个 if-then 子句处理。这个 if-then 子句创建了一个名为 myelement 的 Element 对象,其中包含了当前节点下的所有元素。可以使用例如 getChildNodesgetElementById 方法来查询这些元素,项目中还 记录了 www.w3.org 其他查询方法。
在这个示例中,每个元素就是配置的键。而配置的值储存在元素的内容中,可以使用 .getTextContent 方法来提取出配置的值。
在 IDE 中运行代码(或者运行编译后的二进制文件):

  1. $ java ./DemoXMLParser.java
  2. Property = window
  3. Theme = Dark
  4. Fullscreen = 0
  5. Icon set = Tango

下面是完整的代码示例:

  1. package myConfigParser;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.nio.file.Path;
  5. import java.nio.file.Paths;
  6. import javax.xml.parsers.DocumentBuilder;
  7. import javax.xml.parsers.DocumentBuilderFactory;
  8. import javax.xml.parsers.ParserConfigurationException;
  9. import org.w3c.dom.Document;
  10. import org.w3c.dom.Element;
  11. import org.w3c.dom.NamedNodeMap;
  12. import org.w3c.dom.Node;
  13. import org.w3c.dom.NodeList;
  14. import org.xml.sax.SAXException;
  15. public class ConfigParser {
  16. public static void main(String[] args) {
  17. Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
  18. File configFile = new File(configPath.toString(), "myconfig.xml");
  19. DocumentBuilderFactory factory =
  20. DocumentBuilderFactory.newInstance();
  21. DocumentBuilder builder = null;
  22. try {
  23. builder = factory.newDocumentBuilder();
  24. } catch (ParserConfigurationException e) {
  25. e.printStackTrace();
  26. }
  27. Document doc = null;
  28. try {
  29. doc = builder.parse(configFile);
  30. } catch (SAXException e) {
  31. e.printStackTrace();
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. doc.getDocumentElement().normalize();
  36. NodeList nodes = doc.getElementsByTagName("window");
  37. for (int i = 0; i < nodes.getLength(); i++) {
  38. Node mynode = nodes.item(i);
  39. System.out.println("Property = " + mynode.getNodeName());
  40. if (mynode.getNodeType() == Node.ELEMENT_NODE) {
  41. Element myelement = (Element) mynode;
  42. System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
  43. System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
  44. System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
  45. } // close if
  46. } // close for
  47. } // close method
  48. } //close class

使用 Java 更新 XML

用户时不时地会改变某个偏好项,这时候 org.w3c.dom 库就可以更新某个 XML 元素的内容。只需要选择这个 XML 元素,就像读取它时那样。不过,此时不再使用 .getTextContent 方法,而是使用 .setTextContent 方法。

  1. updatePref = myelement.getElementsByTagName("fullscreen").item(0);
  2. updatePref.setTextContent("1");
  3. System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());

这么做会改变应用程序内存中的 XML 文档,但是还没有把数据写回到磁盘上。配合使用 javax 和 w3c 库,就可以把读取到的 XML 内容写回到配置文件中。

  1. TransformerFactory transformerFactory = TransformerFactory.newInstance();
  2. Transformer xtransform;
  3. xtransform = transformerFactory.newTransformer();
  4. DOMSource mydom = new DOMSource(doc);
  5. StreamResult streamResult = new StreamResult(configFile);
  6. xtransform.transform(mydom, streamResult);

这么做会没有警告地写入转换后的数据,并覆盖掉之前的配置。
下面是完整的代码,包括更新 XML 的操作:

  1. package myConfigParser;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.nio.file.Path;
  5. import java.nio.file.Paths;
  6. import javax.xml.parsers.DocumentBuilder;
  7. import javax.xml.parsers.DocumentBuilderFactory;
  8. import javax.xml.parsers.ParserConfigurationException;
  9. import javax.xml.transform.Transformer;
  10. import javax.xml.transform.TransformerException;
  11. import javax.xml.transform.TransformerFactory;
  12. import javax.xml.transform.dom.DOMSource;
  13. import javax.xml.transform.stream.StreamResult;
  14. import org.w3c.dom.Document;
  15. import org.w3c.dom.Element;
  16. import org.w3c.dom.Node;
  17. import org.w3c.dom.NodeList;
  18. import org.xml.sax.SAXException;
  19. public class ConfigParser {
  20. public static void main(String[] args) {
  21. Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
  22. File configFile = new File(configPath.toString(), "myconfig.xml");
  23. DocumentBuilderFactory factory =
  24. DocumentBuilderFactory.newInstance();
  25. DocumentBuilder builder = null;
  26. try {
  27. builder = factory.newDocumentBuilder();
  28. } catch (ParserConfigurationException e) {
  29. // TODO Auto-generated catch block
  30. e.printStackTrace();
  31. }
  32. Document doc = null;
  33. try {
  34. doc = builder.parse(configFile);
  35. } catch (SAXException e) {
  36. // TODO Auto-generated catch block
  37. e.printStackTrace();
  38. } catch (IOException e) {
  39. // TODO Auto-generated catch block
  40. e.printStackTrace();
  41. }
  42. doc.getDocumentElement().normalize();
  43. Node updatePref = null;
  44. // NodeList nodes = doc.getChildNodes();
  45. NodeList nodes = doc.getElementsByTagName("window");
  46. for (int i = 0; i < nodes.getLength(); i++) {
  47. Node mynode = nodes.item(i);
  48. System.out.println("Property = " + mynode.getNodeName());
  49. if (mynode.getNodeType() == Node.ELEMENT_NODE) {
  50. Element myelement = (Element) mynode;
  51. System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
  52. System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
  53. System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
  54. updatePref = myelement.getElementsByTagName("fullscreen").item(0);
  55. updatePref.setTextContent("2");
  56. System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
  57. } // close if
  58. }// close for
  59. // write DOM back to the file
  60. TransformerFactory transformerFactory = TransformerFactory.newInstance();
  61. Transformer xtransform;
  62. DOMSource mydom = new DOMSource(doc);
  63. StreamResult streamResult = new StreamResult(configFile);
  64. try {
  65. xtransform = transformerFactory.newTransformer();
  66. xtransform.transform(mydom, streamResult);
  67. } catch (TransformerException e) {
  68. e.printStackTrace();
  69. }
  70. } // close method
  71. } //close class