Java实现对XML文档的校验

Java实现对XML文档的校验

1. xml的前身是标准通用标记语言,是自IBM从60年代就开始发展的通用标记语言,xml本身是一种格式规范,是一种包含了数据以及数据说明的文本格式规范。

2. 在平时写代码的时候,我们常常见到的xml文件一般就是spring的配置文件和mybatis的配置文件,而对于有些业务来说,也时常会用xml作为数据展示的一种标准形式,例如患者信息的展示会通过自定义节点,属性,属性值的形式,把个人信息以及关联的信息承载出来,对于Java操作xml文档的方式,一般来说,常用的是使用SAX解析和DOM解析来解析xml文档,然后遍历节点,获取节点的属性值,来获取xml中展示的信息,但是解析xml只是一种获取数据的方法,如何利用java语言判断xml文档的内容是否符合要求和规范,xml的语法是否符合xml语法格式又成了需要思考的问题,既然使用了xml来作为装载信息的一种呈现方式,那么肯定是需要符合xml的语法要求的,其次要符合相关的内容要求:

例如:

!--患者身份证号标识-->
<id root="1.16.156.122011.5.24" extension="4110821997****20**"/>

该节点要求展示的是患者的身份证号,在子节点extention中展示的就是该患者的身份证件号,不同的患者展示的身份证号不同,是根据业务获取的,但是不变的是root=“1.16.156.122011.5.24”,如果在展示的过程中 写成了root=“1.16.156.122011.5.23”,虽然语法上不会报错,但是内容却是不符合要求的,要提示出相关节点属性值错误的警示

以下给出了相关的逻辑供参考

1.场景:使用java校验xml语法是否符合标准

使用SAX解析在解析的过程中校验xml的语法格式,如果有误,就抛出SAXParseException

<!--maven依赖-->
<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>
package com.example.demo.test;
import com.example.demo.utils.JaxbUtil;
import com.example.demo.utils.XmlValidateUtils;
import lombok.SneakyThrows;
import org.dom4j.Document;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.SAXValidator;
import org.dom4j.io.XMLWriter;
import org.dom4j.util.XMLErrorHandler;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.io.InputStream;

public class demo4 {
    @SneakyThrows
    public static void main(String[] args) {
        String xmlFileName = "E:\\xml\\test.xml";
        String xsdFileName = "E:\\xml\\test.xsd";
        try {
            //创建默认的XML错误处理器
            XMLErrorHandler errorHandler = new XMLErrorHandler();
            //获取基于 SAX 的解析器的实例
            SAXParserFactory factory = SAXParserFactory.newInstance();

            //解析器在解析时验证 XML 内容。
            factory.setValidating(true);
            //指定由此代码生成的解析器将提供对 XML 名称空间的支持。
            factory.setNamespaceAware(true);
            //使用当前配置的工厂参数创建 SAXParser 的一个新实例。
            SAXParser parser = factory.newSAXParser();
            //创建一个读取工具
            SAXReader xmlReader = new SAXReader();
            //获取要校验xml文档实例
            Document xmlDocument = (Document) xmlReader.read(new File(xmlFileName));
            //设置 XMLReader 的基础实现中的特定属性。核心功能和属性列表可以在 [url]http://sax.sourceforge.net/?selected=get-set[/url] 中找到。
            parser.setProperty(
                    "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
                    "http://www.w3.org/2001/XMLSchema");
            parser.setProperty(

                    "http://java.sun.com/xml/jaxp/properties/schemaSource",
                    "file:" + xsdFileName);
            //创建一个SAXValidator校验工具,并设置校验工具的属性
            SAXValidator validator = new SAXValidator(parser.getXMLReader());
            //设置校验工具的错误处理器,当发生错误时,可以从处理器对象中得到错误信息。
            validator.setErrorHandler(errorHandler);
            //校验
            validator.validate(xmlDocument);

            XMLWriter writer = new XMLWriter(OutputFormat.createPrettyPrint());
            //如果错误信息不为空,说明校验失败,打印错误信息
            if (errorHandler.getErrors().hasContent()) {
                System.out.println("XML文件通过XSD文件校验失败!");
                writer.write(errorHandler.getErrors());
            } else {
                System.out.println("Good! XML文件通过XSD文件校验成功!");
            }
        } catch (Exception ex) {
            System.out.println("XML文件: " + xmlFileName + " 通过XSD文件:" + xsdFileName + "检验失败。\n原因: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
}

生成xsd文件可以使用Trang.jar 命令

java -jar Trang.jar xml文件名 生成的xsd文件名
例如
java -jar Trang.jar test.xml test.xsd

Trang.jar 的下载大家可以自行在网上搜索寻找

也可以使用IDEA自动生成xsd来生成

在这里插入图片描述在这里插入图片描述

2.场景:使用java校验xml内容是否和要求内容相同

1.首先获取到前端传递过来的待校验的xml内容,根据获取的xml内容生成临时xml文件
2.获取正确的xml文件,用来校验生成的xml文件的内容是否相同,例如:

//待校验的xml文件内容
<id root="1.16.156.122011.5.24" extension="4110821997****20**"/>
//用来校验的xml文件内容
<id root="1.16.156.122011.5.25" extension="4110821997****20**"/>

此时root="1.16.156.122011.5.24 和root=“1.16.156.122011.5.25” 不相同,由此推断出待校验的xml文档内容出现了错误

3.通过io流读取待校验的xml文档,在读取的过程中为每一行追加行号,并且同时用io流读取正确的xml文档,并为每一行追加行号
4.通过DOM解析器,解析两个xml文档,比较两个文档的接节点以及内容是否相同,如果不同,就获取行号,把报错信息展示出来

在这里插入图片描述部分参考代码如下:

//生成带有行号的临时文件
 public TempFileDTO createTempFile(String xmlContent, String xsdContent) throws IOException {
        File tempXmlUncheck = File.createTempFile("temp_uncheck", ".xml");
        File tempXmlUseCheck = File.createTempFile("temp_usecheck", ".xml");
        BufferedWriter xmlWriter = new BufferedWriter(new FileWriter(tempXmlUncheck));
        BufferedWriter xsdWriter = new BufferedWriter(new FileWriter(tempXmlUseCheck));
        xmlWriter.write(xmlContent);
        xmlWriter.flush();
        xmlWriter.close();
        xsdWriter.write(xsdContent);
        xsdWriter.flush();
        xsdWriter.close();
        BufferedReader bufferedReaderXmlUncheck = new BufferedReader(new FileReader(tempXmlUncheck.getAbsolutePath()));
        BufferedReader bufferedReaderXmlUsecheck = new BufferedReader(new FileReader(tempXmlUseCheck.getAbsolutePath()));
        File newxmlTempFile = File.createTempFile("uncheck", ".xml");
        File newxsdTemptFile = File.createTempFile("usecheck", ".xml");
        BufferedWriter bufferedWriterXmlUncheck = new BufferedWriter(new FileWriter(newxmlTempFile.getAbsolutePath()));
        BufferedWriter bufferedWriterXmlTUsecheck = new BufferedWriter(new FileWriter(newxsdTemptFile.getAbsolutePath()));
        String lineUncheck;
        int lineNumberUncheck = 0;
        StringBuilder builderUncheck = new StringBuilder();
        while ((lineUncheck = bufferedReaderXmlUncheck.readLine()) != null) {
            lineNumberUncheck++;
            if (lineUncheck.contains("<?xml")) {
                builderUncheck.append(lineUncheck);
            } else if (lineUncheck.contains("<ClinicalDocument")) {
                builderUncheck.append(lineUncheck);
            } else if (lineUncheck.contains("xmlns:xsi")) {
                builderUncheck.append(lineUncheck);
            } else if (lineUncheck.contains("xmlns:mif")) {
                builderUncheck.append(lineUncheck);
            } else if (lineUncheck.contains("xsi:schemaLocation")) {
                builderUncheck.append(lineUncheck);
            } else if (lineUncheck.startsWith("</")) {
                builderUncheck.append(lineUncheck);
            } else {
                if (lineUncheck.contains("<") && lineUncheck.contains("/>")) {
                    builderUncheck.append(lineUncheck.replace("/>", " line=\"" + lineNumberUncheck + "\"/>"));
                } else if (lineUncheck.contains("<") && !lineUncheck.endsWith("/>") && !lineUncheck.contains("!") && !lineUncheck.contains(">")) {
                    builderUncheck.append(lineUncheck + " " + "lineline=\"" + lineNumberUncheck + "\"" + " ");
                } else if (lineUncheck.contains("<") && !lineUncheck.endsWith("/>") && !lineUncheck.contains("!") && lineUncheck.contains("/")) {
                    builderUncheck.append(lineUncheck);
                } else if (lineUncheck.contains("<") && lineUncheck.endsWith(">") && !lineUncheck.contains("!") && !lineUncheck.contains("/")) {
                    builderUncheck.append(lineUncheck.replace(">", " line=\"" + lineNumberUncheck + "\">"));
                } else if (!lineUncheck.contains("<") && lineUncheck.contains("/>")) {
                    builderUncheck.append(lineUncheck.replace("/>", " line=\"" + lineNumberUncheck + "\"/>"));
                } else if (!lineUncheck.contains("<") && lineUncheck.contains(">") && !lineUncheck.contains("/")) {
                    builderUncheck.append(lineUncheck.replace(">", " line=\"" + lineNumberUncheck + "\">"));
                } else if (lineUncheck.contains("<") && lineUncheck.contains(">") && !lineUncheck.contains("!")) {
                    builderUncheck.append(lineUncheck + " " + "<line=\"" + lineNumberUncheck + "\"/>" + " ");
                }
            }
            builderUncheck.append(System.getProperty("line.separator"));
        }
        bufferedReaderXmlUncheck.close();
        bufferedWriterXmlUncheck.write(builderUncheck.toString());
        bufferedWriterXmlUncheck.flush();
        bufferedWriterXmlUncheck.close();

        builderUsecheck.append(System.getProperty("line.separator"));

        }
        bufferedReaderXmlUsecheck.close();
        bufferedWriterXmlTUsecheck.write(builderUsecheck.toString());
        bufferedWriterXmlTUsecheck.flush();
        bufferedWriterXmlTUsecheck.close();
        TempFileDTO tempFileDTO = new TempFileDTO();
        tempFileDTO.setXmlPath(newxmlTempFile.getAbsolutePath()).setXsdPath(newxsdTemptFile.getAbsolutePath()).setXmlTempFile(newxmlTempFile).setXsdTemptFile(newxsdTemptFile);
        boolean deleteXml = tempXmlUncheck.delete();
        log.info("原始待验证的临时xml文件已删除:{}", deleteXml);
        boolean deleteXsd = tempXmlUseCheck.delete();
        log.info("原始用来验证的临时xml文件已删除:{}", deleteXsd);
        return tempFileDTO;
    }
//生成的临时文件每一行都追加了 line ="行号" 节点
 <id root="2.*.*.*.1.11" extension= "" line="27"/>
<id root="2.*.*.*.1.12" extension= ""  line="28"/>
//根据生成待校验的临时文件和用来验证的临时文件来比较节点属性值是否相同
 public  void compareNodes( Node documentElementUnchek, Node documentElementUseCheck,XmlCheckVO vo) {
        //获取根节点属性列表
        NamedNodeMap attributesUnchek = documentElementUnchek.getAttributes();
        NamedNodeMap attributesUseCheck = documentElementUseCheck.getAttributes();
        if (Objects.nonNull(attributesUseCheck) && attributesUseCheck.getLength() > 0) {
            for (int i = 0; i < attributesUseCheck.getLength(); i++) {
                if (Objects.nonNull(attributesUseCheck.item(i))
                        && Objects.nonNull(attributesUnchek.item(i))) {

                    if (!attributesUseCheck.item(i).getNodeName().equals(attributesUnchek.item(i).getNodeName())&&!attributesUseCheck.item(i).getNodeName().matches(attributesUnchek.item(i).getNodeName())) {
                        for (int n = 0; n < attributesUseCheck.getLength(); n++) {
                            if (attributesUnchek.item(n).getNodeName().contains("line")) {
                                vo.setLineNo(attributesUnchek.item(n).getNodeValue());
                                vo.setMsg(attributesUnchek.item(i).getNodeName() + "节点错误,应改为:" + attributesUseCheck.item(i).getNodeName());
                                break;
                            }
                        }
                    }

                    if (!attributesUseCheck.item(i).getNodeValue().contains("$")
                            && !attributesUseCheck.item(i).getNodeValue().equals(attributesUnchek.item(i).getNodeValue())) {
                        for (int k = 0; k < attributesUseCheck.getLength(); k++) {
                            if (attributesUnchek.item(k).getNodeName().contains("line")) {
                                vo.setLineNo(attributesUnchek.item(k).getNodeValue());
                                vo.setMsg(attributesUnchek.item(i).getNodeName() + "=" + attributesUnchek.item(i).getNodeValue() + "内容错误,应改为:" + attributesUseCheck.item(i).getNodeValue());
                                break;
                            }
                        }
                    }
                }

            }

        }
        //获取根节点的子节点递归
        NodeList childNodesUnchek = documentElementUnchek.getChildNodes();
        NodeList childNodesUseCheck = documentElementUseCheck.getChildNodes();
        for (int j = 0; j < childNodesUseCheck.getLength(); j++) {
            if (vo.getLineNo() != null) {
                //vo中如果有行号信息说明已经捕获到错误,直接推出递归返回vo
                break;
            }
            //递归调用
            compareNodes(childNodesUnchek.item(j), childNodesUseCheck.item(j), vo);
        }

    }

本文章仅仅提供比较xml内容的思路和相关的方法,具体详细操作需要读者根据自己的业务场景进行分析操作,以上代码仅提供参考和提供思路

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨落纠纷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值