使用SAX结合RelaxNG校验XML报no grammar found错误如何解决
我正尝试使用SAX解析XML文件,并基于RelaxNG模式对文件做合法性校验,当前实现代码如下:
System.setProperty(SchemaFactory.class.getName() + ":" + XMLConstants.RELAXNG_NS_URI, "com.thaiopensource.relaxng.jaxp.XMLSyntaxSchemaFactory"); SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.RELAXNG_NS_URI); Schema schema = sf.newSchema(scf); SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setSchema(schema); try { SAXParser parser = factory.newSAXParser (); XMLReader reader = parser.getXMLReader (); reader.setFeature("http://xml.org/sax/features/validation", true); reader.setProperty("http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation", schemaFilename); reader.setFeature("http://apache.org/xml/features/xinclude", true); reader.setFeature("http://xml.org/sax/features/namespaces", true); reader.setFeature("http://apache.org/xml/features/xinclude/fixup-base-uris", false); reader.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); reader.setErrorHandler (this); reader.setContentHandler (this); reader.setEntityResolver (this); reader.parse (new InputSource(input)); }
运行上述代码时,程序抛出如下错误:
Document is invalid: no grammar found.
经测试确认以下两点:
- 待校验的XML文档与对应RelaxNG模式使用jing工具校验均可正常通过
- 若使用trang将RelaxNG模式转换为XSD格式后,替换原有RNG模式执行校验,整个流程可正常运行
初步排查:怀疑实例化SAXParserFactory对象时,需要显式指定使用RelaxNG对应的解析器工厂。RelaxNG官方文档提及RelaxNG“可作为类库配合任意SAX2解析器完成校验”,但对应文档链接已失效;查阅jing-trang的RelaxNG源码仓库,也未找到名称匹配.*ParseFactory.*规则的类。
求问:该如何调整配置,才能基于SAX正常使用RelaxNG完成XML校验?
报错是配置冲突导致的,你混用了W3C XSD专属的校验配置和JAXP标准的RelaxNG校验流程,根本不需要找什么RelaxNG专属的SAXParserFactory实现,改两处配置就能正常跑:
- 删掉XSD专属的模式地址配置
你代码里设置的http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation是Apache Xerces专门给无命名空间W3C XSD用的外部模式加载属性,RelaxNG校验完全不认这个配置。你之前已经通过SchemaFactory加载了RNG模式,又通过factory.setSchema(schema)把校验规则绑到了解析器工厂,不需要再重复指定模式路径。留着这个属性反而会让解析器主动去找XSD语法规则,找不到就直接抛no grammar found的错误。 - 关掉SAX默认的validation开关
把reader.setFeature("http://xml.org/sax/features/validation", true)这行删掉,或者设为false。这个特性开了之后会触发解析器内置的DTD/XSD默认校验逻辑,和你通过setSchema挂载的RelaxNG校验器是两套独立流程,开着会干扰RNG校验的正常执行。
修正后的核心可用代码:
// 注册RNG的SchemaFactory实现,这部分逻辑是对的,不用改 System.setProperty(SchemaFactory.class.getName() + ":" + XMLConstants.RELAXNG_NS_URI, "com.thaiopensource.relaxng.jaxp.XMLSyntaxSchemaFactory"); SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.RELAXNG_NS_URI); // 确保scf是指向正确RNG模式文件的源即可 Schema schema = sf.newSchema(scf); SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setSchema(schema); // 注意:命名空间感知必须在工厂层面开启,RNG校验依赖命名空间匹配 factory.setNamespaceAware(true); SAXParser parser = factory.newSAXParser(); XMLReader reader = parser.getXMLReader(); // 移除原有的validation特性设置、移除external-noNamespaceSchemaLocation属性设置 reader.setFeature("http://apache.org/xml/features/xinclude", true); reader.setFeature("http://apache.org/xml/features/xinclude/fixup-base-uris", false); reader.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); reader.setErrorHandler(this); reader.setContentHandler(this); reader.setEntityResolver(this); reader.parse(new InputSource(input));
补个容易踩的坑:setNamespaceAware(true)一定要在SAXParserFactory实例上设置,别等拿到XMLReader对象再去改命名空间特性。工厂初始化解析器的时候如果没开命名空间感知,后续再改reader配置会导致RNG校验的命名空间匹配逻辑失效。
JAXP的校验机制本身是跨模式语言统一的:只要正确注册了对应模式语言的SchemaFactory,通过setSchema绑定到解析器工厂后,不管底层用的是Xerces还是其他兼容SAX2的解析器,都会自动挂载对应的校验器完成校验,这也是文档里说的RelaxNG可以配合任意SAX2解析器工作的实际含义,不需要找专门的解析器工厂类。
内容的提问来源于stack exchange,提问作者opeongo

