根据多个模式定义验证XML文件

37

我正尝试使用多个不同的模式对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”解析为“group”组件。

我已经使用Python成功验证了此问题,但在使用Java 6.0Xerces 2.8.1时遇到了实际问题。 有人可以建议出现了什么错误,或者提供一种更简单的方法来验证我的XML文档吗?

8个回答

18

如果有人遇到同样的问题,我需要从一个单元测试中以资源的形式加载父模式(和隐式的子模式)来验证XML字符串。我使用Xerces XMLSchemFactory和Java 6验证器来实现这一点。

为了正确地通过include加载子模式,我必须编写自定义资源解析器。代码可以在这里找到:

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

要使用解析器,请在模式工厂上指定它:

xmlSchemaFactory.setResourceResolver(new ResourceResolver());

并且它将使用类路径(在我的情况下是从src/main/resources)来解析您的资源。欢迎对此进行任何评论...


4
能否进一步阐述一下自定义资源解析器是如何使这一切工作的?谢谢。 - Casey
我可以补充一下,您需要添加类似于以下内容的代码:<xsd:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="/xmldsig-core-schema.xsd" /> 到使用 new StreamSource(this.getClass().getResourceAsStream("parent.xsd") 加载的父XSD中。 - Jaime Hablutzel
1
你是否创建了一个“人工”的父模式,导入了所有其他模式? - zedoo
2
链接不再有效,但我在这里发现了代码示例:http://code.google.com/p/xmlsanity/source/browse/src/com/arc90/xmlsanity/validation/ResourceResolver.java?r=03a92d97f15904b3892922e45724bb086d54fa4e。 - Tom Saleeba
2
据我所见,代码现在在此处:https://github.com/arc90/xmlsanity/blob/master/src/main/java/com/arc90/xmlsanity/util/ClassBasedResourceResolver.java。 - beat
Google Code已经停用,请将您的代码迁移到其他地方。 - Holger Jakobs

7

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"));

对我有用。只需记得在包含模式具有不同目标命名空间的情况下使用import。 - fer.marino

2
从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);

你的回答是否涉及验证多个模式?也许你应该添加一个循环来描述它。 - Ragunath Jawahar
1
值得一提的是,这里模式的顺序很重要。如果 A 导入了 B,则在创建模式时使用的数组中必须先有 B 再有 A。 - Hedley
这留下了最重要的问题未解答:如何获取应用程序创建的schemaDocuments数组? - Holger Jakobs
Hedley you are the MA" - Apurva Singh

2

我遇到了同样的问题,在调查后找到了这个解决方案。对我很有效。

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.");
}

2
在Xerces中的模式(schema)相关内容非常严格,同时当它不喜欢所发现的内容时会给出完全没有用处的错误信息,这是一个令人沮丧的组合。
在Python中的模式(schema)相关内容可能会更加宽容,并且会让小错误在模式(schema)中不被报告。
现在,如果像您所说的那样,c.xsd包含b.xsd,而b.xsd包含a.xsd,则无需将所有三个文件加载到模式工厂中。这不仅是不必要的,而且很可能会让Xerces感到困惑并导致错误,因此这可能是您遇到的问题。只需将c.xsd传递给工厂,让它自行解析b.xsd和a.xsd,它应该相对于c.xsd进行解析。

是的,这似乎也导致了相同的错误。我在想模式文件中的导入声明是否会引起问题...两个模式都没有目标命名空间也没有帮助... 真烦人! - Jonathan Holloway
也许解决这个问题的一种方法是使用ResourceResolver并将其设置在架构工厂上... - Jonathan Holloway
4
你确定你没有混淆 import 和 include 吗?它们有着不同的含义,不应混淆。a、b 和 c 是否在不同的命名空间中?如果是,那么它们应该被导入(import),而不是被包含(include)。如果它们在同一个命名空间中,那么它们应该被包含(include)。 - skaffman
1
我并没有像这样编写模式,也不能更改它们,使用了include - 它们位于不同的命名空间中 - 不太确定为什么。最终,我不得不编写一个自定义解析器并导入根模式才能使它正常工作...但无论如何感谢您在加载根模式方面的指针... - Jonathan Holloway
1
@skaffman 我了解到XSD的顺序可能很重要。例如,我有两个xsd文件a.xsd和b.xsd。在我的xml文件中,首先使用属于a.xsd的命名空间,下一个命名空间属于b.xsd。因此,我必须对xml文件进行a.xsd、b.xsd(而不是b.xsd、a.xsd)的验证。但我是手动检测到这一点的。如何自动检测它? - limonik

1
我最终使用了这个:
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)参数"http://your_url_schema_location"为您的xsd文件位置。

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

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


1

以防万一,如果有人仍然来到这里寻找针对多个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
如果所有的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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接