【问题标题】:How to check if an XML file has a minimum structure (Java)?如何检查 XML 文件是否具有最小结构(Java)?
【发布时间】:2020-03-31 15:40:50
【问题描述】:

我需要检查一个 XML 文件(现在表示为字符串)是否具有最小结构,该结构也存储在另一个文件/字符串中。

这张图片描述了一个小例子来解释我的意思:

最小结构在右上角。

Template = "<A><B/><C><E></E></C></A>"
XML1 = "<A><B/><C><D></D><E/><F/></A>" //Compliant to Template: the structure is kept
XML2 = "<A><B><E/></B><C/></A>" //Not compliant to Template: E is child of B here, while E is child of C in Template
XML3 = "<A><C><E/><D/></C><F></F><B/></A>" //Compliant to Template: the order of children doesn't matter

一种可能的方法是在两棵树中转换我要检查的两个 XML 文件,然后对于模板的每个节点,比较从树根开始的路径,假设名称是唯一的。我可以使用其他方法或库吗?

【问题讨论】:

  • 我没有仔细考虑过,但我可能会考虑使用 Sax 和 Dom 组合的方法。将最小结构加载到 DOM 树中,然后 Sax 解析 XML 文件,在到达 SAX 中的结束标记时从 DOM 中删除节点。最后,如果 DOM 为空,则输入具有最小结构。只是一个想法 - 这是你遇到的一个有趣的问题
  • 看看下面的答案,如果最小结构是固定的并且已知,那么根据模式进行验证肯定是最简单的,但如果直到运行时才知道,那么你需要一个替代方案。

标签: java xml


【解决方案1】:

您可以使用 XSD 来定义 XML 的结构并对照它检查 XML 文件。 你可以看看 javax.xml.validation.Validator;

验证者信息: https://docs.oracle.com/javase/8/docs/api/javax/xml/validation/Validator.html

示例 xsd https://docs.microsoft.com/en-Us/visualstudio/xml-tools/sample-xsd-file-purchase-order-schema?view=vs-2019

为您准备的简单 XSD:

<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="A">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" type="xs:string" name="B"/>
        <xs:element minOccurs="0" name="C">
          <xs:complexType>
            <xs:sequence>
              <xs:element minOccurs="0" type="xs:string" name="E"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
      <xs:element minOccurs="0" type="xs:string" name="F"/>
    </xs:complexType>
  </xs:element>
</xs:schema>

如您所见,我定义了元素的层次结构。 XSD 将忽略 B、C 和 F 的顺序。 另外你可以定义minOccursmaxOccurs,默认是1。

【讨论】:

  • 谢谢,我从来没有使用过验证器,你能告诉我它们是否也支持参数检查吗?我的意思是,除了检查是否存在 XML 元素之外,还要检查它是否有某个参数,如果有的话,比较/检索它的值?
  • 您可以通过定义 来做到这一点,在这种情况下,元素必须具有 string-attribute "朗”。如果未设置,它将被设置为定义的默认值。如果你想要一个特定的值,你可以使用固定而不是默认值。 这要求属性等于给定值。对于必需的属性,插入使用 =“必需”。有关更多信息,您可以查看W3C Recommendation on xsd
【解决方案2】:

您可能的方法(转换两个 XML 并比较节点)很好,但您不比较路径,而是在导航节点时并行比较节点。

正如您所说,这假定名称是唯一的。

例如像这样,使用递归方法:

public static void checkXml(String templateXml, String dataXml) throws Exception {
    DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
    Element templateRoot = domBuilder.parse(new InputSource(new StringReader(templateXml))).getDocumentElement();
    Element dataRoot = domBuilder.parse(new InputSource(new StringReader(dataXml))).getDocumentElement();
    if (! templateRoot.getNodeName().equals(dataRoot.getNodeName()))
        throw new IllegalArgumentException("Different root elements: " + dataRoot.getNodeName() +
                                                                " != " + templateRoot.getNodeName());
    checkChildren(templateRoot, dataRoot, dataRoot.getNodeName());
}
private static void checkChildren(Node templateParent, Node dataParent, String parentPath) {
    for (Node templateChild = templateParent.getFirstChild(); templateChild != null; templateChild = templateChild.getNextSibling()) {
        if (templateChild.getNodeType() == Node.ELEMENT_NODE) {
            String childPath = parentPath + "/" + templateChild.getNodeName();
            Node dataChild = getChild(dataParent, templateChild.getNodeName());
            if (dataChild == null)
                throw new IllegalArgumentException("Missing child: " + childPath);
            checkChildren(templateChild, dataChild, childPath);
        }
    }
}
private static Node getChild(Node parent, String name) {
    for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling())
        if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(name))
            return child;
    return null;
}

测试

public static void main(String[] args) throws Exception {
    String template = "<A><B/><C><E></E></C></A>";
    String xml1 = "<A><B/><C><D></D><E/></C><F/></A>"; //Compliant to Template: the structure is kept
    String xml2 = "<A><B><E/></B><C/></A>"; //Not compliant to Template: E is child of B here, while E is child of C in Template
    String xml3 = "<A><C><E/><D/></C><F></F><B/></A>"; //Compliant to Template: the order of children doesn't matter

    test(template, xml1);
    test(template, xml2);
    test(template, xml3);
}
private static void test(String templateXml, String dataXml) throws Exception {
    try {
        checkXml(templateXml, dataXml);
        System.out.println("Ok");
    } catch (IllegalArgumentException e) {
        System.out.println(e.getMessage());
    } catch (Exception e) {
        System.out.println(e);
    }
}

输出

Ok
Missing child: A/C/E
Ok

【讨论】:

    【解决方案3】:

    Andreas 的回答很好 - 我一直在思考这个问题,并想出了一种 DOM/SAX 方法,我将仅出于兴趣而对其进行描述。

    • 将最小结构解析为 DOM 树(如 Andreas 的解决方案)
    • SAX 按如下方式解析输入文件:
    • 在每个开始标签上,将完整的标签路径推送到堆栈上(通过查看堆栈顶部并附加新标签来导出它 - 堆栈可以包含标签列表或串联的字符串)
    • 在每个结束标记上,从堆栈中弹出顶部,在 DOM 树中查找匹配的节点(这有点繁琐,因为 Document 没有“通过路径获取”方法)。如果您找到一个并且它没有孩子,则将其从树上移除,否则什么也不做并继续
    • 在 SAX 解析整个输入后,如果 DOM 树为空,则输入正常。 DOM 树中保留的任何结构都是输入的缺失部分。

    缺点是您已经为最小结构操作了 DOM,因此您每次都必须重新解析它,如果您要处理大量输入文件,则会产生一些额外的成本。

    无论如何,安德烈亚斯已经整合了一个完整的编码解决方案,所以我只是把它放在这里作为一般兴趣的替代方案。

    【讨论】:

    • 请注意,这种方法不需要唯一的名称,如果这对您来说确实是个问题
    • 此解决方案也需要唯一的名称,否则它无法正确检测到缺失的节点。
    • 您可能是对的,但当我们说非唯一名称时,我们是指例如单个 C 中的多个同级 E 节点,还是分散在树周围的 E 节点?对于后者,两种解决方案都可以工作,因为路径是唯一的。对于前者,如果最小结构需要 3 个 C 节点并且输入有 2 个,那么我们会将 /C/E 推入堆栈并连续两次弹出。如果每次我们在树中找到第一个 /C/E 并将其删除,那么最后会留下一个 /C/E。因此我认为它会起作用,但我没有对其进行编码来检查
    • (刚刚下班,所以暂时离开笔记本电脑 :-))
    • 如果模板需要三个 P 节点,第一个是 A,第二个是 B,第三个是 C,该怎么办?如果文档有三个 P 节点,但 A、B、C 都在第三个,则报告的缺失节点将不正确。
    猜你喜欢
    • 1970-01-01
    • 2016-04-29
    • 1970-01-01
    • 2011-10-30
    • 1970-01-01
    • 2010-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多