实现 SAX 验证

默认情况下,示例程序SAXLocalNameCount使用非验证解析器,但它也可以激活验证。激活验证使应用程序可以判断 XML 文档是否包含正确的标签或这些标签的 Sequences 是否正确。换句话说,它可以告诉您文档是否有效。但是,如果未激活验证,则只能告诉您文档的格式是否正确,如上一节中从 XML 元素中删除结束标记时所显示的那样。为了使验证成为可能,XML 文档需要与 DTD 或 XMLPattern 相关联。 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\(\)方法中的以下代码显示了如何配置工厂,以实现验证语法分析器。

static public void main(String[] args) throws Exception {

    String filename = null;
    boolean dtdValidate = false;
    boolean xsdValidate = false;
    String schemaSource = null;

    for (int i = 0; i < args.length; i++) {

        if (args[i].equals("-dtd")) {
            dtdValidate = true;
        }
        else if (args[i].equals("-xsd")) {
            xsdValidate = true;
        } 
        else if (args[i].equals("-xsdss")) {
            if (i == args.length - 1) {
               usage();
            }
            xsdValidate = true;
            schemaSource = args[++i];
        } 
        else if (args[i].equals("-usage")) {
            usage();
        }
        else if (args[i].equals("-help")) {
            usage();
        }
        else {
            filename = args[i];
            if (i != args.length - 1) {
                usage();
            }
        }
    }

    if (filename == null) {
        usage();
    }

    SAXParserFactory spf = SAXParserFactory.newInstance();
    spf.setNamespaceAware(true);
    spf.setValidating(dtdValidate || xsdValidate);
    SAXParser saxParser = spf.newSAXParser();

    // ... 
}

在这里,SAXLocalNameCount程序被配置为在启动时接受其他参数,这告诉该程序不对特定的架构源文件执行任何验证,DTD 验证,XML 架构定义(XSD)验证或 XSD 验证。 (这些选项\-dtd\-xsd\-xsdss的描述也已添加到usage\(\)方法中,但此处未显示.)然后,对工厂进行配置,以便在调用newSAXParser时将生成适当的验证解析器。如设置解析器所示,您还可以使用setNamespaceAware\(true\)将工厂配置为返回可识别名称空间的解析器。 Sun 的实现支持配置选项的任何组合。 (如果特定实现不支持组合,则需要生成工厂配置错误)。

使用 XMLPattern 进行验证

尽管对 XML Schema 的全面处理不在本教程的讨论范围之内,但是本节向您展示了使用 XML Schema 语言编写的现有 Pattern 来验证 XML 文档的步骤。要了解有关 XML Schema 的更多信息,可以在http://www.w3.org/TR/xmlschema-0/查看在线教程 XML Schema Part 0:Primer。


注意- 有多种 Pattern 定义语言,包括 RELAX NG,Schematron 和 W3C“ XML Schema”标准。 (即使 DTD 也是唯一不使用 XML 语法描述架构约束的 DTD,也被视为“架构”.)但是,“ XML Schema”给我们带来了术语挑战。尽管短语“ XML Schema schema”很精确,但我们将使用短语“ XML Schema definition”来避免出现冗余。


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

  • 必须在 SAX 解析器上设置适当的属性。

  • 必须设置适当的错误处理程序。

  • 该文档必须与 Pattern 关联。

设置 SAX 解析器属性

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

public class SAXLocalNameCount extends DefaultHandler {

    static final String JAXP_SCHEMA_LANGUAGE =
        "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
    
    static final String W3C_XML_SCHEMA =
        "http://www.w3.org/2001/XMLSchema";

    static final String JAXP_SCHEMA_SOURCE =
        "http://java.sun.com/xml/jaxp/properties/schemaSource";
}

注意- 必须将解析器工厂配置为生成一个可识别名称空间并进行验证的解析器。这已显示在配置工厂中。 文件对象模型中提供了有关名称空间的更多信息,但到目前为止,请了解架构验证是面向名称空间的过程。因为默认情况下,与 JAXP 兼容的解析器不支持名称空间,所以必须设置属性以使架构验证起作用。


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

// ...
if (xsdValidate) {
    saxParser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
    // ...
}

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

// ...
if (xsdValidate) {
    try {
        saxParser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
    }
    catch (SAXNotRecognizedException x){
        System.err.println("Error: JAXP SAXParser property not recognized: "
                           + JAXP_SCHEMA_LANGUAGE);

        System.err.println( "Check to see if parser conforms to the JAXP spec.");
        System.exit(1);
    }
}
// ...

将文档与架构相关联

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

  • 通过在 XML 文档中包括 Pattern 声明。

  • 通过指定在应用程序中使用的架构。


注意- 当应用程序指定要使用的架构时,它将覆盖文档中的任何架构声明。


要在文档中指定架构定义,您将创建如下所示的 XML:

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

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


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


您也可以在应用程序中指定架构文件,就像SAXLocalNameCount一样。

// ...
if (schemaSource != null) {
    saxParser.setProperty(JAXP_SCHEMA_SOURCE, new File(schemaSource));
}

// ...

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

验证解析器中的错误处理

重要的是要认识到,文件验证失败时引发异常的唯一原因是设置错误处理中显示的错误处理代码的结果。该代码在这里被提醒:

// ...

public void warning(SAXParseException spe) throws SAXException {
    out.println("Warning: " + getParseExceptionInfo(spe));
}
        
public void error(SAXParseException spe) throws SAXException {
    String message = "Error: " + getParseExceptionInfo(spe);
    throw new SAXException(message);
}

public void fatalError(SAXParseException spe) throws SAXException {
    String message = "Fatal Error: " + getParseExceptionInfo(spe);
    throw new SAXException(message);
}

// ...

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

DTD Warnings

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

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

  • 提供有关实体,属性或符号的其他声明。 (将忽略此类声明.仅使用第一个声明.此外,请注意,如先前所见,元素的重复定义在验证时始终会产生致命错误.)

  • 引用未声明的元素类型。 (仅当 XML 文档中实际使用了未声明的类型时,才会发生有效性错误.当在 DTD 中引用未声明的元素时,将产生警告.)

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

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

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

  • 未验证时引用未定义的参数实体。 (验证时,会产生错误.尽管读取参数实体不需要非验证解析器,但是 Java XML 解析器会这样做.由于这不是必需的,因此 Java XML 解析器会生成警告而不是错误.)

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

运行带有验证的 SAX 解析器示例

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

试验 DTD 验证错误

如上所述,这些示例重用了SAXLocalNameCount程序。 在没有验证的情况下运行 SAX Parser 示例中提供了找到示例及其相关文件的位置。

  • 如果尚未这样做,请导航至samples目录. % cd install-dir/jaxp-1_4_2-release-date/samples

  • 如果尚未这样做,请编译示例类. % javac sax/*

  • 在文本编辑器中打开文件data/rich_iii\.xml.

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

  • 从文件开头删除声明\<!DOCTYPE PLAY SYSTEM "play\.dtd"\>.

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

  • 运行SAXLocalNameCount程序,并激活 DTD 验证.

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

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

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

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

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


此消息表明没有可验证文档rich_iii\.txt的语法,因此它自动无效。换句话说,该消息表明您正在try验证文档,但是尚未声明 DTD,因为没有DOCTYPE声明。因此,现在您知道 DTD 是有效文档的要求。这就说得通了。

  • \<!DOCTYPE PLAY SYSTEM "play\.dtd"\>声明恢复为data/rich_iii\.xml.

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

  • 返回data/rich_iii\.xml并修改第 26 行中字符“ KING EDWARD The Third”的标签.

将开始和结束标记从\<PERSONA\>\</PERSONA\>更改为\<PERSON\>\</PERSON\>。第 26 行现在应如下所示:

26:<PERSON>KING EDWARD The Fourth</PERSON>

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

  • 运行SAXLocalNameCount程序,并激活 DTD 验证.

这次,您在运行程序时将看到另一个错误:

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

在这里,您可以看到解析器已反对 DTD data/play\.dtd中未包含的元素。

  • data/rich_iii\.xml中,更正“第四名爱德华”的标签.

将开始标签和结束标签恢复为原始版本\<PERSONA\>\</PERSONA\>

  • data/rich_iii\.xml中,从第 24 行中删除\<TITLE\>Dramatis Personae\</TITLE\>.

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

  • 运行SAXLocalNameCount程序,并激活 DTD 验证.

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

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

通过从第 24 行删除\<TITLE\>元素,\<PERSONAE\>元素将变为无效,因为它不包含 DTD 期望的\<PERSONAE\>元素的子元素。请注意,即使您从第 24 行删除了\<TITLE\>元素,该错误消息也指出该错误位于data/rich_iii\.xml的第 85 行。这是因为\<PERSONAE\>元素的结束标记位于第 85 行,并且解析器仅在以下情况时抛出异常:它到达要解析的元素的末尾。

  • 在文本编辑器中打开 DTD 文件data/play\.dtd.

在 DTD 文件中,您可以看到\<PERSONAE\>元素的声明,以及可以在符合播放 DTD 的 XML 文档中使用的所有其他元素。 \<PERSONAE\>的声明如下所示。

<!ELEMENT PERSONAE (TITLE, (PERSONA | PGROUP)+)>

如您所见,\<PERSONAE\>元素需要\<TITLE\>子元素。管道(\|)键表示\<PERSONA\>\<PGROUP\>子元素可以包含在\<PERSONAE\>元素中,而\(PERSONA \| PGROUP\)分组后的加号(+)意味着必须至少包含一个或多个这些子元素中的一个包括在内。

  • \<PERSONAE\>的声明中TITLE之后添加问号(?)键.

在 DTD 中的子元素声明中添加问号会使该子元素的一个实例的存在成为可选项。

<!ELEMENT PERSONAE (TITLE?, (PERSONA | PGROUP)+)>

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

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

  • 运行SAXLocalNameCount程序,并激活 DTD 验证. % java sax/SAXLocalNameCount -dtd data/rich_iii.xml

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

试验 Pattern 验证错误

先前的练习演示了使用SAXLocalNameCount对照 DTD 验证 XML 文件。在本练习中,您将使用SAXLocalNameCount对照标准 XML 架构定义和自定义架构源文件来验证另一个 XML 文件。同样,将通过修改 XML 文件和架构来破坏解析过程来演示这种类型的验证,以便解析器引发错误。

如上所述,这些示例重用了SAXLocalNameCount程序。 在没有验证的情况下运行 SAX Parser 示例中提供了找到示例及其相关文件的位置。

  • 如果尚未这样做,请导航至samples目录. % cd install-dir/jaxp-1_4_2-release-date/samples

  • 如果尚未这样做,请编译示例类. % javac sax/*

  • 在文本编辑器中打开文件data/personal\-schema\.xml.

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

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

  • 在文本编辑器中打开文件data/personal\.xsd.

该架构定义了关于每个员工需要什么样的信息,才能使与该架构关联的 XML 文档被视为有效。例如,通过检查架构定义,您可以看到每个person元素都需要一个name,并且每个人的姓名都必须包含family名称和given名称。员工还可以选择具有电子邮件地址和 URL。

  • data/personal\.xsd中,将person元素所需的最小电子邮件地址数从0更改为1.

email元素的声明现在如下。

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

  • data/personal\-schema\.xml中,从person元素one\.worker中删除email元素.

现在 Worker 一看起来像这样:

<person id="one.worker">
  <name><family>Worker</family> <given>One</given></name>
  <link manager="Big.Boss"/>
</person>
  • 针对personal\-schema\.xml运行SAXLocalNameCount,无需进行 Pattern 验证. % java sax/SAXLocalNameCount data/personal-schema.xml

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

Local Name "email" occurs 5 times
Local Name "name" occurs 6 times
Local Name "person" occurs 6 times
Local Name "family" occurs 6 times
Local Name "link" occurs 6 times
Local Name "personnel" occurs 1 times
Local Name "given" occurs 6 times

您会看到email仅发生五次,而personal\-schema\.xml中有六个person元素。因此,由于我们将email元素的最小出现次数设置为每个person元素 1,因此我们知道此文档无效。但是,由于未告知SAXLocalNameCount针对架构进行验证,因此不会报告任何错误。

  • 再次运行SAXLocalNameCount,这次指定应针对personal\.xsdPattern 定义验证personal—schema\.xml文档.

如您在上面的使用 XMLPattern 进行验证中所见,SAXLocalNameCount具有启用架构验证的选项。使用以下命令运行SAXLocalNameCount

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

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

Exception in thread "main" org.xml.sax.SAXException: Error: 
URI=file:install_dir/samples/data/personal-schema.xml 
Line=19: cvc-complex-type.2.4.a: Invalid content was found starting with 
element 'link'. 
One of '{email}' is expected.
  • email元素恢复到person元素one\.worker.

  • 第三次运行SAXLocalNameCount,再次指定应针对personal\.xsdPattern 定义验证personal—schema\.xml文档. % java sax/SAXLocalNameCount -xsd data/personal-schema.xml

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

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

  • personnel元素中删除 Pattern 定义personal\.xsd的声明.

personnel元素中删除斜体代码。

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

  • 运行SAXLocalNameCount,再次指定架构验证. % java sax/SAXLocalNameCount -xsd data/personal-schema.xml

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

Exception in thread "main" org.xml.sax.SAXException: 
Error: URI=file:install_dir/samples/data/personal-schema.xml 
Line=8: cvc-elt.1: Cannot find the declaration of element 'personnel'.
  • 再次运行SAXLocalNameCount,这次在命令行中将其传递给 Pattern 定义文件. % java sax/SAXLocalNameCount -xsdss data/personal.xsd data/personal-schema.xml

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