1. 概述

在这个教程中,我们将演示如何使用Java验证一个XML文件与XSD文件的匹配性。

2. XML和XSD文件的定义

考虑以下包含姓名和地址的XML文件baeldung.xml,地址由邮政编码和城市组成:

<?xml version="1.0" encoding="UTF-8" ?>
<individual>
    <name>Baeldung</name>
    <address>
        <zip>00001</zip>
        <city>New York</city>
    </address>
</individual>

baeldung.xml的内容完全符合person.xsd文件的描述:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="individual">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string" />
                <xs:element name="address">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="zip" type="xs:positiveInteger" />
                            <xs:element name="city" type="xs:string" />
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

然而,我们的XML文件不符合full-person.xsd文件的要求:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="individual">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name">
                    <xs:simpleType>
                        <xs:restriction base="xs:string">
                            <xs:maxLength value="5" />
                        </xs:restriction>
                    </xs:simpleType>
                </xs:element>
                <xs:element name="address">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="zip" type="xs:positiveInteger" />
                            <xs:element name="city" type="xs:string" />
                            <xs:element name="street" type="xs:string" />
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

有两个问题:

  • 姓名属性的最大长度限制为5个字符
  • 地址期望有一个街道属性

接下来,我们将展示如何使用Java获取这些信息。

3. 验证XML文件与XSD文件

javax.xml.validation包定义了XML文档验证的API。

首先,我们将准备一个能够读取遵循XML Schema 1.0规范的文件的SchemaFactory。然后,我们将使用这个SchemaFactory创建对应于我们的XSD文件的SchemaSchema表示一组约束。

最后,我们从Schema中获取ValidatorValidator是一个处理器,用于检查XML文档与Schema的匹配:

private Validator initValidator(String xsdPath) throws SAXException {
    SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    Source schemaFile = new StreamSource(getFile(xsdPath));
    Schema schema = factory.newSchema(schemaFile);
    return schema.newValidator();
}

在这个代码中,getFile方法允许我们将XSD读入File。在示例中,我们会将文件放在资源目录下,所以这个方法读取的是:

private File getFile(String location) {
    return new File(getClass().getClassLoader().getResource(location).getFile());
}

值得注意的是,当我们创建Schema时,如果XSD文件无效,可能会抛出SAXException

现在我们可以使用Validator验证XML文件是否符合XSD描述。 validate方法要求我们将File转换为StreamSource

public boolean isValid() throws IOException, SAXException {
    Validator validator = initValidator(xsdPath);
    try {
        validator.validate(new StreamSource(getFile(xmlPath)));
        return true;
    } catch (SAXException e) {
        return false;
    }
}

如果解析过程中出现错误,validate方法会抛出SAXException,这表明XML文件不符合XSD规范。

validate方法也可能抛出IOException,如果在读取File时发生底层问题。

现在,我们可以将这些代码封装到XmlValidator类中,并检查baeldung.xml符合person.xsd的描述,但不匹配full-person.xsd

@Test
public void givenValidXML_WhenIsValid_ThenTrue() throws IOException, SAXException {
    assertTrue(new XmlValidator("person.xsd", "baeldung.xml").isValid());
}

@Test
public void givenInvalidXML_WhenIsValid_ThenFalse() throws IOException, SAXException {
    assertFalse(new XmlValidator("full-person.xsd", "baeldung.xml").isValid());
}

4. 列出所有验证错误

validate方法的基本行为是在解析抛出SAXException时退出。

现在我们想要收集所有验证错误,需要改变这种行为。为此,我们需要自定义一个ErrorHandler

public class XmlErrorHandler implements ErrorHandler {

    private List<SAXParseException> exceptions;

    public XmlErrorHandler() {
        this.exceptions = new ArrayList<>();
    }

    public List<SAXParseException> getExceptions() {
        return exceptions;
    }

    @Override
    public void warning(SAXParseException exception) {
        exceptions.add(exception);
    }

    @Override
    public void error(SAXParseException exception) {
        exceptions.add(exception);
    }

    @Override
    public void fatalError(SAXParseException exception) {
        exceptions.add(exception);
    }
}

然后,我们可以告诉Validator使用这个特定的ErrorHandler

public List<SAXParseException> listParsingExceptions() throws IOException, SAXException {
    XmlErrorHandler xsdErrorHandler = new XmlErrorHandler();
    Validator validator = initValidator(xsdPath);
    validator.setErrorHandler(xsdErrorHandler);
    try {
        validator.validate(new StreamSource(getFile(xmlPath)));
    } catch (SAXParseException e) 
    {
        // ...
    }
    xsdErrorHandler.getExceptions().forEach(e -> LOGGER.info(String.format("Line number: %s, Column number: %s. %s", e.getLineNumber(), e.getColumnNumber(), e.getMessage())));
    return xsdErrorHandler.getExceptions();
}

由于baeldung.xml满足person.xsd的要求,所以在这种情况下不会列出任何错误。但是,当使用full-person.xsd时,我们将打印以下错误消息:

XmlValidator - Line number: 3, Column number: 26. cvc-maxLength-valid: Value 'Baeldung' with length = '8' is not facet-valid with respect to maxLength '5' for type '#AnonType_nameindividual'.
XmlValidator - Line number: 3, Column number: 26. cvc-type.3.1.3: The value 'Baeldung' of element 'name' is not valid.
XmlValidator - Line number: 7, Column number: 15. cvc-complex-type.2.4.b: The content of element 'address' is not complete. One of '{street}' is expected. 

我们在第1部分提到的所有错误都被程序找到了。

5. 致命错误

Furthermore, the custom error handler XmlErrorHandler catches “Fatal errors” related to validation. Fatal errors indicate that the XML document being validated contains formatting errors, which means that it is not well-formed XML. To demonstrate, let’s introduce a formatting error in the XML document baeldung.xml by removing the closing tag:

<?xml version="1.0" encoding="UTF-8" ?>
<individual>
    <name>Baeldung</name>
    <address>
        <zip>00001</zip>
        <city>New York</city>
</individual>

Validation with full-person.xsd, will include the following error message:

[main] INFO XmlValidator - Line number: 1, Column number: 126. The element type
"address" must be terminated by the matching end-tag "</address>".

However, the validation can’t catch and accumulate fatal errors due to formatting errors in the XML Schema. This is because the XML file validation against the XML Schema can’t be performed. To demonstrate, let’s introduce a formatting error in the XML Schema full-person.xsd by removing the closing tag just before the closing tag. Let’s perform XML file validation with the XML Schema:

Exception in thread "main" org.xml.sax.SAXParseException; 
systemId: file:/C:/full-person.xsd; lineNumber: 1; columnNumber: 593; The element type
"xs:element" must be terminated by the matching end-tag "". at org.apache.xerces.util.ErrorHandlerWrapper.createSAXParseException(Un
known Source) at org.apache.xerces.util.ErrorHandlerWrapper.fatalError(Unknown Source)

This time the SAXParseException can’t be caught. Therefore, it is thrown regardless of the error handler we configured.

6. 总结

在这篇文章中,我们了解了如何验证XML文件与XSD文件,并且还能列出所有验证错误。

如往常一样,代码可以在GitHub上找到。