实现 SAX 验证

原文: https://docs.oracle.com/javase/tutorial/jaxp/sax/validation.html

示例程序SAXLocalNameCount默认使用非验证解析器,但它也可以激活验证。激活验证允许应用程序判断 XML 文档是否包含正确的标记,或者这些标记是否按正确顺序排列。换句话说,它可以告诉您文档是否有效。但是,如果未激活验证,则只能判断文档是否格式正确,如上一节中从 XML 元素中删除结束标记时所示。为了使验证成为可能,XML 文档需要与 DTD 或 XML 模式相关联。使用SAXLocalNameCount程序可以实现这两个选项。

选择解析器实现

如果未指定其他工厂类,则使用默认的SAXParserFactory类。要使用其他制造商的解析器,您可以更改指向它的环境变量的值。您可以从命令行执行此操作:

java -Djavax.xml.parsers.SAXParserFactory = _yourFactoryHere_ [...]

您指定的工厂名称必须是完全限定的类名(包括所有包前缀)。有关更多信息,请参阅SAXParserFactory类的newInstance()方法中的文档。

使用验证解析器

到目前为止,本课程主要集中在非验证解析器上。本节将检查验证解析器,以了解在使用它解析示例程序时会发生什么。

关于验证解析器必须了解两件事:

  • 架构或 DTD 是必需的。

  • 因为存在模式或 DTD,所以ContentHandler。尽可能调用 ignorableWhitespace()方法。

可忽略的白色空间

当存在 DTD 时,解析器将不再在它知道不相关的空白区域上调用characters()方法。从对仅处理 XML 数据感兴趣的应用程序的角度来看,这是一件好事,因为应用程序永远不会受到仅仅为了使 XML 文件可读而存在的空白空间的困扰。

另一方面,如果您正在编写一个过滤 XML 数据文件的应用程序,并且如果您想输出同样可读的文件版本,那么该空格将不再是无关紧要的:它将是必不可少的。要获取这些字符,您可以将ignorableWhitespace方法添加到您的应用程序中。要处理解析器看到的任何(通常)可忽略的空白区域,您需要添加类似以下代码的内容来实现ignorableWhitespace事件处理器。

public void ignorableWhitespace (char buf[], int start, int length) throws SAXException { emit("IGNORABLE"); }

此代码只是生成一条消息,让您知道可以看到可忽略的空白区域。但是,并非所有解析器都是相同的。 SAX 规范不要求调用此方法。只要 DTD 能够实现,Java XML 实现就会这样做。

配置工厂

需要设置SAXParserFactory ,使其使用验证解析器而不是默认的非验证解析器。来自SAXLocalNameCount示例的main()方法的以下代码显示了如何配置工厂以使其实现验证解析器。

  1. static public void main(String[] args) throws Exception {
  2. String filename = null;
  3. boolean dtdValidate = false;
  4. boolean xsdValidate = false;
  5. String schemaSource = null;
  6. for (int i = 0; i < args.length; i++) {
  7. if (args[i].equals("-dtd")) {
  8. dtdValidate = true;
  9. }
  10. else if (args[i].equals("-xsd")) {
  11. xsdValidate = true;
  12. }
  13. else if (args[i].equals("-xsdss")) {
  14. if (i == args.length - 1) {
  15. usage();
  16. }
  17. xsdValidate = true;
  18. schemaSource = args[++i];
  19. }
  20. else if (args[i].equals("-usage")) {
  21. usage();
  22. }
  23. else if (args[i].equals("-help")) {
  24. usage();
  25. }
  26. else {
  27. filename = args[i];
  28. if (i != args.length - 1) {
  29. usage();
  30. }
  31. }
  32. }
  33. if (filename == null) {
  34. usage();
  35. }
  36. SAXParserFactory spf = SAXParserFactory.newInstance();
  37. spf.setNamespaceAware(true);
  38. spf.setValidating(dtdValidate || xsdValidate);
  39. SAXParser saxParser = spf.newSAXParser();
  40. // ...
  41. }

这里, SAXLocalNameCount程序被配置为在启动时获取其他参数,这表明它不对特定的模式源文件实现验证,DTD 验证,XML 模式定义(XSD)验证或 XSD 验证。 。 (这些选项的描述, -dtd-xsd-xsdss也被添加到用法()方法中,但是这里没有显示。)然后,工厂被配置为当调用newSAXParser时它将生成适当的验证解析器。如设置解析器中所示,您还可以使用setNamespaceAware(true)将工厂配置为返回名称空间感知解析器。 Sun 的实现支持任何配置选项组合。 (如果特定实现不支持组合,则需要生成出厂配置错误)。

使用 XML Schema 进行验证

尽管 XML Schema 的完整处理超出了本教程的范围,但本节将向您展示使用 XML Schema 语言编写的现有模式验证 XML 文档的步骤。要了解有关 XML Schema 的更多信息,您可以在 http://www.w3.org/TR/xmlschema-0/ 上查看在线教程 XML Schema Part 0:Primer 。 。


:有多种模式定义语言,包括 RELAX NG,Schematron 和 W3C“XML Schema”标准。 (即使 DTD 有资格作为“模式”,但它是唯一不使用 XML 语法来描述模式约束的模式。)但是,“XML 模式”向我们提出了术语挑战。尽管短语“XML Schema schema”是准确的,但我们将使用短语“XML Schema definition”来避免出现冗余。


要收到 XML 文档中验证错误的通知,必须将解析器工厂配置为创建验证解析器,如上一节所示。此外,必须满足以下条件:

  • 必须在 SAX 解析器上设置适当的属性。
  • 必须设置适当的错误处理器。
  • 该文档必须与架构相关联。

设置 SAX 分析器属性

首先定义设置属性时将使用的常量是有帮助的。 SAXLocalNameCount示例设置以下常量。

  1. public class SAXLocalNameCount extends DefaultHandler {
  2. static final String JAXP_SCHEMA_LANGUAGE =
  3. "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
  4. static final String W3C_XML_SCHEMA =
  5. "http://www.w3.org/2001/XMLSchema";
  6. static final String JAXP_SCHEMA_SOURCE =
  7. "http://java.sun.com/xml/jaxp/properties/schemaSource";
  8. }

:解析器工厂必须配置为生成一个名称空间感知和验证的解析器。这在配置工厂中显示。 文档对象模型中提供了有关命名空间的更多信息,但是现在,要了解模式验证是面向命名空间的过程。由于默认情况下 JAXP 兼容的解析器不支持名称空间,因此必须将架构验证的属性设置为有效。


然后,您必须配置解析器以告诉它使用哪种模式语言。在SAXLocalNameCount中,可以针对 DTD 或针对 XML 架构执行验证。下面的代码使用上面定义的常量将 W3C 的 XML Schema 语言指定为在程序启动时指定-xsd选项时使用的语言。

  1. // ...
  2. if (xsdValidate) {
  3. saxParser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
  4. // ...
  5. }

除了设置错误处理中描述的错误处理之外,在为基于模式的验证配置解析器时可能会发生一个错误。如果解析器不符合 JAXP 规范,因此不支持 XML Schema,则它会抛出SAXNotRecognizedException 。为了处理这种情况, setProperty()语句包含在 try / catch 块中,如下面的代码所示。

  1. // ...
  2. if (xsdValidate) {
  3. try {
  4. saxParser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
  5. }
  6. catch (SAXNotRecognizedException x){
  7. System.err.println("Error: JAXP SAXParser property not recognized: "
  8. + JAXP_SCHEMA_LANGUAGE);
  9. System.err.println( "Check to see if parser conforms to the JAXP spec.");
  10. System.exit(1);
  11. }
  12. }
  13. // ...

将文档与模式相关联

要使用 XML Schema 定义验证数据,必须确保 XML 文档与一个 XML 文档相关联。有两种方法可以做到这一点。

  • 通过在 XML 文档中包含模式声明。
  • 通过指定要在应用程序中使用的架构。

:当应用程序指定要使用的模式时,它会覆盖文档中的任何模式声明。


要在文档中指定模式定义,您可以创建如下 XML:

  1. <documentRoot
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:noNamespaceSchemaLocation='YourSchemaDefinition.xsd'>

第一个属性定义 XML 名称空间( xmlns )前缀, xsi ,它代表 XML Schema 实例。第二行指定用于文档中没有名称空间前缀的元素的模式,即用于通常在任何简单,简单的 XML 文档中定义的元素。


:有关命名空间的更多信息包含在文档对象模型中的 XML Schema 验证中。现在,将这些属性视为用于验证不使用它们的简单 XML 文件的“神奇咒语”。在了解了有关命名空间的更多信息之后,您将了解如何使用 XML Schema 来验证使用它们的复杂文档。在文档对象模型中的“使用多个命名空间验证”中讨论了这些想法。


您也可以在应用程序中指定模式文件,如SAXLocalNameCount中的情况。

  1. // ...
  2. if (schemaSource != null) {
  3. saxParser.setProperty(JAXP_SCHEMA_SOURCE, new File(schemaSource));
  4. }
  5. // ...

在上面的代码中,变量schemaSource与一个模式源文件有关,您可以通过-xsdss选项启动它来指向SAXLocalNameCount应用程序并提供要使用的架构源文件的名称。

验证分析器中的错误处理

重要的是要认识到,当文件验证失败时抛出异常的唯一原因是设置错误处理中显示的错误处理代码。该代码在此处转载为提醒:

  1. // ...
  2. public void warning(SAXParseException spe) throws SAXException {
  3. out.println("Warning: " + getParseExceptionInfo(spe));
  4. }
  5. public void error(SAXParseException spe) throws SAXException {
  6. String message = "Error: " + getParseExceptionInfo(spe);
  7. throw new SAXException(message);
  8. }
  9. public void fatalError(SAXParseException spe) throws SAXException {
  10. String message = "Fatal Error: " + getParseExceptionInfo(spe);
  11. throw new SAXException(message);
  12. }
  13. // ...

如果不抛出这些异常,则忽略验证错误。通常,SAX 解析错误是验证错误,但如果文件指定解析器未准备处理的 XML 版本,也可以生成它。请记住,除非您提供错误处理器(如此处的错误处理器),否则您的应用程序不会生成验证异常

DTD 警告

如前所述,仅在 SAX 解析器处理 DTD 时才会生成警告。某些警告仅由验证解析器生成。非验证解析器的主要目标是尽可能快地运行,但它也会产生一些警告。

XML 规范建议应该由于以下原因生成警告:

  • 为实体,属性或符号提供附加声明。 (忽略此类声明。仅使用第一个。另请注意,重复的元素定义在验证时始终会产生致命错误,如前所述。)

  • 引用未声明的元素类型。 (只有在 XML 文档中实际使用了未声明的类型时才会出现有效性错误。在 DTD 中引用未声明的元素时会出现警告。)

  • 声明未声明元素类型的属性。

在其他情况下,Java XML SAX 解析器也会发出警告:

  • 没有<!DOCTYPE ...>验证时。

  • 不验证时引用未定义的参数实体。 (验证时,会产生错误。虽然不需要非验证解析器来读取参数实体,但 Java XML 解析器也会这样做。因为它不是必需的,所以 Java XML 解析器会生成警告,而不是错误。)

  • 某些情况下,字符编码声明看起来不正确。

运行带验证的 SAX 分析器示例

在本节中,将再次使用之前使用的SAXLocalNameCount示例程序,除非这次将对 XML Schema 或 DTD 进行验证。演示不同类型验证的最佳方法是修改正在解析的 XML 文件的代码,以及关联的模式和 DTD,以中断处理并使应用程序生成异常。

尝试 DTD 验证错误

如上所述,这些示例重用SAXLocalNameCount程序。您可以在运行没有验证的 SAX 分析器示例中找到您将找到样例及其相关文件的位置。

  1. 如果您还没有这样做,请导航至样例目录。 %cd _install-dir_/ jaxp-14_2-` 释放日期 _ `/样例``
  2. 如果您还没有这样做,请编译示例类。 % javac sax/*
  3. Open the file data/rich_iii.xml in a text editor.

    这是在运行SAXLocalNameCount无验证的示例中未经验证处理的相同 XML 文件。在data / rich_iii.xml的开头,您将看到DOCTYPE声明将验证解析器指向名为play.dtd的 DTD 文件。如果激活了 DTD 验证,则将根据play.dtd中提供的结构检查要解析的 XML 文件的结构。

  4. Delete the declaration <!DOCTYPE PLAY SYSTEM "play.dtd"> from the beginning of the file.

    不要忘记保存修改,但保持文件打开,因为稍后将再次需要。

  5. Run the SAXLocalNameCount program, with DTD validation activated.

    为此,您必须在运行程序时指定-dtd选项。

    % java sax/SAXLocalNameCount -dtd data/rich_iii.xml

    您看到的结果将如下所示:

    1. Exception in thread "main" org.xml.sax.SAXException:
    2. Error: URI=file:install-dir/JAXP_sources/jaxp-1_4_2-release-date
    3. /samples/data/rich_iii.xml
    4. Line=12: Document is invalid: no grammar found.

    :该消息由 JAXP 1.4.2 库生成。如果您使用的是其他解析器,则错误消息可能会有所不同。


    此消息表示没有语法可以验证文档rich_iii.txt ,因此它自动无效。换句话说,消息是说您正在尝试验证文档,但是没有声明 DTD,因为没有DOCTYPE声明存在。所以现在您知道 DTD 是有效文档的要求。那讲得通。

  6. Restore the <!DOCTYPE PLAY SYSTEM "play.dtd"> declaration to data/rich_iii.xml.

    同样,不要忘记保存文件,但保持打开状态。

  7. Return to data/rich_iii.xml and modify the tags for the character “KING EDWARD The Fourth” in line 26.

    < PERSONA&gt;更改开始和结束标记< / PERSONA>< PERSON>< / PERSON> 。第 26 行现在看起来像这样:

    26:&lt;PERSON&gt;KING EDWARD The Fourth&lt;/PERSON>

    同样,不要忘记保存修改,并保持文件打开。

  8. Run the SAXLocalNameCount program, with DTD validation activated.

    这次,运行程序时会看到不同的错误:

    1. % java sax/SAXLocalNameCount -dtd data/rich_iii.xml
    2. Exception in thread "main" org.xml.sax.SAXException:
    3. Error: URI=file:install-dir/JAXP_sources/jaxp-1_4_2-release-date
    4. /samples/data/rich_iii.xml
    5. Line=26: Element type "PERSON" must be declared.

    在这里你可以看到解析器已经反对一个未包含在 DTD data / play.dtd中的元素。

  9. In data/rich_iii.xml correct the tags for “KING EDWARD The Fourth”.

    将开始和结束标记返回到其原始版本,< PERSONA>< / PERSONA>

  10. In data/rich_iii.xml, delete <TITLE&gt;Dramatis Personae&lt;/TITLE> from line 24.

    再一次,不要忘记保存修改。

  11. Run the SAXLocalNameCount program, with DTD validation activated.

    和以前一样,您将看到另一个验证错误:

    1. java sax/SAXLocalNameCount -dtd data/rich_iii.xml
    2. Exception in thread "main" org.xml.sax.SAXException:
    3. Error: URI=file:install-dir/JAXP_sources/jaxp-1_4_2-release-date
    4. /samples/data/rich_iii.xml
    5. Line=85: The content of element type "PERSONAE" must match "(TITLE,(PERSONA|PGROUP)+)".

    通过删除< TITLE&gt;来自第 24 行的元件,< PERSONAE>元素被赋予无效,因为它不包含 DTD 期望的< PERSONAE&gt;的子元素。元素。请注意,即使您删除了< TITLE&gt;,错误消息也会指出错误位于data / rich_iii.xml的第 85 行。来自第 24 行的元素。这是因为< PERSONAE&gt;的结束标记。元素位于第 85 行,解析器仅在它到达解析元素的末尾时抛出异常。

  12. Open the DTD file, data/play.dtd in a text editor.

    在 DTD 文件中,您可以看到< PERSONAE&gt;的声明。元素,以及可以在符合播放 DTD 的 XML 文档中使用的所有其他元素。 < PERSONAE&gt;的声明看起来像这样。

    1. &lt;!ELEMENT PERSONAE (TITLE, (PERSONA | PGROUP)+)&gt;

    如您所见,< PERSONAE>元素需要< TITLE>子元素。管道( | )键意味着< PERSONA>< PGROUP&gt;子元素可以包含在< PERSONAE&gt;中。元素和(PERSONA | PGROUP)分组后的正( + )键意味着必须包括这些子元素中的至少一个或多个。``

  13. Add a question mark (?) key after TITLE in the declaration of <PERSONAE>.

    在 DTD 中向子元素的声明添加问号使得该子元素的一个实例的存在是可选的。

    1. &lt;!ELEMENT PERSONAE (TITLE?, (PERSONA | PGROUP)+)&gt;

    如果在元素后添加星号( * ),则可以包含该子元素的零个或多个实例。但是,在这种情况下,在文档的某个部分中拥有多个标题是没有意义的。

    不要忘记保存对data / play.dtd所做的修改。

  14. Run the SAXLocalNameCount program, with DTD validation activated.% java sax/SAXLocalNameCount -dtd data/rich_iii.xml

    这次,您应该看到SAXLocalNameCount的正确输出,没有错误。

试验模式验证错误

上一个练习演示了使用SAXLocalNameCount来针对 DTD 验证 XML 文件。在本练习中,您将使用SAXLocalNameCount来针对标准 XML 架构定义和自定义架构源文件验证不同的 XML 文件。同样,这种类型的验证将通过修改 XML 文件和模式来破解解析过程来演示,以便解析器抛出错误。

如上所述,这些示例重用SAXLocalNameCount程序。您可以在运行没有验证的 SAX 分析器示例中找到您将找到样例及其相关文件的位置。

  1. 如果您还没有这样做,请导航至样例目录。 %cd _install-dir_/ jaxp-14_2-` 释放日期 _ `/样例``
  2. 如果您还没有这样做,请编译示例类。 % javac sax/*
  3. Open the file data/personal-schema.xml in a text editor.

    这是一个简单的 XML 文件,提供小公司员工的姓名和联系方式。在此 XML 文件中,您将看到它已与架构定义文件personal.xsd相关联。

    <personnel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation='personal.xsd'>

  4. Open the file data/personal.xsd in a text editor.

    此模式定义了每个员工需要哪些类型的信息,以便将与模式关联的 XML 文档视为有效。例如,通过检查模式定义,您可以看到每个元素需要名称,并且每个人的姓名必须包含系列名称和给出了的名字。员工还可以选择使用电子邮件地址和 URL。

  5. In data/personal.xsd, change the minimum number of email addresses required for a person element from 0 to 1.

    电子邮件元素的声明现在如下。

    <xs:element ref="email" minOccurs='1' maxOccurs='unbounded'/>

  6. In data/personal-schema.xml, delete the email element from the person element one.worker.

    工人一现在看起来像这样:

    1. &lt;person id="one.worker"&gt;
    2. &lt;name&gt;&lt;family&gt;Worker&lt;/family&gt; &lt;given&gt;One&lt;/given&gt;&lt;/name&gt;
    3. &lt;link manager="Big.Boss"/&gt;
    4. &lt;/person&gt;
  7. Run SAXLocalNameCount against personal-schema.xml, with no schema validation.% java sax/SAXLocalNameCount data/personal-schema.xml

    SAXLocalNameCount通知您personal-schema.xml中每个元素出现的次数。

    1. Local Name "email" occurs 5 times
    2. Local Name "name" occurs 6 times
    3. Local Name "person" occurs 6 times
    4. Local Name "family" occurs 6 times
    5. Local Name "link" occurs 6 times
    6. Local Name "personnel" occurs 1 times
    7. Local Name "given" occurs 6 times

    你看到电子邮件只发生了五次,而personal-schema.xml中有六个元素。因此,因为我们将电子邮件元素的最小出现次数设置为 1 person元素,我们知道此文档无效。但是,由于没有告知SAXLocalNameCount对模式进行验证,因此不会报告错误。

  8. Run SAXLocalNameCount again, this time specifying that the personal—schema.xml document should be validated against a the personal.xsd schema definition.

    正如您在上面的验证 XML Schema 中所看到的, SAXLocalNameCount有一个启用模式验证的选项。使用以下命令运行SAXLocalNameCount

    % java sax/SAXLocalNameCount -xsd data/personal-schema.xml

    这一次,您将看到以下错误消息。

    1. Exception in thread "main" org.xml.sax.SAXException: Error:
    2. URI=file:install_dir/samples/data/personal-schema.xml
    3. Line=19: cvc-complex-type.2.4.a: Invalid content was found starting with
    4. element 'link'.
    5. One of '{email}' is expected.
  9. 电子邮件元素恢复为元素one.worker

  10. Run SAXLocalNameCount a third time, again specifying that the personal—schema.xml document should be validated against a the personal.xsd schema definition.% java sax/SAXLocalNameCount -xsd data/personal-schema.xml

    这次您将看到正确的输出,没有错误。

  11. 再次在文本编辑器中打开personal-schema.xml

  12. Delete the declaration of the schema definition personal.xsd from the personnel element.

    人员元素中删除斜体代码。

    <personnel _xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation='personal.xsd'/_>

  13. Run SAXLocalNameCount, again specifying schema validation.% java sax/SAXLocalNameCount -xsd data/personal-schema.xml

    显然,这不起作用,因为尚未声明用于验证 XML 文件的模式定义。您将看到以下错误。

    1. Exception in thread "main" org.xml.sax.SAXException:
    2. Error: URI=file:install_dir/samples/data/personal-schema.xml
    3. Line=8: cvc-elt.1: Cannot find the declaration of element 'personnel'.
  14. Run SAXLocalNameCount again, this time passing it the schema definition file at the command line.% java sax/SAXLocalNameCount -xsdss data/personal.xsd data/personal-schema.xml

    这次使用SAXLocalNameCount选项,该选项允许您指定未硬编码到应用程序中的模式定义。你应该看到正确的输出。