Java-XML的解析与生成

何华灿
2023-12-01

XML解析


零、创建一个XML文件

如果学过HTML,那么这就看着很轻松了,就是由一个一个标签组成的,开头的第一行是版本和编码的声明,一般不会改变。这里我们创建一个简单的books.xml文件。

<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book id="1">
        <name>老人与海</name>
        <author>海明威</author>
    </book>
    <book id="2">
        <name>茶花女</name>
        <author>小仲马</author>
    </book>
</books>

一、使用DOM解析XML节点

解析XML文件需要使用几个类,我们直接通过代码来看一下。

1、加载解析XML文件

先创建一个DocumentBuilderFactory对象,然后通过该对象的newDocumentBuilder()方法创建一个DocumentBuilder对象,这里需要捕获一下异常。然后通过该对象的parse()方法解析XML文件,该方法接收一个文件路径的参数(默认项目根目录)。

import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document document = db.parse("books.xml");
}catch (Exception e){
    e.printStackTrace();
}

2、获取节点属性

这里我们要获取每一个book节点,接下来的方法就js的DOM操作差不多了。获取到的所有节点对象一个是NodeList类型。我们可以通过该对象的getLength()方法获取到该节点的个数。

NodeList bookList = document.getElementsByTagName("book");
System.out.println(bookList.getLength()); // 2

以上获取到的是一个节点的集合,我们可以通过遍历来获取每一个节点。这里用到的是NodeList对象的item()方法。

for(int i = 0; i < bookList.getLength(); i++){
    Node book =  bookList.item(i);
}

上面的xml文件中的每个book节点都有一个id属性,我们来获取它。这里有两种常见的情况,在属性名未知的时候,可以先获取所有属性再进行遍历:

NamedNodeMap attrs = book.getAttributes();
for (int j = 0; j < attrs.getLength(); j++){
   Node attr = attrs.item(j);
   System.out.println(attr.getNodeName() + ":" + attr.getNodeValue());
}

getAttributes()方法获取所有属性是一个NamedNodeMap 类型的对象,然后她也有getLength()方法和item()方法,由此可以获取到该节点的每一个属性,是一个Node类型的对象。最后通过该对象的对应方法获取属性名和属性值。

感觉好繁琐呀,如果我已经知道这个节点有一个id属性呢?通过节点集合的item方法获取到一个Element类型的对象,这里需要强转一下类型。直接给该对象的getAttribute()方法传递一个属性名参数就可以获取到其属性值。

Element book  = (Element) bookList.item(i);
String attrValue = book.getAttribute("id");
System.out.println(attrValue);

3、获取节点的子节点

每一个book节点又有子节点,我们来看一下这么获取:通过节点的getChildNodes()方法获取子节点集合对象。同样通过item()方法获取每一个子节点。

NodeList childNodes = book.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++){
    System.out.println(childNodes.item(j).getNodeName()); //打印每一个子节点名
}

这里注意,空格会被当做是Text类型的节点。打印的节点名永远都是#text。

节点名称NodeTypenodeName的返回值nodeValue的返回值
Element1节点的标签名null
Attr2属性名称属性值
Text3#text节点内容

所以这里我们通过节点类型来判断一下,以便我们获取到真正的标签节点。

// 只要Element 类型的节点
if(childNodes.item(j).getNodeType() == Node.ELEMENT_NODE){
    // ...
}

4、获取节点的内容

这里也有两种常见的情况,节点中文本内容前没有其他子节点的时候:通过getFirstChild()获取到第一个节点(其实就是文本节点),然后getNodeValue()得到文本节点的内容。这种方法算是碰巧能取到内容。

String text = childNodes.item(j).getFirstChild().getNodeValue();

如果节点中还有节点怎么,比如上面的name节点改一下:<name><a>书名:</a>老人与海</name>。可以通过getTextContent()方法获取节点下的所有文本内容。直接把标签节点忽略。

String text2 = childNodes.item(j).getTextContent();
System.out.println(text2); //书名:老人与海

二、SAX解析xml文件

SAX解析需要先创建一个继承于DefaultHandler的类。通过重写方法来对XML文件进行解析。

public class SAXHandler extends DefaultHandler {
    //遍历开始标签
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
    }
    //遍历结束标签
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
    }

    //用来标识解析开始
    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
        System.out.println("解析开始了");
    }
    //标识解析结束
    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }
    // 可以用来获取节点中的文本
    @Override
    public void characters(char[] ch, int start, int length)   throws SAXException {
        super.characters(ch, start, length);
    }
}

1、解析属性

我们在遍历开始标签的startElement方法里,来解析节点。参数qName就是标签名,attributes是所有的属性。

// 如果开始解析book元素
if(qName.equals("book")){
    // 获取指定属性的值
    String value = attributes.getValue("id");
    System.out.println(value);

    // 遍历属性,获取属性名和属性值
    for(int i = 0; i < attributes.getLength(); i++){
        System.out.println(attributes.getQName(i));
        System.out.println(attributes.getValue(i));
    }
}

2、解析节点的文本内容

在characters方法中,第一个参数其实是该文件的所有内容,第二个参数是在解析完一个开始标签之后的位置,第三个参数是开始标签之后到结束标签之前的长度。由此我们可结合这三个参数获取到节点的文本内容。

String text = new String(ch, start, length);
// 通过判断过滤一下空格文本节点
if (!text.trim().equals("")){
    System.out.println(text); //节点中的文本
}

创建实例调用方法

然后我们在实际代码中,创建newSAXParser对象,通过的parse方法进行解析,第一个参数是文件路径,第二个参数就是一个刚才创建的SAXHandler类的实例。

SAXParserFactory spf  = SAXParserFactory.newInstance();
try {
    SAXParser sp =  spf.newSAXParser();
    SAXHandler sh = new SAXHandler();
    sp.parse("books.xml", sh);
} catch (ParserConfigurationException e) {
    e.printStackTrace();
} catch (SAXException e) {
    e.printStackTrace();
}
catch (IOException e) {
    e.printStackTrace();
}

三、JDOM解析

JDOM并不是官方提供的方法,所以需要手动添加jar包。通过build方法解析的时候可以传入一个输入流。然后其他就不赘述了,思想都一样,直接代码一看就明白。

1、属性的解析

SAXBuilder saxBuilder = new SAXBuilder();
try {
    FileInputStream fis = new FileInputStream("books.xml");
    Document document = saxBuilder.build(fis);
    // 获取根节点
    Element rootElement = document.getRootElement();
    // 获取根节点下子节点的集合
    List<Element> bookList = rootElement.getChildren();
    // 遍历节点
    for (Element book: bookList) {
        List<Attribute> attributes = book.getAttributes();
        // 遍历属性
        for (Attribute attr: attributes) {
            System.out.println(attr.getName() + ":" + attr.getValue());
        }
        //通过属性名获取属性值
        book.getAttributeValue("id");
    }
} catch (JDOMException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

2、子节点的解析

通过getChildren获取子节点,然后通过getName()获取节点名,getValue获取节点中的文本内容。

List<Element> bookChilds = book.getChildren();
for (Element ele : bookChilds) {
    System.out.println(ele.getName()+":"+ele.getValue());
}
 
name:老人与海
author:海明威
name:茶花女
author:小仲马

3、补充:乱码问题

如果解析碰到乱码,先修改xml文件的编码格式,如果还是达不到自己想要的效果,我们可以通过在创建输入流的时候指定编码格式。

4、存到java对象

首先创建一个book对象,get和set方法就不写了。

public class Book {
    private String id;
    private String author;
    private String name;
    private String isHot;
}

然后在遍历属性和子节点的时候,将对应的值添加到Book对象中,遍历完一次之后添加到一个List中,然后清空进行下一次遍历。

ArrayList<Book> booksList = new ArrayList<Book>();
SAXBuilder saxBuilder = new SAXBuilder();
try {
    Document document = saxBuilder.build("books.xml");
    // 获取根节点
    Element rootElement = document.getRootElement();
    // 获取根节点下子节点的集合
    List<Element> bookList = rootElement.getChildren();
    // 遍历节点
    Book b = new Book();
    for (Element book: bookList) {
        List<Attribute> attributes = book.getAttributes();
        for (Attribute attr : attributes) {
            if (attr.getName() == "id"){
                b.setId(attr.getValue());
            }
            if (attr.getName() == "isHot"){
                b.setIsHot(b.getIsHot());
            }
        }
        List<Element> bookChilds = book.getChildren();
        for (Element ele : bookChilds) {
            if (ele.getName() == "author"){
                b.setAuthor(ele.getValue());
            }
            if(ele.getName() == "name"){ 
                b.setName(ele.getValue());
            }
        }
        booksList.add(b);
        b = null;
    }
    System.out.println(booksList);
} catch (JDOMException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

四、DOM4J解析

同样需要手动添加包到项目。

1、属性解析

这里不同的是从根节点下获取到一个book节点的遍历器。

SAXReader reader = new SAXReader();
// 加载xml文件,获取document对象
try {
    Document document = reader.read(new File("books.xml"));
    // 获取根节点
    Element books = document.getRootElement();
    // 获取迭代器
    Iterator it = books.elementIterator();
    while (it.hasNext()){
        Element book = (Element)it.next();
        List<Attribute> bookAttrs = book.attributes();
        for(Attribute attr : bookAttrs){
            System.out.println(attr.getName() +":"+ attr.getValue());
        }
    }
} catch (DocumentException e) {
    e.printStackTrace();
}

2、解析子节点

上面从根节点获取到一个book子节点的遍历器,这里同样通过book节点的elementIterator方法获取到一个book节点下子节点的遍历器。

Iterator itt = book.elementIterator();
while (itt.hasNext()){
    Element bookChild = (Element) itt.next();
    System.out.println(bookChild.getName());
}

XML生成


一、DOM方法

首先和解析XML文件一样需要创建一些对象

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = null;
try {
    db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
    e.printStackTrace();
}
Document document = db.newDocument();

创建一个根节点和一个子节点

Element books = document.createElement("books");
Element book = document.createElement("book");

给节点添加属性:

book.setAttribute("id", "0");

给节点添加文本内容:

book.setTextContent("安娜卡列尼娜");

把子节点插入到根节点,并且把根节点插入到document树:

books.appendChild(book);
document.appendChild(books);

最后把document树写进xml文件中:

TransformerFactory tf = TransformerFactory.newInstance();
try {
    Transformer transformer = tf.newTransformer();
    // 把节点数写进文件中
    transformer.transform(new DOMSource(document), new StreamResult(new File("myBooks.xml")));
} catch (TransformerConfigurationException e) {
    e.printStackTrace();
}catch (TransformerException e) {
    e.printStackTrace();
}

SAX

。。。

DOM4J

这里先了解一下RSS,它是XML的一种典型应用。是一种描述和同步网站内容的格式。

// 创建document对象
Document document = DocumentHelper.createDocument();
// 创建根节点
Element rss = document.addElement("rss");
rss.addAttribute("version", "2.0");
XMLWriter writer = null;
try {
    writer = new XMLWriter(new FileOutputStream(new File("rss.xml")));
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
} catch (FileNotFoundException e) {
    e.printStackTrace();
}
try {
    writer.write(document);
    writer.close();
} catch (IOException e) {
    e.printStackTrace();
}
    }
}

此时我们已经成功创建了xml文件,

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"/>

那么接下我们继续创建子节点和内容,调用创建出来的节点对象的addElement就是创建子节点,setText方法是设置内容。

// 生成子节点和内容
Element channel = rss.addElement("channel");
Element title = channel.addElement("title");
title.setText("我是内容");

此时生成的文件还是没有一个很好的格式,我们来设置一下:

// 设置想xml格式(缩进、换行、编码)
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("GBK"); // 可以设置编码,默认是UTF-8
// 在XMLWriter的构造函数中,将format 对象当做第二个参数传入
writer = new XMLWriter(new FileOutputStream(new File("rss.xml")), format);

这样我们的xml文件的格式就非常美观了

<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0">
  <channel>
    <title>我是内容</title>
  </channel>
</rss>

需要注意,如果有特殊字符的情况下默认会给我们进行转义的,比如

title.setText("<我是内容");
// 最后生成时这样的
<title>&lt;我是内容</title>

想要禁止自动转义也很简单,调用writer对象的setEscapeText()方法设置为false就OK了。

writer.setEscapeText(false);

//这时生成的内容就是没有转义的
<title><我是内容</title>

JDOM

先创建根节点,添加到document。

// 创建根节点并添加属性,插入到document
Element rss = new Element("rss");
rss.setAttribute("version", "2.0");
Document document = new Document(rss);
// 将document对象转换成xml文档
XMLOutputter xo = null;
try {
    xo = new XMLOutputter();
} catch (Exception e) {
    e.printStackTrace();
}
try {
    xo.output(document, new FileOutputStream(new File("rssNew.xml")));
} catch (IOException e) {
    e.printStackTrace();
}

创建子节点及子节点的文本,

Element channel = new Element("channel");
rss.addContent(channel);
Element title = new Element("title");
title.setText("我是内容");
channel.addContent(title);

设置格式,也是创建一个format对象然后传入XMLOutputter的构造函数。

Format format = Format.getCompactFormat();
format.setIndent(""); //设置换行
format.setEncoding("GBK"); //设置编码

XMLOutputter xo = new XMLOutputter(format);
 类似资料: