对于未解决的 xsi:type,Xerces-J 能否通过宽松验证是否正确?

Is Xerces-J correct to fail lax validation for an unresolved xsi:type?

我在使用这段代码(如下)验证此 SOAP Envelope 时遇到问题。

我得到的错误是:

org.xml.sax.SAXParseException; cvc-elt.4.2: Cannot resolve 'ipo:UKAddress' to a type definition for element 'shipTo'.

SOAP XSD 将 Body 定义为:

<xs:complexType name="Body">
<xs:sequence>
<xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
</xs:sequence>

我的期望是 "lax" 应该验证它是否有定义,但如果没有则忽略。但是,xsi:type="ipo:UKAddress" 的情况并非如此。我只验证 SOAP Envelope - 而不是 Body.

这看起来像是 xerces-j 中的错误。在同一块代码中,XMLSchemaValidator.java:2152 在引发错误之前实际上检查了 processContents:

else if (wildcard != null && wildcard.fProcessContents == XSWildcardDecl.PC_STRICT) {

然而,XMLSchemaValidator.java:2178 不进行此类检查,无论如何都会抛出。

fCurrentType = getAndCheckXsiType(element, xsiType, attributes);

对我来说,它看起来像是 xerces-j 中的一个错误。此外,此问题存在于 Java 8 中。感谢任何帮助或确认这确实是一个错误。

package com.example.xmlvalidate;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.CodeSource;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class Validate {
    private static final String envelope =
            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
            "<soapenv:Envelope \n" +
            "  xmlns=\"http://www.w3.org/2001/XMLSchema\"" +
            "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
            "  xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"\n" +
            "  >\n" +
            "  <soapenv:Body>\n" +
            "    <ipo:purchaseOrder xmlns:ipo=\"http://www.example.com/IPO\">\n" +
            "      <shipTo exportCode=\"1\" xsi:type=\"ipo:UKAddress\">\n" +
            "        <name>Helen Zoe</name>\n" +
            "        <street>47 Eden Street</street>\n" +
            "        <city>Cambridge</city>\n" +
            "        <postcode>CB1 1JR</postcode>\n" +
            "      </shipTo>\n" +
            "    </ipo:purchaseOrder>\n" +
            "  </soapenv:Body>\n" +
            "</soapenv:Envelope>";

    private static final String SOAP_1_1_ENVELOPE =
            "http://schemas.xmlsoap.org/soap/envelope";
    protected static final String W3C_XML_SCHEMA =
            "http://www.w3.org/2001/XMLSchema";

    public static void validate() throws ParserConfigurationException, SAXException, IOException, TransformerException {
        final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

        documentBuilderFactory.setNamespaceAware(true);
        documentBuilderFactory.setValidating(false);

        final Class<?> clazz = documentBuilderFactory.getClass();
        final CodeSource source = clazz.getProtectionDomain().getCodeSource();
        System.out.println("Document builder implementation: " + clazz.getName() + " from : " + (source == null ? "JRE" : source));

        final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        final InputStream is = new ByteArrayInputStream(envelope.getBytes(StandardCharsets.UTF_8));
        final Document document = documentBuilder.parse(is);
        final DOMSource domSource = new DOMSource(document);

        final StreamSource streamSource = new StreamSource(new URL(SOAP_1_1_ENVELOPE).openStream());
        final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        final Schema schema = schemaFactory.newSchema(streamSource);

        final Validator validator = schema.newValidator();
        validator.validate(domSource);
    }
}

简短回答:在这种情况下,Xerces 要么毫无疑问是正确的,要么不能证明是错误的。

长答案:

您没有指定您使用的是 XSD 1.0 还是 XSD 1.1。

在 1.0 中,规范对于 QName 值未解析为架构中的类型定义的 xsi:type 属性的有效性的影响略有不明确。 Validation Rule: Schema-Validity Assessment (Element) in 3.3.4 的一种自然解读是,当 xsi:type 发生时,它 必须 解决(注意在一致性要求和有效性要求之间的文本)。对该规则的另一种解读是,如果验证规则的第 1.2.1.2.3 条不适用,则显然第 1.2.1.2、1.2 和 1 条不适用,这导致得出元素应该是 laxly 的结论评估。

相同的两个读数适用于验证规则的第 4.2 节:元素局部有效(元素)在同一节中。该子句表示 xsi:type "must resolve to a type definition" 的值,这意味着如果 xsi:type 值未解析,则该元素无效,或者(根据对规则的不同解读)在这种情况下,该元素显然不是(已知)针对指定类型在本地有效。

在 1.1 中,规则已被重写并且可能更加清晰。如果 xsi:type 的值是一个不解析为类型定义的 QName,则计算回退类型,并根据回退类型验证元素;在您似乎想到的情况下,该类型将是 xsd:anyType。但是 1.1 也非常明确地指出,在那种情况下 xsi:type 属性本身是无效的(验证规则第 5 条:属性在 3.2.4.

中局部有效

因此,根据 XSD 1.1 的规则,很明显 Xerces 正确地将输入标记为无效,尽管错误代码可能更像是一个不同的代码。

如果您使用 XSD 1.0 进行操作,从错误代码中可以清楚地看出 Xerces 正在对非解析 xsi:type 值进行第一个视图,并将其视为有效性错误。我认为很难从规范文本中证明这是唯一可能的解释,但更难证明它是错误的:这显然是对规范的合理解释。如果您希望 xsi:type 的问题被忽略并且不被视为有效性错误,您需要跳过通配符,而不是宽松通配符。 (您当然可以为 SOAP 负载声明您自己的元素包装器,在其内容模型中使用跳过通配符声明它,从而强制执行您想要的验证行为。)