# 实现 SAX 验证

> 原文： [https://docs.oracle.com/javase/tutorial/jaxp/sax/validation.html](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（）`方法的以下代码显示了如何配置工厂以使其实现验证解析器。

```
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` 也被添加到`用法（）`方法中，但是这里没有显示。）然后，工厂被配置为当调用 `newSAXParser` 时它将生成适当的验证解析器。如[设置解析器](parsing.html#gclmt)中所示，您还可以使用 `setNamespaceAware（true）`将工厂配置为返回名称空间感知解析器。 Sun 的实现支持任何配置选项组合。 （如果特定实现不支持组合，则需要生成出厂配置错误）。

## 使用 XML Schema 进行验证

尽管 XML Schema 的完整处理超出了本教程的范围，但本节将向您展示使用 XML Schema 语言编写的现有模式验证 XML 文档的步骤。要了解有关 XML Schema 的更多信息，您可以在 [http://www.w3.org/TR/xmlschema-0/](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` 示例设置以下常量。

```
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";
}

```

* * *

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

* * *

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

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

```

除了[设置错误处理](parsing.html#gcnsr)中描述的错误处理之外，在为基于模式的验证配置解析器时可能会发生一个错误。如果解析器不符合 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 文档相关联。有两种方法可以做到这一点。

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

* * *

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

* * *

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

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

```

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

* * *

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

* * *

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

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

// ...

```

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

## 验证分析器中的错误处理

重要的是要认识到，当文件验证失败时抛出异常的唯一原因是[设置错误处理](parsing.html#gcnsr)中显示的错误处理代码。该代码在此处转载为提醒：

```
// ...

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 警告

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

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

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

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

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

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

*   没有`&lt;！DOCTYPE ...&gt;` 验证时。

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

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

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

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

## 尝试 DTD 验证错误

如上所述，这些示例重用 `SAXLocalNameCount` 程序。您可以在[运行没有验证的 SAX 分析器示例](parsing.html#gcnrx)中找到您将找到样本及其相关文件的位置。

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

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

4.  **Delete the declaration `&lt;!DOCTYPE PLAY SYSTEM "play.dtd"&gt;` from the beginning of the file.**

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

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

    为此，您必须在运行程序时指定 `-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` ，因此它自动无效。换句话说，消息是说您正在尝试验证文档，但是没有声明 DTD，因为没有 `DOCTYPE` 声明存在。所以现在您知道 DTD 是有效文档的要求。那讲得通。

6.  **Restore the `&lt;!DOCTYPE PLAY SYSTEM "play.dtd"&gt;` 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.**

    从`&lt; PERSONA&gt;更改开始和结束标记`和`&lt; / PERSONA&gt;` 至`&lt; PERSON&gt;` 和`&lt; / PERSON&gt;` 。第 26 行现在看起来像这样：

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

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

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

    这次，运行程序时会看到不同的错误：

    ```
    % 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` 中的元素。

9.  **In `data/rich_iii.xml` correct the tags for "KING EDWARD The Fourth".**

    将开始和结束标记返回到其原始版本，`&lt; PERSONA&gt;` 和`&lt; / PERSONA&gt;` 。

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

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

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

    和以前一样，您将看到另一个验证错误：

    ```
    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)+)".

    ```

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

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

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

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

    ```

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

13.  **Add a question mark (`?`) key after `TITLE` in the declaration of `&lt;PERSONAE&gt;`.**

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

    ```
    &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 分析器示例](parsing.html#gcnrx)中找到您将找到样本及其相关文件的位置。

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

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

    `&lt;personnel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation='personal.xsd'&gt;`
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`.**

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

    `&lt;xs:element ref="email" minOccurs='1' maxOccurs='unbounded'/&gt;`
6.  **In `data/personal-schema.xml`, delete the `email` element from the `person` element `one.worker`.**

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

    ```
    &lt;person id="one.worker"&gt;
      &lt;name&gt;&lt;family&gt;Worker&lt;/family&gt; &lt;given&gt;One&lt;/given&gt;&lt;/name&gt;
      &lt;link manager="Big.Boss"/&gt;
    &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` 中每个元素出现的次数。

    ```
    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

    ```

    你看到`电子邮件`只发生了五次，而 `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](#gcwtl) 中所看到的， `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.

    ```

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.**

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

    `&lt;personnel _xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation='personal.xsd'/_&gt;`
13.  **Run `SAXLocalNameCount`, again specifying schema validation.**`% 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'.

    ```

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` 选项，该选项允许您指定未硬编码到应用程序中的模式定义。你应该看到正确的输出。