Java中使用含嵌套导入的Schema验证XML遇阻求助
你遇到的问题本质是XSD规范的严格遵循与解析器宽松处理之间的差异。Apache Xerces默认严格执行W3C XSD规范,而其他环境的解析器可能做了兼容性宽松处理。
问题原因
根据W3C XSD规范,一个Schema只能直接引用自己显式导入的命名空间中的组件。你的types.xsd引用了gef:modeId(属于http://www.example.com/geofencing命名空间),但只显式导入了http://www.example.com/implementations——虽然implementationsV2.1.xsd导入了geofencing,但这属于间接导入,严格来说上层Schema不能直接引用间接导入的命名空间组件。
下面是几种无需修改原始XSD文件的解决方案:
方案1:一次性加载所有依赖Schema(推荐)
把所有相关的XSD文件都作为参数传给SchemaFactory.newSchema(),让Xerces将它们纳入同一个全局Schema集合,这样就能跨间接导入的命名空间找到组件:
private void validateResponseSchema(String response) throws IOException, SAXException { SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); // 加载所有关联的XSD文件 Source[] schemaSources = new Source[] { new StreamSource(new ClassPathResource("types.xsd").getInputStream()), new StreamSource(new ClassPathResource("implementationsV2.1.xsd").getInputStream()), new StreamSource(new ClassPathResource("geofencingServiceV2.1.xsd").getInputStream()) }; Schema schema = factory.newSchema(schemaSources); Validator validator = schema.newValidator(); validator.validate(new StreamSource(new ByteArrayInputStream(response.getBytes()))); }
这个方法不依赖解析器特性,兼容性最好,也不需要修改任何XSD文件。
方案2:开启Xerces的宽松校验特性
Xerces提供了一个特有的特性,允许间接导入的命名空间组件被引用。你可以在创建SchemaFactory后开启它:
private void validateResponseSchema(String response) throws IOException, SAXException { SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); // 开启允许访问间接导入的Schema语法树的特性 factory.setFeature("http://apache.org/xml/features/validation/schema/allow-grammar-access", true); Schema schema = factory.newSchema(new ClassPathResource("types.xsd").getURL()); Validator validator = schema.newValidator(); validator.validate(new StreamSource(new ByteArrayInputStream(response.getBytes()))); }
注意:这个特性是Xerces专属的,如果你的代码需要在其他解析器上运行,方案1更稳妥。
方案3:内存中动态添加缺失的Import
如果上面的方法都不适用,你可以在内存中读取types.xsd的内容,动态添加对geofencing命名空间的Import,不需要修改物理文件:
private void validateResponseSchema(String response) throws IOException, SAXException { SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); // 读取types.xsd的原始内容 String typesXsdContent = new String( Files.readAllBytes(Paths.get(new ClassPathResource("types.xsd").getURI())), StandardCharsets.UTF_8 ); // 在现有implementations的Import后添加geofencing的Import typesXsdContent = typesXsdContent.replace( "<xsd:import namespace=\"http://www.example.com/implementations\" schemaLocation=\"implementationsV2.1.xsd\"/>", "<xsd:import namespace=\"http://www.example.com/implementations\" schemaLocation=\"implementationsV2.1.xsd\"/>\n <xsd:import namespace=\"http://www.example.com/geofencing\" schemaLocation=\"geofencingServiceV2.1.xsd\"/>" ); // 使用修改后的内容创建Schema Schema schema = factory.newSchema(new StreamSource(new ByteArrayInputStream(typesXsdContent.getBytes(StandardCharsets.UTF_8)))); Validator validator = schema.newValidator(); validator.validate(new StreamSource(new ByteArrayInputStream(response.getBytes()))); }
如果types.xsd的格式有变化(比如换行、空格不同),建议用XML解析器(如DOM)来修改内容,这样比字符串替换更健壮。
内容的提问来源于stack exchange,提问作者vincbon

