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. 总结

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

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