【问题标题】:Compare 2 XMLs - One with Actual Data and other with Patterns for dynamic Data like DateTime比较 2 个 XML - 一个与实际数据,另一个与 DateTime 等动态数据的模式
【发布时间】:2021-08-05 20:05:48
【问题描述】:

我需要根据预期的 XML 数据验证实际的 XML 数据。

示例:预期数据

<root>
<Orders>
    <Order>
        <DateTime>\d{8}_\d{4}</DateTime>
        <OrderID>\d{4}</OrderID>
    </Order>
        <Order>
        <DateTime>\d{8}_\d{4}</DateTime>
        <OrderID>\d{4}</OrderID>
    </Order>
</Orders>
<queryStatus>Success</queryStatus>
</root>

由于 DateTime 和 OrderIDs 会在每次执行时发生变化,因此我维护一个模式,而不是硬编码 DateTime 或 OrderIDs。以上只是一个示例 - 我们将需要比较和验证几个不同的 XML。

上述实际数据应与两个 XML 中的任何一个匹配

XML1:

<root>
<Orders>
    <Order>
        <DateTime>08052021_1250</DateTime>
        <OrderID>1234</OrderID>
    </Order>
        <Order>
        <DateTime>08052021_1251</DateTime>
        <OrderID>4567</OrderID>
    </Order>
</Orders>
<queryStatus>Success</queryStatus>
</root>

XML2:

<root>
<queryStatus>Success</queryStatus>
<Orders>
    <Order>
        <DateTime>08052021_1250</DateTime>
        <OrderID>1234</OrderID>
    </Order>
        <Order>
        <DateTime>08052021_1251</DateTime>
        <OrderID>4567</OrderID>
    </Order>
</Orders>
</root>

据我了解,xmlunit 会报告 DateTime 和 OrderID 不匹配。我愿意接受基于 Java 的解决方案或基于 Bash 脚本 (xmllint) 的解决方案。您能否提供有关如何解决此问题的任何指示。

【问题讨论】:

  • 查看XSDRELAX NG 或类似工具。
  • \d{8}_\d{4} 用于验证模式而非实际值的正则表达式。您是否需要验证值是否相等?就 XML 内容而言,XML1 和 XML2 看起来相同。
  • XML1 和 XML2 是相同的,只是 XML 元素的顺序不同。是的 \d{8}_\d{4} 是一个正则表达式。由于我需要针对“预期数据”验证每次执行时具有不同日期时间/订单号的输出,因此我无法将实际值放入“预期数据”中。因此我需要输入一个正则表达式(或其他东西),如果实际数据与预期的数据格式相匹配,我们需要进行匹配。

标签: java xml bash pattern-matching


【解决方案1】:

正如@Shawn 在 cmets 中所建议的,您可能希望使用标准工具来验证 XML 数据 w.r.t。一个模型,以专用语言指定,例如 XML SchemaRELAX NG

编写 XSD 模型

首先,您需要编写此模型的定义,例如 XML 模式定义 (.xsd),同时考虑到您提到的关于元素 OrdersqueryStatus: relying on the &lt;xs:all&gt; construct instead of the &lt;xs:sequence&gt; one 用于 rootType 定义。

model.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
  attributeFormDefault="unqualified"
  elementFormDefault="qualified">
  <xs:element name="root" type="rootType"/>
  <xs:complexType name="rootType">
    <xs:all>
      <xs:element type="queryStatusType" name="queryStatus"/>
      <xs:element type="OrdersType" name="Orders"/>
    </xs:all>
  </xs:complexType>
  <xs:simpleType name="queryStatusType">
    <xs:restriction base="xs:string">
      <xs:enumeration value="Success"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:complexType name="OrdersType">
    <xs:sequence>
      <xs:element type="OrderType" name="Order" minOccurs="1" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="OrderType">
    <xs:sequence>
      <xs:element type="DateTimeType" name="DateTime"/>
      <xs:element type="OrderIDType" name="OrderID"/>
    </xs:sequence>
  </xs:complexType>
  <xs:simpleType name="DateTimeType">
    <xs:restriction base="xs:string">
      <xs:pattern value="[0-9]{8}_[0-9]{4}"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="OrderIDType">
    <xs:restriction base="xs:string">
      <xs:pattern value="[0-9]{4}"/>
    </xs:restriction>
  </xs:simpleType>
</xs:schema>

上面的 XML Schema 定义是在 the so-called Venetian Blind design, but this is not the only possible choice 之后编写的。

显然,这个概念验证应该适应您的需求,例如,如果上面的 minOccurs="1" maxOccurs="unbounded" 规范不相关,等等;但由于 XML Schema 语言有点复杂而且表达能力很强,我建议先阅读一些关于这个主题的介绍性课程,然后再查看其 W3C 规范等参考资料:StructuresDatatypes

验证您的 XML 文档

你需要:

  • XML 验证器
  • .xsd 文件(类似于上面的示例)
  • 要验证的 .xml 文档

这实际上与语言无关,因为正如您所提到的,您可以:

  1. 在您常用的编程语言中使用 XML 的标准库;
  2. 通过命令行工具(如xmllint)使用shell 脚本。

关于后一种选择,as mentioned in this other SO answer,你可以直接运行:

xmllint --noout --schema model.xsd file1.xml
# → file1.xml validates
echo $?
# → 0

【讨论】:

  • 您好埃里克,感谢您的回答。但是,将 xmllint & 排序与 xsltproc 与使用 grep / diff 的实际验证相结合,似乎提供了一种可行的解决方案,比为大量 XML 文件创建 XSD 模式要少得多。但是,我同意,如果我们可以创建 XSD 架构文件,您的解决方案将是一个更优雅的解决方案。
【解决方案2】:

用于验证您的文档的RELAX-NG 紧凑模式:

element root {
   element Orders {
      element Order {
         element DateTime {
            xsd:string { pattern = "\d{8}_\d{4}" }
         },
         element OrderID {
            xsd:string { pattern = "\d{4}" }
         }
     }+
    } &
   element queryStatus {
      xsd:string {
         pattern = "Success"
      }
   }
}

(如果重要的话,这将匹配Orders 中的一个或多个Order 元素,而不仅仅是2 个)。

例如,使用jing 验证器(用java编写,看起来可以在您自己的java代码中使用):

$ jing -c expected.rnc test[12].xml && echo "Files pass"
Files pass

如果由于某种原因您无法重写预期的数据 XML,可以使用 XSLT 样式表将其转换为标准 RELAX-NG XML:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>
  
  <xsl:template match="/root">
    <element name="root" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
      <interleave>
        <xsl:apply-templates select="*"/>
      </interleave>
    </element>
  </xsl:template>

  <xsl:template match="Orders">
    <element xmlns="http://relaxng.org/ns/structure/1.0" name="Orders">
      <xsl:for-each select="Order">
        <element name="Order">
          <element name="DateTime">
            <data type="string">
              <param name="pattern"><xsl:value-of select="DateTime"/></param>
            </data>
          </element>
          <element name="OrderID">
            <data type="string">
              <param name="pattern"><xsl:value-of select="OrderID"/></param>
            </data>
          </element>
        </element>
      </xsl:for-each>
    </element>
  </xsl:template>
  
  <xsl:template match="queryStatus">
    <element xmlns="http://relaxng.org/ns/structure/1.0" name="queryStatus">
      <data type="string">
        <param name="pattern"><xsl:value-of select="."/></param>
      </data>
    </element>
  </xsl:template>
  
</xsl:stylesheet>

(这将匹配预期文件中Order 元素的确切数量。)

例子:

$ xsltproc convert.xslt expected.xml > expected.rng
$ jing expected.rng test[12].xml && echo "Files pass"
Files pass
$ xmllint --noout --relaxng expected.rng test[12].xml
test1.xml validates
test2.xml validates

【讨论】:

    【解决方案3】:

    尝试 XSLT 3.0 转换。

    对于XML2中的每个元素,在XML1中找到对应的元素,并测试XML2中的值是否与XML1中的正则表达式匹配:即

    <xsl:mode on-no-match="shallow-skip"/>
    <xsl:template match="*[text()]">
      <xsl:if test="not(matches(., f:corresponding-pattern(.)))">
        <xsl:message>Mismatch!</xsl:message>
      </xsl:if>
    </xsl:template>
    

    这就留下了如何实现f:corresponding-pattern() 的问题。可能一个好方法是通过路径索引 XML1 中的所有元素:

    <xsl:key name="by-path" match="*[text()]" use="path(.)"/>
    

    然后f:corresponding-pattern(.) 减少为key('by-path', path(.), doc('xml1.xml'))

    【讨论】:

      【解决方案4】:

      非常感谢 RELAX-NG XML / XSD 模型解决方案,但它们听起来有点复杂 + 将至少一百种不同的 XML 输出转换为 RELAX-NG XML / XSD 模型的复杂性对我来说有点令人生畏。 (可能是因为我对这些方法不太熟悉)。但是我在互联网上发现了一个更简单的解决方法: https://rhubbarb.wordpress.com/2009/04/20/comparing-xml-files/ 此链接建议在 Bash 中使用 xsltproc 命令通过 XSLT 对 XML 元素和属性进行排序:

      xsltproc sort.xsl $var_ExpectedFile>tmpExpectedFile.xml
      xsltproc sort.xsl $var_actualFile>tmpActualFile.xml
      

      sort.xsl 的内容

      <?xml version="1.0" ?>
      <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
      <xsl:template match="/">
              <xsl:apply-templates/>
      </xsl:template>
      <xsl:template match="*">
              <xsl:element name="{name()}">
       <xsl:copy-of select="namespace::*"/>
                      <xsl:for-each select="@*">
                              <xsl:sort select="name()"/>
                              <xsl:attribute name="{name()}">
                                      <xsl:value-of select="."/>
                              </xsl:attribute>
                      </xsl:for-each>
                      <xsl:apply-templates>
                              <xsl:sort select="name()"/>
                              <xsl:sort select="@type" />
                              <xsl:sort select="@name" />
                              </xsl:apply-templates>
              </xsl:element>
      </xsl:template>
      </xsl:stylesheet>
      

      如果我从 XML 中删除所有新行,并使用上面的 XSLT 对 XML 进行排序,我可以使用一个简单的方法来比较 XML

      grep -f  $var_ExpectedFile $var_actualFile>
      

      到目前为止,这对我已经尝试过的 XML 有效。听起来容易多了..但需要对这种方法进行审查。听起来是不是很傻?

      【讨论】:

      • 恕我直言,对 XML 元素进行排序并在之后执行 grep -f 似乎很脆弱且不够表达......即使它看起来很简单并且可以用于验证您的某些 XML 文档,如果您想验证一个给定元素/属性是可选的,或者只能具有给定数量的子元素(例如,2 到 4 个)的 XML 文档,我想以精确和易于处理的方式处理此验证的唯一解决方案是使用XML规范,比如XSD(比如我评论的第二个例子,你可以写&lt;xs:element type="SubEltType" name="SubElt" minOccurs="2" maxOccurs="4"/&gt;
      • 请注意,XSD 可以从 XML 文件自动生成以进行验证:参见例如this nice website;但您也可以在网上找到a dedicated XSD editor or a plugin for your Java IDE。然后,生成的 XSD 文件将直接没问题,或者如果您想放松一些约束,请使用您的 XSD 编辑器修改它们(或手动,在浏览我提到的 W3C 的 XSD 规范 in my answer 之后)。
      猜你喜欢
      • 2023-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多