高级 XML 验证-使用 XSLT 和 Java 扩展验证 XML 文档中的复杂约束

 
XSLT 样式表被设计用来转换 XML 文档。当基于语法的验证不能覆盖所有需要的约束时,通过与 Java 扩展一起使用,样式表可以成为 XML Schema 的一个强有力的补充。在本文中,Peter Heneback 讲解了使用 XSLT 和 Java 扩展来验证文档的案例,并提供了实用的指导和代码示例。

背景

基于语法的验证语言,例如 XML Schema 和 DTD,可以很好地确保 XML 文档遵从定义良好的消息结构。这样可确保接收 XML 消息的应用程序能够正确地处理接收到的 XML 消息,但是不能保证包含在消息中的数据是有效的。例如,基于语法的验证语言的这些局限性意味着必须使用不同的方法来验证变量和外部数据集上的同现约束(co-occurrence constraints)和其他约束。

在很多情况下,用 XML Schema 或 DTD 不能实现的验证逻辑被并入到应用程序代码中。这种解决方案比较容易实现,但是得到的实现常常不够灵活。本文首先调查研究 Schematron 这种解决上述问题的方法,接着指出这种方法的一些缺点。然后,本文探索一种使用 W3C 标准组件并结合 Java 扩展和开放源代码 XSLT 处理器的方法。





回页首


Schematron

常被推荐的用于补充 XML Schema 的方案是使用 Schematron。Schematron 是基于规则的语言,使用 XPath 来表达关于 XML 实例文档中的内容的断言。这是通过用一个基本样式表转换 Schematron 模式,将该模式转换成一个 XSLT 样式表来完成的。转换的结果是一个 XML 格式的报告,其中包含关于哪些断言失败的详细信息,并附有注释。然而,Schematron 不大适用于定义结构,因为用 Schematron 定义结构很快就会变得吃力起来。因此,仍然需要首先用 XML Schema 对文档进行验证,但是 XML Schema 和 Schematron 一起可以满足大多数应用程序的验证需求。实际上,在 XML Schema 语法中,由于 Schematron 断言处在不同的名称空间里,两者通常可以包括在同一个文件中,并且分别作为文档验证过程的一部分。

图 1 描绘了一个很常见的应用场景的逻辑处理步骤。XSLT 首先验证传入的 XML 实例文档,然后在应用程序本身对它进行处理或者它被发送到外部应用程序之前对它进行转换。从图中很容易看出,当结合使用 XML Schema 和 Schematron 第一次执行验证,然后使用一个 XSLT 样式表转换验证过的文档时,这一操作变得复杂起来。可以通过将模式分离,提前转换 Schematron 模式来缩短该过程,但是这仍然需要运行两次 XSLT 处理器,并且需要解析和检查由 Schematron 转换产生的验证报告。


图 1. 使用 Schematron 进行验证
使用 Schematron 验证 XML 数据

Schematron 的缺点

  • 在使用 Schematron 模式验证 XML 文档之前,至少需要使用基本样式表对该模式进行一次转换。如果 Schematron 语法包括在 XML Schema 中,那么还需要进一步的转换。
  • Schematron 目前不能直接将报告返回给应用程序。在进行验证之后,需要手动地或自动地对报告加以处理。




回页首


XSLT 和 Java 扩展

在本节中,我将介绍 XSLT 加 Java 扩展这种补充基于语法的 XML Schema 验证的方法,这是一种基于规则的方法。我将带您亲历一组简单的示例。至于如何使用 XML Schema 实现 XML 验证器(validator),本文不作介绍,因为在 developerWorks 上的很多文章和教程上对此作了大量的阐述(参见 参考资料)。

图 2 展示了与 图 1 相同的场景,不过这次使用 XSLT 加 Java 扩展在一步内执行验证和转换。这一次不再产生一个报告,然后又必须单独处理该报告,而是直接将验证失败以一个 ValidationException 对象的形式返回给应用程序,其中 ValidationException 类扩展了 Exception 类。除了减少文档经过 XSLT 处理器的次数以外,在第一次验证失败时就会停止转换,从而防止对无效数据的不必要的处理。


图 2. 使用 XSLT 和 Java 扩展进行验证
使用 XSLT 和 Java 扩展进行验证

一个 XML 文档的简单转换

为了演示如何使用 XSLT 和 Java 进行验证,我使用一个简单的登记表的转换作为示例输入,该登记表包含雇员详细信息,例如姓名、电话号码、称呼和性别。稍后可以看到,称呼和性别信息在同现约束的中央。清单 1 包含 XML 输入文档的一部分。


清单 1. XML 输入文档
<input:staff xmlns:input="cross-field-validation-namespace">

...

  <input:employee id="1234A">
    <input:first_name>Julia</input:first_name>
    <input:last_name>Smith</input:last_name>
    <input:title>Mrs</input:title>
    <input:gender>F</input:gender>
    <input:telephones>
      <input:mobile preferred="false">0770-555 1231</input:mobile>
      <input:mobile preferred="true">0771-555 1232</input:mobile>
      <input:home preferred="false">0207-555 1233</input:home>
    </input:telephones>
  </input:employee>

...

</input:staff>

这个例子中的转换简单地将雇员登记表中的姓和名合并在一起。该转换还提取那些 preferred 属性被设置为 true 的电话号码,如 清单 2 所示。


清单 2. 雇员数据的简单转换
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" xmlns:xalan="http://xml.apache.org/xslt"
  xmlns:input="cross-field-validation-namespace">

  <xsl:template match="/input:staff">
    <phones>
        <xsl:apply-templates select="input:employee" />
    </phones>
  </xsl:template>

  <xsl:template match="input:employee">
    <employee>
      <name>
        <xsl:value-of select="concat(input:first_name,' ',input:last_name)" />
      </name>
      <tel>
        <xsl:value-of select="input:telephones/*[@preferred = 'true']" />
      </tel>
    </employee>
  </xsl:template>

</xsl:stylesheet>

清单 3 显示了通过用 清单 2 中的 XSLT 转换输入文档而得到的输出文档。


清单 3. 输出文档
<phones xmlns:input="cross-field-validation-namespace"
  xmlns:exception="xfield.exception.ValidationExceptionThrower"
  xmlns:xalan="http://xml.apache.org/xslt">
  <employee>
    <name>Julia Smith</name>
    <tel>0771-555 1232</tel>
  </employee>
  <employee>
    <name>John Smith</name>
    <tel>0207-555 1236</tel>
  </employee>
  <employee>
    <name>Jenny Smith</name>
    <tel>0770-555 1237</tel>
  </employee>
</phones>

实现简单的同现约束验证

为了通过 XSLT 处理器将验证错误返回给应用程序,只需要两个非常简单的 Java 类。为此,必须创建 ValidationException 类和 ValidationExceptionThrower 类。ValidationException 类是标准 Java Exception 类的一个简单的扩展,它使应用程序可以将验证错误与处理器抛出的其他异常区分开来。当 ValidationExceptionThrower 类的 throwException 方法被调用时,该类简单地抛出 ValidationException。为此另外还需要一个类。当在 XSLT 中使用 Java 扩展时,不能使用常规的 Java throw 语法抛出异常。只能使用用于 XSLT 的 Java 扩展来创建对象和调用方法。清单 45 展示了 Java 扩展所需的两个类的完整源代码。


清单 4. ValidationException 类
package xfield.exception;

public class ValidationException extends Exception{

  public ValidationException(String sMsg)
  {
    super(sMsg);
  } // end constructor

} // end class



清单 5. ValidationExceptionThrower 类
package xfield.exception;

public class ValidationExceptionThrower {

  public ValidationExceptionThrower()
  {
    // Emtpy
  } // end constructor

  public void throwException(String sMessage) throws Exception
  {
    throw new ValidationException(sMessage);
  } // end throwException

} // end class

检查同现约束

如前所述,本文使用称呼和性别作为一个常见同现约束的例子。您需要检查称呼和性别元素是否匹配。例如,如果 title 被设为 Mr,那么 gender 应该被设置为表示男性的 M。但是,要验证 title 和 gender 元素是否分别包含有效的值,最好的做法是使用 XML Schema 中的枚举。

为了进行验证,将转换代码放入到 <choose/> 子句的 <otherwise/> 块中,并检查 <when/> 块中的 test 语句。如果 test 语句的值等于 false,也就是说验证成功,那么就会执行转换代码。如果 test 语句的值等于 true,那么就会使用一个 Java 扩展在 ValidationExceptionThrower 类的一个实例上调用 throwException()。注意,在 XSLT 的根标记中附加了一个带前缀 exception 的名称空间,其中包含类名和包名。然后这个前缀被像一个对象名那样用来调用 throwException() 方法,调用时以一个错误字符串作为参数。回到 ValidationExceptionThrower 类的 Java 代码上来,很容易看出一个包含 XSLT 中的错误字符串的 ValidationException 是如何被抛出的。

为了检查进一步的状况,添加所需的 <when/> 语句,并添加适当的错误消息作为这些方法的参数。


清单 6. 基本转换和同现约束验证
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" xmlns:xalan="http://xml.apache.org/xslt"
  xmlns:exception="xfield.exception.ValidationExceptionThrower"
  xmlns:input="cross-field-validation-namespace">

...

<xsl:template match="input:employee">
  <xsl:choose>
    <xsl:when test="input:gender = 'M' and input:title != 'Mr' 
                 or input:gender = 'F' and input:title = 'Mr'">
      <xsl:value-of
        select="exception:throwException('Gender and title do not match')"/>
    </xsl:when>
    <xsl:otherwise>
       <employee>
         <name>
           <xsl:value-of select="concat(input:first_name,' ',input:last_name)"/>
         </name>
         <tel>
           <xsl:value-of select="input:telephones/*[@preferred = 'true']"/>
         </tel>
       </employee>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

清单 7 所示,添加 XPath 表达式到异常消息将产生更详细的对验证失败的描述,这样大大方便了发现问题。


清单 7. 详细的验证失败信息
<xsl:when test="input:gender = 'M' and input:title != 'Mr' 
          or input:gender = 'F' and input:title = 'Mr'">
  <xsl:value-of select="exception:throwException(
                  concat('Gender and title do not match for employee ',
                          input:first_name
,' ',
input:last_name))" />
</xsl:when>

将验证与转换分离

如果需要将转换和验证逻辑分离开来,那么可以使用一个标准的 <include/> 指令来引用验证检查,并将它们与转换代码一起执行。清单 8 展示了转换代码,其中 <include/> 标记引用了文件 validate.xsl。还应注意添加的对名为 validate 的模板的调用。这个调用应该与被包括的文件中包含条件检查的模板的名称相匹配,如 清单 9 所示,这一点很重要。


清单 8. 转换逻辑摘录
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" xmlns:xalan="http://xml.apache.org/xslt"
  xmlns:input="cross-field-validation-namespace">

  <xsl:include href="validate.xsl" />

  <xsl:template match="/">
    <xsl:call-template name="validate" />
    <phones>
      <xsl:apply-templates />
    </phones>
  </xsl:template>

  <xsl:template match="input:staff/input:employee">
    <employee>
      <name>
        <xsl:value-of
          select="concat(input:first_name,' ',input:last_name)" />
      </name>
      <tel>
        <xsl:value-of
          select="input:telephones/*[@preferred = 'true']" />
      </tel>
    </employee>
  </xsl:template>

</xsl:stylesheet>


清单 9. 验证逻辑摘录
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" xmlns:xalan="http://xml.apache.org/xslt"
  xmlns:exception="xfield.exception.ValidationExceptionThrower"
  xmlns:input="cross-field-validation-namespace">

  <xsl:template name="validate">
    <xsl:for-each select="/input:staff/input:employee">
      <xsl:if
        test="input:gender = 'M' and input:title != 'Mr' 
               or input:gender = 'F' and input:title = 'Mr'">
        <xsl:value-of
          select="exception:throwException('Gender and title do not match')" />
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

对外部引用数据进行验证

XML Schema 可以使用枚举将传入的数据与一组预先决定的可接受的值进行比较。但是,如果引用数据是变量,且本身包含关系,那么 XML Schema 就无法对它进行验证。这个例子表明,使用 XSLT 和 Java 扩展的解决方案可以满足这种场景,并且可以对变量和外部 XML 数据集进行验证。注意,Schematron 也提供了这种功能,因为被转换的模式使用 XSLT 来执行验证。


清单 10. 引用数据
<roles>
...
  <employee id="1234A" role="A"/>
  <employee id="1234D" role="A"/>
  <employee id="1234C" role="X"/>
  <employee id="1234B" role="Z"/>
  <employee id="1234X" role="Z"/>
...    
</roles>

这个例子中的外部引用数据是一组雇员,这些雇员有相关联的角色。检查传入的 XML 文档中的所有雇员 ID 是否都在引用数据集中。为此,使用 document() 函数将一个外部 XML 文档装载到一个变量中。检查是否至少有一个具有当前 ID 的雇员在 reference 变量中,以断言该雇员号是有效的。


清单 11. 验证外部引用数据
<xsl:variable name="reference" select="document('reference.xml')" />

<xsl:template name="validate">
  <xsl:for-each select="/input:staff/input:employee">

    <xsl:if test="input:gender = 'M' and input:title != 'Mr' 
           or input:gender = 'F' and input:title = 'Mr'">
      <xsl:value-of
        select="exception:throwException(concat('Gender and title do not match 
               for employee ',input:first_name,' ',input:last_name))" />
    </xsl:if>

    <xsl:variable name="current_id" select="@id" />

    <xsl:if
      test="count($reference/roles/employee[@id = $current_id]) = 0">
      <xsl:value-of
        select="exception:throwException(concat('Invalid employee ID: ',$current_id))" />
    </xsl:if>

  </xsl:for-each>

</xsl:template>





回页首


结束语

总而言之,当处理同现约束,或者需要一个 XML 报告工具时,Schematron 是 XML Schema 的一个很好的补充。但是当性能更为重要时,尤其是当验证之后要进行转换时,XSLT 加 Java 扩展是一种更紧凑的解决方案。






回页首


下载

描述名字大小下载方法
Java and XSLT source code used in this articleadvanced_xml_validation.zip18KBHTTP
关于下载方法的信息


参考资料

学习

获得产品和技术
  • Saxon:下载 Saxon XSLT 处理器。

  • Xalan-J:下载 Xalan-J XSLT 处理器。

  • Xerces:下载 Xerces XML 解析器。



关于作者

Peter Heneback 的照片

Peter Heneback 最近毕业并获得计算机科学硕士学位,毕业后在 IBM United Kingdom Limited 工作,担任集成技术、Java 和 XML 方面的顾问。他一直从事网格和 Web 服务实现方面的工作,之前还发表了一篇关于网格安全的文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值