根据多个架构定义验证 XML 文件

Validate an XML File Against Multiple Schema Definitions

提问人:Jonathan Holloway 提问时间:7/8/2009 更新时间:6/14/2020 访问量:38913

问:

我正在尝试根据许多不同的模式验证XML文件(为人为的例子道歉):

  • a.xsd
  • b.xsd
  • c.xsd

特别是 c.xsd 导入 b.xsd 和 b.xsd 导入 a.xsd,使用:

<xs:include schemaLocation="b.xsd"/>

我正在尝试通过 Xerces 以以下方式执行此操作:

XMLSchemaFactory xmlSchemaFactory = new XMLSchemaFactory();
Schema schema = xmlSchemaFactory.newSchema(new StreamSource[] { new StreamSource(this.getClass().getResourceAsStream("a.xsd"), "a.xsd"),
                                                         new StreamSource(this.getClass().getResourceAsStream("b.xsd"), "b.xsd"),
                                                         new StreamSource(this.getClass().getResourceAsStream("c.xsd"), "c.xsd")});     
Validator validator = schema.newValidator();
validator.validate(new StreamSource(new StringReader(xmlContent)));

但这无法正确导入所有三个模式,导致无法将名称“blah”解析为(n)“组”组件。

我已经使用 Python 成功验证了这一点,但在 Java 6.0Xerces 2.8.1 中遇到了真正的问题。任何人都可以建议这里出了什么问题,或者一种更简单的方法来验证我的 XML 文档?

Java XSD Xerces

评论


答:

2赞 skaffman 7/8/2009 #1

Xerces 中的模式内容是 (a) 非常非常迂腐的,并且 (b) 当它不喜欢它找到的东西时,会给出完全无用的错误消息。这是一个令人沮丧的组合。

python 中的模式内容可能更加宽容,并且让模式中的小错误没有报告。

现在,如果如您所说,c.xsd 包含 b.xsd,而 b.xsd 包含 a.xsd,则无需将所有三个都加载到架构工厂中。这不仅没有必要,而且可能会混淆 Xerces 并导致错误,所以这可能是您的问题。只需将 c.xsd 传递给工厂,让它解析 b.xsd 和 a.xsd 本身,它应该相对于 c.xsd 执行此操作。

评论

0赞 Jonathan Holloway 7/8/2009
是的,这似乎也会导致同样的错误。我想知道架构文件中的导入声明是否会导致问题......两个架构也没有目标命名空间也无济于事......加格
0赞 Jonathan Holloway 7/8/2009
也许解决此问题的方法之一是使用 ResourceResolevr 并将其设置在架构工厂上......
4赞 skaffman 7/8/2009
你确定你没有混淆导入和包含吗?它们意味着两种不同的东西,不应混淆。a、b 和 c 是否位于不同的命名空间中?如果是这样,那么它们应该被导入,而不是包括在内。如果它们位于同一命名空间中,则应包含它们。
1赞 Jonathan Holloway 7/9/2009
我没有这样编写架构,也无法更改它们,使用了包含 - 它们在不同的命名空间中 - 不太确定为什么。我必须编写一个自定义解析器并导入根架构才能最终使其正常工作......但是,无论如何,感谢您关于加载根架构的指针...
1赞 limonik 8/1/2016
@skaffman我了解到 XSD 的顺序可能很重要。例如,我有 2 个 xsd 文件 a.xsd 和 b.xsd。在我的 xml 文件中,首先使用属于 a.xsd 的命名空间,下一个命名空间属于 b.xsd。所以我必须根据 a.xsd,b.xsd(不是 b.xsd,a.xsd)验证 xml 文件,但我手动检测到了这一点。如何自动检测它?
18赞 Jonathan Holloway 7/10/2009 #2

因此,以防万一其他人在这里遇到同样的问题,我需要从单元测试中加载父架构(和隐式子架构) - 作为资源 - 以验证 XML 字符串。我使用 Xerces XMLSchemFactory 和 Java 6 验证器来做到这一点。

为了通过包含正确加载子架构,我必须编写一个自定义资源解析器。代码可以在这里找到:

https://code.google.com/p/xmlsanity/source/browse/src/com/arc90/xmlsanity/validation/ResourceResolver.java

若要使用解析程序,请在架构工厂中指定它:

xmlSchemaFactory.setResourceResolver(new ResourceResolver());

它将使用它通过类路径解析您的资源(在我的情况下来自 src/main/resources)。欢迎对此发表任何意见...

评论

4赞 Casey 11/12/2009
有没有机会进一步详细说明自定义资源解析器如何使这一切工作?谢谢。
0赞 Jaime Hablutzel 7/24/2012
我可以补充一点,您必须添加如下内容:在父 xsd 中加载了<xsd:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="/xmldsig-core-schema.xsd" /> new StreamSource(this.getClass().getResourceAsStream("parent.xsd")
1赞 zedoo 7/22/2013
您是否创建了一个“人工”父架构来导入所有其他父架构?
2赞 Tom Saleeba 2/19/2015
该链接不再起作用,但您我在这里找到了代码示例:code.google.com/p/xmlsanity/source/browse/src/com/arc90/...
2赞 beat 1/17/2017
据我所知,代码现在在这里:github.com/arc90/xmlsanity/blob/master/src/main/java/com/arc90/......
7赞 iolha 9/20/2010 #3

http://www.kdgregory.com/index.php?page=xml.parsing单个文档的多个架构”部分'

我基于该文档的解决方案:

URL xsdUrlA = this.getClass().getResource("a.xsd");
URL xsdUrlB = this.getClass().getResource("b.xsd");
URL xsdUrlC = this.getClass().getResource("c.xsd");

SchemaFactory schemaFactory = schemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
//---
String W3C_XSD_TOP_ELEMENT =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
   + "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" elementFormDefault=\"qualified\">\n"
   + "<xs:include schemaLocation=\"" +xsdUrlA.getPath() +"\"/>\n"
   + "<xs:include schemaLocation=\"" +xsdUrlB.getPath() +"\"/>\n"
   + "<xs:include schemaLocation=\"" +xsdUrlC.getPath() +"\"/>\n"
   +"</xs:schema>";
Schema schema = schemaFactory.newSchema(new StreamSource(new StringReader(W3C_XSD_TOP_ELEMENT), "xsdTop"));

评论

0赞 fer.marino 3/4/2015
为我工作。请记住使用导入,以防包含架构具有不同的目标命名空间
2赞 Hesse 3/28/2012 #4

来自 xerces 文档:http://xerces.apache.org/xerces2-j/faq-xs.html

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

...

StreamSource[] schemaDocuments = /* created by your application */;
Source instanceDocument = /* created by your application */;

SchemaFactory sf = SchemaFactory.newInstance(
    "http://www.w3.org/XML/XMLSchema/v1.1");
Schema s = sf.newSchema(schemaDocuments);
Validator v = s.newValidator();
v.validate(instanceDocument);

评论

0赞 Ragunath Jawahar 10/29/2012
您的答案是否涉及验证多架构?也许你应该添加一个循环来描述它。
1赞 Hedley 10/28/2015
值得一提的是,架构顺序在这里很重要。如果 A 导入 B,则在用于创建架构的数组中,必须在 A 之前有 B。
0赞 Holger Jakobs 4/6/2017
这留下了最重要的问题没有得到解答:如何获取应用程序创建的schemaDocuments数组?
0赞 Apurva Singh 9/26/2017
赫德利,你是MA”
1赞 Edenshaw 9/21/2012 #5

我最终使用了这个:

import org.apache.xerces.parsers.SAXParser;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.IOException;
 .
 .
 .
 try {
        SAXParser parser = new SAXParser();
        parser.setFeature("http://xml.org/sax/features/validation", true);
        parser.setFeature("http://apache.org/xml/features/validation/schema", true);
        parser.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);
        parser.setProperty("http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation", "http://your_url_schema_location");

        Validator handler = new Validator();
        parser.setErrorHandler(handler);
        parser.parse("file:///" + "/home/user/myfile.xml");

 } catch (SAXException e) {
    e.printStackTrace();
 } catch (IOException ex) {
    e.printStackTrace();
 }


class Validator extends DefaultHandler {
    public boolean validationError = false;
    public SAXParseException saxParseException = null;

    public void error(SAXParseException exception)
            throws SAXException {
        validationError = true;
        saxParseException = exception;
    }

    public void fatalError(SAXParseException exception)
            throws SAXException {
        validationError = true;
        saxParseException = exception;
    }

    public void warning(SAXParseException exception)
            throws SAXException {
    }
}

记得更改:

1) xsd 文件位置的参数“http://your_url_schema_location”。

2) 指向 xml 文件的字符串“/home/user/myfile.xml”。

我不必设置变量:-Djavax.xml.validation.SchemaFactory:http://www.w3.org/2001/XMLSchema=org.apache.xerces.jaxp.validation.XMLSchemaFactory

2赞 Weslor 11/2/2015 #6

我遇到了同样的问题,经过调查找到了这个解决方案。它对我有用。

Enum要设置不同的:XSDs

public enum XsdFile {
    // @formatter:off
    A("a.xsd"),
    B("b.xsd"),
    C("c.xsd");
    // @formatter:on

    private final String value;

    private XsdFile(String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }
}

验证方法:

public static void validateXmlAgainstManyXsds() {
    final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

    String xmlFile;
    xmlFile = "example.xml";

    // Use of Enum class in order to get the different XSDs
    Source[] sources = new Source[XsdFile.class.getEnumConstants().length];
    for (XsdFile xsdFile : XsdFile.class.getEnumConstants()) {
        sources[xsdFile.ordinal()] = new StreamSource(xsdFile.getValue());
    }

    try {
        final Schema schema = schemaFactory.newSchema(sources);
        final Validator validator = schema.newValidator();
        System.out.println("Validating " + xmlFile + " against XSDs " + Arrays.toString(sources));
        validator.validate(new StreamSource(new File(xmlFile)));
    } catch (Exception exception) {
        System.out.println("ERROR: Unable to validate " + xmlFile + " against XSDs " + Arrays.toString(sources)
                + " - " + exception);
    }
    System.out.println("Validation process completed.");
}
1赞 Shafiul 1/9/2020 #7

为了以防万一,仍然有人来这里寻找针对多个 XSD 验证 xml 或对象的解决方案,我在这里提到它

//Using **URL** is the most important here. With URL, the relative paths are resolved for include, import inside the xsd file. Just get the parent level xsd here (not all included xsds).

URL xsdUrl = getClass().getClassLoader().getResource("my/parent/schema.xsd");

SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(xsdUrl);

JAXBContext jaxbContext = JAXBContext.newInstance(MyClass.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setSchema(schema);

/* If you need to validate object against xsd, uncomment this
ObjectFactory objectFactory = new ObjectFactory();
JAXBElement<MyClass> wrappedObject = objectFactory.createMyClassObject(myClassObject); 
marshaller.marshal(wrappedShipmentMessage, new DefaultHandler());
*/

unmarshaller.unmarshal(getClass().getClassLoader().getResource("your/xml/file.xml"));
0赞 Dojo 6/14/2020 #8

如果所有 XSD 都属于同一命名空间,则创建一个新的 XSD 并将其他 XSD 导入其中。然后在 java 中使用新的 XSD 创建架构。

Schema schema = xmlSchemaFactory.newSchema(
    new StreamSource(this.getClass().getResourceAsStream("/path/to/all_in_one.xsd"));

all_in_one.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:ex="http://example.org/schema/" 
 targetNamespace="http://example.org/schema/" 
 elementFormDefault="unqualified"
 attributeFormDefault="unqualified">

    <xs:include schemaLocation="relative/path/to/a.xsd"></xs:include>
    <xs:include schemaLocation="relative/path/to/b.xsd"></xs:include>
    <xs:include schemaLocation="relative/path/to/c.xsd"></xs:include>

</xs:schema>