Java解析XML时处理CDATA节点需要根据不同的解析方式采用对应的处理逻辑,CDATA节点内的内容会被XML解析器当作纯文本处理,不会解析其中的特殊字符,这是它和普通文本节点最大的区别。

什么是CDATA节点
CDATA全称是Character Data,是XML中用来存储不需要解析的文本内容的一种节点类型。当XML内容中包含大量特殊字符比如<、>、&时,使用CDATA可以避免手动转义这些字符,提升内容的可读性。CDATA节点的语法格式是<![CDATA[内容]]>,解析器会直接将中括号里的内容作为纯文本返回。
DOM解析方式处理CDATA节点
DOM解析会把整个XML文档加载到内存中形成树形结构,CDATA节点在DOM中会被识别为CDATA_SECTION_NODE类型的节点,我们可以通过节点的类型判断来获取CDATA内容。
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.ByteArrayInputStream;
public class DomCdataParse {
public static void main(String[] args) throws Exception {
String xml = "<?xml version="1.0" encoding="UTF-8"?>" +
"<root>" +
"<content><![CDATA[<div>这是一段包含HTML标签的内容</div>]]></content>" +
"</root>";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new ByteArrayInputStream(xml.getBytes()));
// 获取content节点
Node contentNode = document.getElementsByTagName("content").item(0);
// 遍历content节点的子节点
NodeList childNodes = contentNode.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
// 判断节点类型是否为CDATA节点
if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
System.out.println("CDATA节点内容:" + node.getNodeValue());
} else if (node.getNodeType() == Node.TEXT_NODE) {
// 处理普通文本节点的情况
String text = node.getNodeValue().trim();
if (!text.isEmpty()) {
System.out.println("普通文本节点内容:" + text);
}
}
}
}
}
SAX解析方式处理CDATA节点
SAX是事件驱动的解析方式,默认情况下SAX会把CDATA节点的内容拆分成多次characters方法的调用,我们需要开启CDATA处理开关,并重写startCDATA和endCDATA方法来拼接完整的CDATA内容。
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.*;
import java.io.ByteArrayInputStream;
public class SaxCdataParse {
public static void main(String[] args) throws Exception {
String xml = "<?xml version="1.0" encoding="UTF-8"?>" +
"<root>" +
"<content><![CDATA[<div>这是SAX解析的CDATA内容</div>]]></content>" +
"</root>";
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(new ByteArrayInputStream(xml.getBytes()), new CDataHandler());
}
static class CDataHandler extends DefaultHandler {
private StringBuilder cdataContent = new StringBuilder();
private boolean isInCdata = false;
@Override
public void startCDATA() throws SAXException {
// 进入CDATA节点时标记状态
isInCdata = true;
cdataContent.setLength(0);
}
@Override
public void endCDATA() throws SAXException {
// 离开CDATA节点时输出内容
System.out.println("SAX解析到的CDATA内容:" + cdataContent.toString());
isInCdata = false;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 如果在CDATA节点内,拼接内容
if (isInCdata) {
cdataContent.append(ch, start, length);
}
}
}
}
StAX解析方式处理CDATA节点
StAX是拉式解析方式,开发者可以主动控制解析流程,通过判断事件类型是否为XMLStreamConstants.CDATA来获取CDATA节点的内容。
import javax.xml.stream.*;
import java.io.ByteArrayInputStream;
public class StaxCdataParse {
public static void main(String[] args) throws Exception {
String xml = "<?xml version="1.0" encoding="UTF-8"?>" +
"<root>" +
"<content><![CDATA[<div>这是StAX解析的CDATA内容</div>]]></content>" +
"</root>";
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new ByteArrayInputStream(xml.getBytes()));
while (reader.hasNext()) {
int event = reader.next();
// 判断事件类型是否为CDATA
if (event == XMLStreamConstants.CDATA) {
System.out.println("StAX解析到的CDATA内容:" + reader.getText());
}
}
reader.close();
}
}
不同解析方式的注意事项
- DOM解析适合XML文档较小、需要随机访问节点的场景,处理CDATA时只需要判断节点类型即可,逻辑简单但内存占用较高。
- SAX解析适合大文件解析,内存占用低,但需要手动维护CDATA的状态标记,避免内容拼接错误。
- StAX解析可以灵活控制解析流程,处理CDATA时直接判断事件类型即可,代码可读性较好,是现在比较推荐的中小型XML解析方式。
- 如果XML中CDATA节点和普通文本节点混合出现,需要注意区分节点类型,避免把普通文本误当作CDATA内容处理。
常见问题解答
为什么解析后CDATA内容丢失?
最常见的原因是解析时没有开启对应的CDATA处理开关,比如SAX解析默认不会触发CDATA相关事件,需要重写startCDATA和endCDATA方法。另外如果XML中CDATA节点的格式不正确,比如缺少结束的]]>,也会导致解析异常内容丢失。
CDATA节点里可以嵌套CDATA吗?
不可以,XML规范规定CDATA节点内部不能再包含CDATA的结束标记]]>,如果需要存储包含该标记的内容,只能手动将内容拆分成多个CDATA节点,或者在存储前对内容进行转义处理。
如何处理CDATA里的换行和空格?
CDATA节点内的换行和空格都会被原样保留,解析获取到的内容会包含原始的换行和空格,如果需要格式化内容,可以在获取到内容后自行处理,比如使用trim方法去除首尾空格,或者替换换行符。