当schema来自classpath时,Java XML验证不起作用

时间:2012-06-12 13:14:20

标签: java xml xsd xml-validation

我正在根据模式验证XML文档。尝试使用此代码验证它们时,一些更复杂的文档/模式总是会失败:

    DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
    dbfac.setNamespaceAware(true);
    dbfac.setIgnoringElementContentWhitespace(true);
    DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
    Document doc = docBuilder.parse("sampleResponse.xml");

    SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    Source schemaSource = new StreamSource(getClass().getResourceAsStream("/" + "SampleResponse.xsd"));

    Schema schema = schemaFactory.newSchema(schemaSource);
    Validator validator = schema.newValidator();
    Source source = new DOMSource(doc);
    // Set a custom error handler that simple re-throws every exception
    validator.setErrorHandler(new ValidationErrorHandler());
    validator.validate(source);

问题在于这一行:

    Source schemaSource = new StreamSource(getClass().getResourceAsStream("/" + "SampleResponse.xsd"));

如果我将架构读作文件,它可以工作:

    Source schemaSource = new StreamSource(new File("somepath/SampleResponse.xsd"));

当我直接从类路径获取架构时,为什么验证不起作用?

(在Windows 7 64位上使用Java 1.6)

失败时的异常消息: Could not validate against schema SampleResponse.xsd. Nested exception: src-resolve: Cannot resolve the name 'oa:Attachments' to a(n) 'element declaration' component.

3 个答案:

答案 0 :(得分:4)

将File传递给StreamSource时,InputStream设置为文件的内容,而systemId也设置为File的URL。这允许解析模式中的相对URI。如果您的架构有任何相对URL,这绝对是您的问题。要在从类路径中读取架构时使这些相对URL可解析,您需要实现EntityResolver。如果您不使用相对URI,则systemId可能仍然存在其他更微妙的影响。我建议使用构造函数

StreamSource(InputStream inputStream, String systemId)

尝试将systemId设置为:null,包含架构的File,其他一些文件,不存在的File。这可能会给你一些关于Validator在systemId上做什么的提示。

答案 1 :(得分:0)

我发现我不需要实现EntityResolver来使相对URL可以从类路径中解析。

将系统ID设置为类路径资源的URI就足够了。

以下是一个使用Spring从类路径上的.xsd文件构建 StreamSource 列表的工作示例。

设置验证来源

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
Resource[] theResources = patternResolver.getResources("classpath:schemas/**/*.xsd");
List<Source> sources = new ArrayList<>();
for (Resource resource: theResources) {
    StreamSource dtd = new StreamSource(resource.getInputStream());
    dtd.setSystemId(resource.getURI().toString());
    sources.add(dtd);
}

patternResolver 是给定的classpath:schemas/**/*.xsd模式,允许它以递归方式查找类路径上 schemas 目录中的所有.xsd文件。

.xsd文件可以使用相对路径导入其他.xsd文件。例如,.xsd文件可以包含这样的导入:

<xsd:import namespace="urn:www.example.com/common" schemaLocation="./common.xsd">

这一行:

dtd.setSystemId(resource.getURI().toString());

是模式验证程序解析.xsd文件中相对路径的关键。

执行验证

上面构建的 StreamSource 数组( sources )现在可用于设置XML验证的模式源:

import org.xmlunit.builder.Input;
import org.xmlunit.validation.Languages;
import org.xmlunit.validation.Validator;
import javax.xml.transform.Source;

Validator v = Validator.forLanguage(Languages.W3C_XML_SCHEMA_NS_URI);
v.setSchemaSources(sources.toArray(new Source[sources.size()]));
Source input = Input.fromByteArray(xmlBytes).build();
v.validateInstance(input);

validateInstance 方法调用验证 xmlBytes 数组所代表的XML。

答案 2 :(得分:0)

为了后代,这是我在Scala中执行的,受Joman68的答案https://stackoverflow.com/a/50518995/434405的启发,该操作没有使用spring libs。

import javax.xml.XMLConstants
import javax.xml.transform.Source
import javax.xml.transform.stream.StreamSource
import javax.xml.validation.{Schema, SchemaFactory, Validator}

object SchemaCheck extends App {

  private val xsds = List("schema.xsd") // add more as required

  private val schemaDocuments: Array[Source] = xsds.map { xsd =>
    val res = getClass.getResource(s"/$xsd")
    val dtd = new StreamSource(res.toURI.toString)
    dtd.setSystemId(res.toURI.toString)
    dtd
  }.toArray

  private val sf           = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
  private val s: Schema    = sf.newSchema(schemaDocuments)
  private val v: Validator = s.newValidator()

  private val instanceDocument: Source = new StreamSource(new java.io.File("test.xml"))
  v.validate(instanceDocument)
}