首页 > 解决方案 > XSD 验证使用 XPath 向 XML 节点抛出异常

问题描述

我正在尝试针对 XSD 实施 XML 验证,该验证应该使用 XPath 捕获所有违反 XSD 的 XML 节点的违规行为。我的代码:

package com.xsdvalidator.poc;

import java.io.File;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.transform.sax.SAXSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class Xsd_Validator {

    static int errorCount = 0;
    static List<String> errors = new ArrayList<String>();
    static Validator validator = null;

    public static String Validate(String xmlString, String xsdPath, String delimeter) {
        try {
            if (validator == null) {
                loadSchema(xsdPath);
            }
            SAXSource source = new SAXSource(new InputSource(new StringReader(xmlString)));
            validator.validate(source);
            if (errorCount > 0) {
                return "Error occurred (" + errorCount/2 + ") while validating XML. " + String.join(delimeter, errors);
            }
        } catch (Exception e) {
            return "Error occurred while validating XML. " + e.toString();
        } finally {
            errorCount = 0;
            errors = new ArrayList<String>();
        }
        return "";
    }

    private static void loadSchema(String name) throws SAXException {
        String language = XMLConstants.W3C_XML_SCHEMA_NS_URI;
        SchemaFactory factory = SchemaFactory.newInstance(language);
        Schema schema = factory.newSchema(new File(name));
        validator = schema.newValidator();
        validator.setErrorHandler(new ExceptionCatcher());
    }

    static class ExceptionCatcher implements ErrorHandler {
        public void fatalError(SAXParseException e) throws SAXException {
            errors.add(e.toString());
            errorCount++;
        }

        public void error(SAXParseException e) throws SAXException {
            errors.add(e.getMessage());
            errorCount++;
        }

        public void warning(SAXParseException e) throws SAXException {
            errors.add(e.getMessage());
            errorCount++;
        }
    }
}

XSD:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning"
            elementFormDefault="qualified"
            vc:minVersion="1.1">
    <xsd:element name="Employees">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element maxOccurs="unbounded" minOccurs="1" ref="Employee" />
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Employee">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element maxOccurs="1" minOccurs="1" ref="Name" />
                <xsd:element maxOccurs="1" minOccurs="1" ref="EmployeeID" />
                <xsd:element maxOccurs="1" minOccurs="1" ref="EmailID" />
                <xsd:element maxOccurs="1" minOccurs="1" ref="PhoneNumber" />
                <xsd:element maxOccurs="1" minOccurs="1" ref="JoiningDate" />
                <xsd:element maxOccurs="1" minOccurs="0" ref="Work" />
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Name">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element maxOccurs="1" minOccurs="1" type="FirstNameType" name="First" />
                <xsd:element maxOccurs="1" minOccurs="0" type="MiddleNameType" name="Middle" />
                <xsd:element maxOccurs="1" minOccurs="1" type="LastNameType" name="Last" />
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="EmployeeID" type="EmployeeIDType" />
    <xsd:element name="EmailID" type="EmailType" />
    <xsd:element name="PhoneNumber" type="PhoneNumberType" />
    <xsd:element name="JoiningDate" type="xsd:dateTime" />
    <xsd:element name="Work" type="WorkType" />
    <!-- Custom Data Types -->
    <xsd:simpleType name="FirstNameType">
        <xsd:restriction base="xsd:string">
            <xsd:whiteSpace value="collapse" />
            <xsd:maxLength value="20" />
            <xsd:minLength value="2" />
            <xsd:pattern value="[A-Za-z ]+" />
        </xsd:restriction>
    </xsd:simpleType>
    <xsd:simpleType name="MiddleNameType">
        <xsd:restriction base="xsd:string">
            <xsd:whiteSpace value="collapse" />
            <xsd:length value="1"/>
            <xsd:pattern value="[A-Za-z ]+" />
        </xsd:restriction>
    </xsd:simpleType>
    <xsd:simpleType name="LastNameType">
        <xsd:restriction base="xsd:string">
            <xsd:whiteSpace value="collapse" />
            <xsd:maxLength value="12" />
            <xsd:minLength value="2" />
            <xsd:pattern value="[A-Za-z ]+" />
        </xsd:restriction>
    </xsd:simpleType>
    <xsd:simpleType name="EmployeeIDType">
        <xsd:restriction base="xsd:integer">
            <xsd:whiteSpace value="collapse" />
            <xsd:pattern value="\d{1,12}" />
        </xsd:restriction>
    </xsd:simpleType>
    <xsd:simpleType name="EmailType">
        <xsd:restriction base="xsd:string">
            <xsd:pattern value="[^@]+@[^\.]+\..+|" />
        </xsd:restriction>
    </xsd:simpleType>
    <xsd:simpleType name="PhoneNumberType">
        <xsd:restriction base="xsd:integer">
            <xsd:whiteSpace value="collapse" />
            <xsd:pattern value="\d{1,10}" />
        </xsd:restriction>
    </xsd:simpleType>
    <xsd:simpleType name="WorkType">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration value="PERMANENT"/>
            <xsd:enumeration value="TEMPORARY"/>
            <xsd:enumeration value="CONTRACT"/>
        </xsd:restriction>
    </xsd:simpleType>
</xsd:schema>

示例 XML:

<?xml version="1.0" encoding="utf-8"?>
<Employees>
    <Employee>
        <Name>
            <First>Dev1</First>
            <Middle>K</Middle>
            <Last>Das</Last>
        </Name>
        <EmployeeID>12345</EmployeeID>
        <EmailID>dev.d@email.com</EmailID>
        <PhoneNumber>8777000000</PhoneNumber>
        <JoiningDate>2019-10-31T10:00:00</JoiningDate>
        <Work>PERMANENT</Work>
    </Employee>
    <Employee>
        <Name>
            <First>James</First>
            <Last>Bond2</Last>
        </Name>
        <EmployeeID>121212</EmployeeID>
        <EmailID>james.bond@email.com</EmailID>
        <PhoneNumber>8777111111</PhoneNumber>
        <JoiningDate>2019-10-31T10:00:00</JoiningDate>
        <Work>PERMANENT</Work>
    </Employee>
</Employees>

这给了我一个例外,例如:

Error occurred (2) while validating XML. cvc-pattern-valid: Value 'Dev1' is not facet-valid with respect to pattern '[A-Za-z ]+' for type 'FirstNameType'.¡cvc-type.3.1.3: The value 'Dev1' of element 'First' is not valid.¡cvc-pattern-valid: Value 'Bond2' is not facet-valid with respect to pattern '[A-Za-z ]+' for type 'LastNameType'.¡cvc-type.3.1.3: The value 'Bond2' of element 'Last' is not valid.

错误是正确的,但我想要违反规则的元素的 XPath。例如:

Error occurred (2) while validating XML. cvc-pattern-valid: Value 'Dev1' is not facet-valid with respect to pattern '[A-Za-z ]+' for type 'FirstNameType'.¡cvc-type.3.1.3: The value 'Dev1' of element '/Employees[1]/Employee[1]/Name[1]/First[1]' is not valid.¡cvc-pattern-valid: Value 'Bond2' is not facet-valid with respect to pattern '[A-Za-z ]+' for type 'LastNameType'.¡cvc-type.3.1.3: The value 'Bond2' of element '/Employees[1]/Employee[2]/Name[1]/Last[1]' is not valid.

可能吗?任何建议/代码都会有所帮助。谢谢。

标签: javaxmlvalidationxsd

解决方案


您实际上并没有说明您正在使用哪个模式验证器,并且 JAXP 规范没有定义 SAXParseException 中可能可用的所有细节。如果您使用的是 Saxon 的模式验证器,则异常对象可能包含两个路径(尽管详细信息没有详细记录):无效元素的路径和导致其无效的元素的路径。例如,如果 FOO 元素包含 FOO 的内容模型不允许的 BAR 元素,那么您将获得指向无效元素 (FOO) 的路径和指向错误位置 (BAR) 的路径。


推荐阅读