使用SchemaFactory进行XSD验证:无法解析'ext:UBLExtensions'元素声明
解决UBL发票XSD验证XML时的导入依赖问题
嘿,我太懂这种被XSD导入依赖卡壳的感觉了!UBL的发票XSD本身就是模块化设计,主文件会引用一堆子XSD,光把所有InputStream丢进验证方法里根本不够,关键得让解析器能精准找到这些被导入的文件才行。
先看看你写的基础验证方法,其实已经摸到关键点了——用到了LSResourceResolver,这可是处理XSD导入的核心武器!不过你的代码还没补全,我给你优化并补全完整实现,顺便把踩过的坑都讲清楚:
import javax.xml.XMLConstants; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.w3c.dom.ls.LSResourceResolver; private Schema validate(InputStream[] schemas, LSResourceResolver lsResourceResolver) { Schema schema = null; try { // 实例化标准的XML Schema工厂 SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); // 绑定资源解析器,这一步是解决XSD导入找不到文件的核心 if (lsResourceResolver != null) { schemaFactory.setResourceResolver(lsResourceResolver); } // 将InputStream数组转为Schema能识别的StreamSource数组 StreamSource[] schemaStreamSources = new StreamSource[schemas.length]; for (int index = 0; index < schemas.length; index++) { schemaStreamSources[index] = new StreamSource(schemas[index]); // 给每个Schema设置唯一标识,帮助解析器定位导入关系 schemaStreamSources[index].setSystemId("ubl-schema-" + index + ".xsd"); } // 编译所有Schema文件生成可用于验证的Schema实例 schema = schemaFactory.newSchema(schemaStreamSources); } catch (Exception e) { e.printStackTrace(); // 这里可以根据业务需求抛出自定义异常或者做错误日志记录 } return schema; }
接下来你需要实现一个靠谱的LSResourceResolver,专门处理XSD里的<xs:import>或<xs:include>引用。比如你的UBL XSD都放在项目的resources/ubl-xsds目录下,就可以这么写:
import org.w3c.dom.ls.LSInput; import org.w3c.dom.ls.LSResourceResolver; public class UblSchemaResourceResolver implements LSResourceResolver { @Override public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) { // systemId就是XSD导入语句里写的文件名,比如"CommonBasicComponents_2.1.xsd" InputStream resourceStream = getClass().getClassLoader().getResourceAsStream("ubl-xsds/" + systemId); if (resourceStream == null) { // 找不到对应资源时返回null,解析器会抛出明确的错误提示 return null; } // 创建LSInput实例并填充资源流信息 LSInput lsInput = new DOMImplementationLSImpl().createLSInput(); lsInput.setByteStream(resourceStream); lsInput.setSystemId(systemId); lsInput.setPublicId(publicId); return lsInput; } // 辅助内部类,用来创建LSInput实例(避免引入额外依赖) private static class DOMImplementationLSImpl implements DOMImplementationLS { @Override public LSInput createLSInput() { return new LSInputImpl(); } // 省略其他未用到的接口方法实现 @Override public LSOutput createLSOutput() { return null; } @Override public LSParser createLSParser(int mode, String schemaType) { return null; } @Override public LSSerializer createLSSerializer() { return null; } @Override public boolean hasFeature(String feature, String version) { return false; } } private static class LSInputImpl implements LSInput { private InputStream byteStream; private String systemId; private String publicId; @Override public InputStream getByteStream() { return byteStream; } @Override public void setByteStream(InputStream byteStream) { this.byteStream = byteStream; } @Override public String getSystemId() { return systemId; } @Override public void setSystemId(String systemId) { this.systemId = systemId; } @Override public String getPublicId() { return publicId; } @Override public void setPublicId(String publicId) { this.publicId = publicId; } // 省略其他未用到的getter/setter方法 } }
最后调用的时候还有几个坑要特别注意:
- 版本一致性:UBL不同版本的XSD绝对不能混用,比如2.1版的主XSD不能引用2.0版的组件XSD
- 命名空间匹配:要确保待验证的XML里的命名空间和XSD定义的完全一致,不然会出现奇怪的验证失败
- 资源路径正确性:如果是打包成Jar文件,一定要用类加载器获取资源,不能用本地文件路径硬编码
内容的提问来源于stack exchange,提问作者Eksit
相关产品推荐
相关产品推荐

