简述
在Java生态圈子里,解析xml的库不要太多,但大多数博客的作者们都推荐一个叫dom4j的库去做这个事情,它的功能最强大最全,也使用最多、当然也最复杂,在这里我因为碰上了需要解析Mybaits mapper xml文件的需求,故作此文以记录。
最终目的: 制作出解析XML的转换工具,使写满Mysql SQL语句的mapper文件,能够通过此工具转换成其他数据库SQL语法的xml文件
Dom4j的安装
dom4j官页:dom4j
dom4j-Api文档:Overview (dom4j 2.1.4 API)
Dom4j主要内容简述:
- Document 文档 - 代表整个 XML 文档。 Document 对象通常称为 DOM 树。
- Element 元素 - 表示 XML 元素。元素对象具有操作其子元素、文本、属性和命名空间的方法。
- Attribute 属性 - 表示元素的属性。属性具有获取和设置属性值的方法。它具有父类型和属性类型。
- Node 节点 - 表示元素、属性或处理指令。
常见的Dom4j方法描述:
-
SAXReader.read(xmlSource)() − 从 XML 源构建 DOM4J 文档。
-
Document.getRootElement() − 获取 XML 文档的根元素。
-
Element.node(index) − 获取元素中特定索引处的 XML 节点。
-
Element.attributes() − 获取元素的所有属性。
-
Node.valueOf(@Name) − 获取具有给定元素名称的属性值。
安装在项目中
maven:
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.4</version>
</dependency>
gradle/KT:
implementation("org.dom4j:dom4j:2.1.4")
Dom4J的XML解析器
SAXReader解析器
接下来使用Dom4j的SAXReader创建解析器,在这里使用hutool.cn提供的工具类ResourceUtil去读取目录下的xml文件
SAXReader reader = new SAXReader();
String xmlString = ResourceUtil.readStr("conversion/TestMain.xml", StandardCharsets.UTF_8);
Document document = reader.read(new ByteArrayInputStream(xmlString.getBytes(StandardCharsets.UTF_8)));
三行代码分别代表:创建解析器 => 获取xml文件 => 解析器解析XML内容
具体使用方法
获取
把整个XML文件解析成Dom4j的 Element对象
获取Root
Element root = document.getRootElement();
获取所有子元素
List<Element> childElements = root.elements()
获取指定子元素-传入参数,参数为指定元素名
List<Element> childSelectElement = root.elements("select");
获取元素的各种内容
1. log.info("Name: {}", childElement.getName());
2. log.info("Text: {}", childElement.getText());
3. log.info("TextTrim: {}", childElement.getTextTrim());
4. log.info("XML: {}", childElement.asXML());
5. log.info("Data: {}", childElement.getData());
6. log.info("Parent: {}", childElement.getParent());
7. log.info("Path: {}", childElement.getPath());
8. log.info("UniquePath: {}", childElement.getUniquePath());
1.这一行获取并记录了当前元素的名称。例如,如果元素是 <Select>,它会输出 “Select”。
2.这一行获取并记录了当前元素的文本内容,包括子元素的文本和任何空白字符(如空格、换行符等)。
3.这一行获取并记录了当前元素的文本内容,但是去除了前后的空白字符。
4.这一行获取并记录了当前元素的XML字符串表示,包括其开始标签、属性、文本内容和结束标签。相当于获取了一个完整的XML
5.这一行获取并记录了当前元素的数据内容。这个方法通常用于获取元素的文本内容,但与getText()和getTextTrim()的区别可能依赖于具体的库实现。
6.这一行获取并记录了当前元素的父元素。如果当前元素是根元素,这个方法可能返回null。父元素和当前元素一样也是Element类型,方法与当前元素均相同
7.这一行获取并记录了当前元素的路径。这个路径是从根元素到当前元素的相对路径。
8.这一行获取并记录了当前元素在文档中的唯一路径。这个路径通常包括每个元素的索引,确保了即使是同名的兄弟元素,路径也是唯一的。
获取元素的属性
根据传入的参数获取元素属性的值
String elementId = childElement.attributeValue("id")
获取元素的属性对象 ,然后获取属性的文本和属性的值
Attribute attribute = childElement.attribute("attributeName");
if (attribute != null) {
String attributeText = attribute.getText();
String value = attribute.getValue();
}
获取元素的所有属性值,形成一个List
List<Attribute> attributes = childElement.attributes();
for (Attribute attribute : attributes) {
String name = attribute.getName();
String value = attribute.getValue();
// 处理属性
}
检查元素是否具备这个属性
boolean hasAttribute = childElement.hasAttribute("attributeName");
检查
检查子元素
selectSingleNode("subelementName");
检查属性
valueOf("@attributeName");
循环遍历元素中的子元素,检查子元素
Set<String> elementsToCheck = Set.of("select", "insert", "update", "delete");
for (Element childElement : childElements) {
if (!elementsToCheck.contains(childElement.getName())) {
log.error("未发现SQL语句元素");
}
}
新增
在childElement中添加了一个<if></if>子元素
Element element = childElement.addElement("if");
在子元素中设置内容:<if> and studentId = 12312</if>
element.setText(" and studentId = 12312");
为元素添加属性并设置值
thisElement.addAttribute("name", "sitinspring");
为一个元素添加一个子元素,并设置完属性和内容
supercarElement.addElement("carname")
.addAttribute("type", "Ferrari 101")
.addText("Ferrari 101");
修改
修改元素的某个属性的值
childElement.setAttribute("attributeName", "attributeValue");
修改元素的内容
在这里它会清除掉旧的内容,用新的内容填补
elementToModify.setText("新的内容");
删除
删除元素中的某个子元素(连同它的一切都删除)
thisElement.remove(childElment);
删除元素的某个属性
Attribute attribute=root.attribute("size");
root.remove(attribute);
解析Mybatis mapper xml的思路
不同场景,使用Dom4j解析的难度是不同的,越是解析复杂的XML内容,越是需要好的设计,Mybatis的mapper-xml 里面一般都写的是SQL,以及夹着一些XML,目的是实现动态加载参数以及动态拼接一些条件,那么解析起来就会有一些麻烦,以下提供一个思路,未必完美,但解决了一些Dom4j解析中会出现的问题
问题简述:
一个元素的内容文本中又包含着其他的元素,而其他的元素中又包含着内容,但彼此的内容是链接在一起的,例如Myabtis mapper-xml中语句都夹着xml,那该怎么办?
解决方法简述:
<select id="queryDataScale" resultType="java.util.Map">
select t.${idColumn} as id , t.${nameColumn} as name, case when t2.${idColumn} is null then 0 else 1 end as has_pms
from ${tableName} t
left join (
select ${idColumn} from ${tableName} where ${idColumn} in
<foreach collection="ids" open="(" close=")" item="item" separator="," >
#{item}
</foreach>
and ${scaleSql}
) t2 on t.${idColumn} = t2.${idColumn}
where t.${idColumn} in
<foreach collection="ids" open="(" close=")" item="item" separator="," >
#{item}
</foreach>
</select>
这里是一个Mybatis mapper-xml内容的一个案例,我们把它看成一个整体,其中select是整个内容的父级,无论它内容中的xml嵌套多少层,最外面都是它本身,所以我们需要一个循环,逐行获得它的节点内容
代码总结:
Element root = document.getRootElement();
List<Element> childElements = root.elements();
for (Element childElement : childElements) {
List<Element> thisElements = root.elements(childElement.getName());
for (Element select : thisElements) {
//这里相当于进入了Select标签元素内部
List<Node> selectChildElements = select.content();
for (Node child : selectChildElements) {
switch (child.getNodeType()) {
case Node.ELEMENT_NODE:
Element element = (Element) child;
log.info("Element: {}", element.getName());
break;
case Node.TEXT_NODE:
Text textNode = (Text) child;
log.info("Text: {}", textNode.getText());
break;
case Node.COMMENT_NODE:
Comment commentNode = (Comment) child;
// 添加注释
log.info("Comment: {}", commentNode.getText());
break;
default:
break;
}
}
}
}
以上对整个XML做了一个遍历,第一个for循环是对xml总体做了一个遍历,而内部则是开始获取<select>的内容,这里通过 select.content() 获取了一个List<Node>,之所以拿到Node的集合,是因为这里我们不光要拿Element,而是对所有的内容,包括文本甚至注释也要拿到,然后遍历Node的集合,通过NodeType对内容做区分,最后可以通过Node.Text_NODE,按顺序逐行拿到所有的文本内容
写入新文件
创建OutputFormat对象,它用于格式化输出的内容,以下设置了缩进、换行和编码格式,具体更多format属性配置:OutputFormat (dom4j API)
最终通过XMLWriter ,将document(变量)的内容写入到了指定的文件中
OutputFormat format = OutputFormat.createPrettyPrint();
format.setExpandEmptyElements(true);
format.setIndent(true);
format.setIndentSize(4);
format.setLineSeparator("\n");
format.setNewlines(true);
format.setEncoding("UTF-8");
format.isXHTML();
// 创建XMLWriter对象,将文档写入文件
XMLWriter writer = new XMLWriter(new FileWriter(new File("src/main/resources/result/text.xml")).getOutputStream(), format);
writer.write(document);
writer.close();