java使用dom4j实现xml文件和对象之间的相互转换

  • 整体描述
  • 两种方式的对比
  • 具体实现
  • 1. 引入dom4j的maven
  • 2.xml文件
  • 3. 创建节点名称的类
  • 4. 创建xml文件读写操作类
  • 5. 解析操作
  • 结语


整体描述

之前写过一篇文章,使用xstream将xml文件转成对象,文章链接:java使用xstream实现xml文件和对象之间的相互转换,使用起来很简单,但是有个问题,就是这种方法只能处理已知的xml中的字段,然后提前将对象创建处理,如果xml中有一些我们不能提前获知的字段,使用这种方法转成对象之后,在反转回xml,那些在对象中没有创建的字段就会丢失。
这里我们使用dom4j的方法,直接操作xml文件,就能避免这种问题。但是两种方法各有优劣,使用的时候可以根据情况进行选择。

两种方式的对比

在这对上面两种解析xml的方式做一个简单对比:

解析方式

xstream

dom4j

优点

解析操作简单,代码复用性强 ,存入数据库操作方便

可以直接操作xml文件,不回造成字段丢失

缺点

对xml中未知字段无法处理

解析操作相对复杂,代码复用性低,同时操作文件问题

具体实现

1. 引入dom4j的maven

添加xstream的依赖

<!-- dom4j xml操作-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>

2.xml文件

这里还是使用的是大疆无人机的地图文件template.kml :
xml文件参考文档:大疆云上API地址: 大疆云上API。
文件具体格式如下,基本包含了xml所有需要使用的示例。
注:文件虽然是.kml格式,但其实就是xml的格式。

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.0">
<Document>

  <!-- Step 1: Implement File Creation Information -->
  <wpml:author>Name</wpml:author>
  <wpml:createTime>1637600807044</wpml:createTime>
  <wpml:updateTime>1637600875837</wpml:updateTime>
 
  <!-- Step 2: Setup Mission Configuration -->
  <wpml:missionConfig>
    <wpml:flyToWaylineMode>safely</wpml:flyToWaylineMode>
    <wpml:finishAction>goHome</wpml:finishAction>
    <wpml:exitOnRCLost>goContinue</wpml:exitOnRCLost>
    <wpml:takeOffSecurityHeight>20</wpml:takeOffSecurityHeight>
    <wpml:globalTransitionalSpeed>10</wpml:globalTransitionalSpeed>
    <wpml:droneInfo>
      <!-- Declare drone model with M30 -->
      <wpml:droneEnumValue>67</wpml:droneEnumValue>
      <wpml:droneSubEnumValue>0</wpml:droneSubEnumValue>
    </wpml:droneInfo>
    <wpml:payloadInfo>
      <!-- Declare payload model with M30 -->
      <wpml:payloadEnumValue>52</wpml:payloadEnumValue>
      <wpml:payloadSubEnumValue>0</wpml:payloadSubEnumValue>
      <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex>
    </wpml:payloadInfo>
  </wpml:missionConfig>
 
  <!-- Step 3: Setup A Folder for Waypoint Template -->
  <Folder>
    <wpml:templateType>waypoint</wpml:templateType>
    <wpml:useGlobalTransitionalSpeed>0</wpml:useGlobalTransitionalSpeed>
    <wpml:templateId>0</wpml:templateId>
    <wpml:waylineCoordinateSysParam>
      <wpml:coordinateMode>WGS84</wpml:coordinateMode>
      <wpml:heightMode>EGM96</wpml:heightMode>
      <wpml:ellipsoidHeight>100</wpml:ellipsoidHeight>
      <wpml:height>100</wpml:height>
      <wpml:positioningType>GPS</wpml:positioningType>
    </wpml:waylineCoordinateSysParam>
    <wpml:autoFlightSpeed>7</wpml:autoFlightSpeed>
    <wpml:transitionalSpeed>7</wpml:transitionalSpeed>
    <wpml:gimbalPitchMode>usePointSetting</wpml:gimbalPitchMode>
    <wpml:globalWaypointHeadingParam>
      <wpml:waypointHeadingMode>followWayline</wpml:waypointHeadingMode>
    </wpml:globalWaypointHeadingParam>
    <wpml:globalWaypointTurnMode>toPointAndStopWithDiscontinuityCurvature</wpml:globalWaypointTurnMode>
    <Placemark>
      <Point>
        <!-- Fill longitude and latitude here -->
        <coordinates>
          longitude,latitude
        </coordinates>
      </Point>
      <wpml:index>0</wpml:index>
      <wpml:ellipsoidHeight>90.2</wpml:ellipsoidHeight>
      <wpml:height>100</wpml:height>
      <wpml:useGlobalHeight>1</wpml:useGlobalHeight>
      <wpml:useGlobalSpeed>1</wpml:useGlobalSpeed>
      <wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam>
      <wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam>
      <wpml:gimbalPitchAngle>0</wpml:gimbalPitchAngle>
    </Placemark>
    <Placemark>
      <Point>
        <!-- Fill longitude and latitude here -->
        <coordinates>
          longitude,latitude
        </coordinates>
      </Point>
      <wpml:index>1</wpml:index>
      <wpml:ellipsoidHeight>90.2</wpml:ellipsoidHeight>
      <wpml:height>100</wpml:height>
      <wpml:useGlobalHeight>1</wpml:useGlobalHeight>
      <wpml:useGlobalSpeed>1</wpml:useGlobalSpeed>
      <wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam>
      <wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam>
      <wpml:gimbalPitchAngle>0</wpml:gimbalPitchAngle>
      <!-- Declare action group for waypoint 1# -->
      <wpml:actionGroup>
        <wpml:actionGroupId>0</wpml:actionGroupId>
        <wpml:actionGroupStartIndex>1</wpml:actionGroupStartIndex>
        <wpml:actionGroupEndIndex>1</wpml:actionGroupEndIndex>
        <wpml:actionGroupMode>sequence</wpml:actionGroupMode>
        <wpml:actionTrigger>
          <wpml:actionTriggerType>reachPoint</wpml:actionTriggerType>
        </wpml:actionTrigger>
        <!-- Declare the 1st action: rotate gimbal -->
        <wpml:action>
          <wpml:actionId>0</wpml:actionId>
          <wpml:actionActuatorFunc>gimbalRotate</wpml:actionActuatorFunc>
          <wpml:actionActuatorFuncParam>
            <wpml:gimbalRotateMode>absoluteAngle</wpml:gimbalRotateMode>
            <wpml:gimbalPitchRotateEnable>0</wpml:gimbalPitchRotateEnable>
            <wpml:gimbalPitchRotateAngle>0</wpml:gimbalPitchRotateAngle>
            <wpml:gimbalRollRotateEnable>0</wpml:gimbalRollRotateEnable>
            <wpml:gimbalRollRotateAngle>0</wpml:gimbalRollRotateAngle>
            <wpml:gimbalYawRotateEnable>1</wpml:gimbalYawRotateEnable>
            <wpml:gimbalYawRotateAngle>30</wpml:gimbalYawRotateAngle>
            <wpml:gimbalRotateTimeEnable>0</wpml:gimbalRotateTimeEnable>
            <wpml:gimbalRotateTime>0</wpml:gimbalRotateTime>
            <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex>
          </wpml:actionActuatorFuncParam>
        </wpml:action>
        <!-- Declare the 2nd action: take photo -->
        <wpml:action>
          <wpml:actionId>1</wpml:actionId>
          <wpml:actionActuatorFunc>takePhoto</wpml:actionActuatorFunc>
          <wpml:actionActuatorFuncParam>
            <wpml:fileSuffix>point1</wpml:fileSuffix>
            <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex>
          </wpml:actionActuatorFuncParam>
        </wpml:action>
      </wpml:actionGroup>
    </Placemark>
  </Folder>
 
</Document>
</kml>

3. 创建节点名称的类

/**
 * 航线文件常量
 *
 * @author nhx
 * @date 2022-08-22
 */
public interface WaylineConstant {

    /**
     * Document
     */
    String Document = "Document";

    /**
     * Folder
     */
    String Folder = "Folder";

    /**
     * Placemark
     */
    String Placemark = "Placemark";

    /**
     * actionGroup
     */
    String actionGroup = "actionGroup";

    /**
     * index
     */
    String index = "index";

    /**
     * action
     */
    String action = "action";

    /**
     * actionActuatorFunc
     */
    String actionActuatorFunc = "actionActuatorFunc";

    /**
     * actionActuatorFuncParam
     */
    String actionActuatorFuncParam = "actionActuatorFuncParam";

    /**
     * takePhoto
     */
    String takePhoto = "takePhoto";

    /**
     * wpml:fileSuffix
     */
    String addFileSuffix = "wpml:fileSuffix";

    /**
     * fileSuffix
     */
    String fileSuffix = "fileSuffix";

    /**
     * fileSuffix
     */
    String actionGroupStartIndex = "actionGroupStartIndex";

    /**
     * fileSuffix
     */
    String actionGroupEndIndex = "actionGroupEndIndex";

}

4. 创建xml文件读写操作类

此处对读写xml文件单独封装一个方法,在其中对xml文件修改保存时进行了一些格式设置,具体详见代码。

/**
     * 将Document对象写入到xml文件
     *
     * @param document    写入的Document
     * @param fileNameDec 写入文件路径
     * @param append      是否追加写入,true表示不覆盖原来的内容,而是加到文件的后面
     */
    public static void documentToFile(Document document, String fileNameDec, boolean append) {
        try {
            OutputFormat outputFormat = OutputFormat.createPrettyPrint();
            // 设置XML编码方式,即是用指定的编码方式保存XML文档到字符串(String),这里也可以指定为GBK或是ISO8859-1
            outputFormat.setEncoding("UTF-8");
            //outputFormat.setSuppressDeclaration(true); //是否生产xml头
            // 设置是否缩进
            outputFormat.setIndent(true);
            // 以两个空格方式实现缩进
            outputFormat.setIndent("  ");
            // 设置是否换行
            outputFormat.setNewlines(true);
            // xml字符串
            StringWriter stringWriter = new StringWriter();
            // xmlWriter是用来把XML文档写入字符串
            XMLWriter xmlWriter = new XMLWriter(stringWriter, outputFormat);
            // 把创建好的XML文档写入字符串
            xmlWriter.write(document);
            //写入文件
            writerFile(stringWriter.toString(), fileNameDec, append);
        } catch (Exception e) {
            log.error("documentToFile:{}", e.toString());
            e.printStackTrace();
        }
    }


    /**
     * 将字符串写入指定路径的文件中
     *
     * @param encryptText 写入字符串
     * @param fileNameDec 写入文件路径
     * @param append      是否追加写入,true表示不覆盖原来的内容,而是加到文件的后面
     */
    public static void writerFile(String encryptText, String fileNameDec, boolean append) {
        FileWriter writer = null;
        try {
            writer = new FileWriter(fileNameDec, append);
            writer.write(encryptText);
            writer.flush();
            writer.close();
        } catch (IOException e) {
            log.error("writerFile:{}", e.toString());
            e.printStackTrace();
        }
    }

5. 解析操作

使用dom4j这种方式,就需要用代码进行逐层读取,这就是代码复用性差的原因,因为一旦xml结构变了,解析的代码需要重新写。

/**
     * xml解析测试方法
     *
     * @param filePath 文件路径
     */
    public static void testXml(String filePath) {
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(filePath);
            if (Objects.isNull(document)) {
                return;
            }
            Element root = document.getRootElement();
            if (Objects.isNull(root)) {
                return;
            }
            Element doc = root.element(WaylineConstant.Document);
            if (Objects.isNull(doc)) {
                return;
            }
            Element folder = doc.element(WaylineConstant.Folder);
            if (Objects.isNull(folder)) {
                return;
            }
            List<Element> elementList = folder.elements(WaylineConstant.Placemark);
            if (Objects.isNull(elementList) || elementList.size() <= 0) {
                return;
            }
            for (Element placemark : elementList) {
                // 循环航点
                Element actionGroup = placemark.element(WaylineConstant.actionGroup);
                if (Objects.isNull(actionGroup)) {
                    continue;
                }
                String index = placemark.element(WaylineConstant.index).getText();
                List<Element> actionList = actionGroup.elements(WaylineConstant.action);
                if (Objects.isNull(actionList) || actionList.size() <= 0) {
                    continue;
                }
                for (Element action : actionList) {
                    // 循环航点动作
                }
            }
            // 写入文件
            FileUtils.documentToFile(document, filePath, false);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

结语

至此,解析和修改参数的工作就完成了,还需要注意的,由于直接对xml文件进行操作,如果有同时操作同一个航线文件的情况,会有问题,需要加一个线程锁或者文件锁之类的判断。