【发布时间】:2015-01-27 04:25:44
【问题描述】:
我最近需要在 java 中使用导入另一个模式的模式来实现模式验证(从今以后我将把它称为模式层次结构)。令我惊讶的是,我发现模式层次结构极大地复杂了用于简单独立模式验证的代码。我对修复(以及我创建的)的理解是 LSResourceResolver 接口的 impl 和要返回的 LSInput 接口的 impl。我的理解是,当需要针对架构层次进行验证时,这是必要的。
我觉得这很令人沮丧,因为一旦验证器拥有根模式的句柄,任何导入都只是相对于该位置。为了使验证更容易和可重用,我开始创建一个解析器,最终将模式验证简化为每种情况下的两个输入。
- 什么是根架构
- 您要验证的有效负载是什么。
换句话说,我的目标是对任何模式结构进行如下工作:
XmlValidator validator = new XmlValidator("some/dir/root.xsd");
validator.validate("<xml><someXml/></xml>");
查看the documentation 以获取被调用以加载资源的函数时,您会发现第一个问题是未调用解析器来加载根资源(根模式)。您需要该根模式的路径才能从中查找其他相对路径。这可以通过将根路径传递给解析器的构造函数并手动跟踪它来克服。
然后是路障。 systemId 参数可靠地包含试图解析/加载的资源(此字符串正是 import/include/redefine schemaLocation 属性的内容)。例如:
如果您正在加载的当前架构有这一行:
<xsd:include schemaLocation="../given/redefine.xsd"/>
加载redefine.xsd时的systemId为:
"../given/redefine.xsd"
但是,baseURI 参数应该保存之前加载的资源(您必须知道这一点,因为您正在根据先前资源的位置创建相对路径)可以是null,根据我的经验,它适用于将要加载的模式的 2/3。
这就是我觉得 java 内部验证无法提供我正在寻找的解决方案的地方。我们试图解决的问题似乎很简单。给定一个根模式,根据根模式的位置加载所有其他包含的模式。除非我遗漏了什么,否则现在这是不可能的,因为 baseURI 可以是 null,因此无法跟踪以前的架构。
在 java 的生命周期中,我们当然不能走这么远,而且这个问题还没有解决。我在这里想念什么?现在不可能编写验证实用程序并且只提供上述两个输入,这是否正确?其他人使用什么进行架构验证?我必须相信其他人不会不断滚动自定义解析器类以围绕架构层次结构跳舞(这应该相当普遍)。
这是试图解决的问题的简单表示。我正在寻找最简单、最类似于 java 的方法来解决这个示例问题:
假设示例项目结构为:
src/main/java/sandbox/TestValidation.java
src/main/resources/sandbox/sample.xml
src/main/resources/sandbox/custom/wrapper.xsd
src/main/resources/sandbox/custom/candy.xsd
src/main/resources/sandbox/given/base.xsd
src/main/resources/sandbox/given/redefine.xsd
TestValidation.java:
import javax.xml.XMLConstants;
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;
import org.xml.sax.SAXException;
import com.blarg.validation.XmlValidator;
public class SchemaValidationTest {
public SchemaValidationTest() throws Exception {
// The linked suggested solution which fails because
// it cannot load the first referenced schema
Source schemaFile = new StreamSource(
getClass().getClassLoader()
.getResourceAsStream("sandbox/custom/wrapper.xsd"));
Source xmlFile = new StreamSource(
getClass().getClassLoader()
.getResourceAsStream("sandbox/sample.xml"));
SchemaFactory schemaFactory = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(schemaFile);
Validator validator = schema.newValidator();
try {
validator.validate(xmlFile);
System.out.println(xmlFile.getSystemId() + " is valid");
} catch (SAXException e) {
System.out.println(xmlFile.getSystemId() + " is NOT valid");
System.out.println("Reason: " + e.getLocalizedMessage());
}
// My custom validator which succeeds all the way until
// it reaches the candy.xsd for reasons described above and again below.
XmlValidator customValidator = new XmlValidator("sandbox/custom/wrapper.xsd");
customValidator.validate(getClass().getClassLoader().getResourceAsStream("sandbox/sample.xml"));
}
public static void main(String[] args) throws Exception {
new SchemaValidationTest();
}
}
sample.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Wrapper> <!-- wrapper.xsd -->
<GiftBasket>
<Fruit> <!-- base.xsd -->
<Apple>
<Size>medium</Size>
<Color>Red</Color> <!-- redefine.xsd -->
</Apple>
<Orange>
<Size>large</Size>
</Orange>
</Fruit>
<Candy> <!-- candy.xsd -->
<Caramel>salted</Caramel>
</Candy>
</GiftBasket>
</Wrapper>
包装器.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:include schemaLocation="../given/redefine.xsd"/>
<xsd:include schemaLocation="./candy.xsd"/>
<xsd:element name="Wrapper">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="GiftBasket" type="GiftBasket_Type" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="GiftBasket_Type">
<xsd:sequence>
<!-- From base.xsd (and apple is redefined in redefine.xsd) -->
<xsd:element name="Fruit" type="Fruit_Type" minOccurs="1" maxOccurs="1"/>
<!-- From candy.xsd -->
<xsd:element name="Candy" type="Candy_Type" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
base.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="unqualified" attributeFormDefault="unqualified">
<xsd:complexType name="Fruit_Type">
<xsd:sequence>
<xsd:element name="Apple" type="Apple_Type" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="Orange" type="Orange_Type" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<!-- This is redefined in redefine.xsd to include additional elements -->
<xsd:complexType name="Apple_Type">
<xsd:sequence>
<xsd:element name="Size" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Orange_Type">
<xsd:sequence>
<xsd:element name="Size" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
重新定义.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="unqualified" attributeFormDefault="unqualified">
<xsd:redefine schemaLocation="./base.xsd">
<xsd:complexType name="Apple_Type">
<xsd:complexContent>
<xsd:extension base="Apple_Type">
<xsd:sequence>
<xsd:element name="Color" type="xsd:string"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:redefine>
</xsd:schema>
candy.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:complexType name="Fruit_Type">
<xsd:choice>
<xsd:element name="Chocolate" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="Caramel" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
</xsd:complexType>
</xsd:schema>
如果您想查看我当前的 LSResourceResolver 实现,它可以让我接近解决方案,如下所示。如果 candy.xsd 的导入和引用的元素从 wrapper.xsd 和 sample.xml 中删除,则此验证。它不起作用的原因是因为在加载 candy.xsd 时,之前加载的路径在 sandbox/given 并且传入的 systemId 将是 ./candy.xsd 所以它会在错误的位置查找 candy.xsd:
package com.blarg.validation;
import java.io.InputStream;
import java.util.LinkedList;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import com.blarg.validation.exception.SchemaNotFoundException;
public class SchemaResolver implements LSResourceResolver {
private String path;
private ClassLoader classLoader;
private SchemaTracker tracker;
public SchemaResolver(String path, ClassLoader classLoader, SchemaTracker tracker) {
this.path = path;
this.tracker = tracker;
this.classLoader = classLoader;
}
public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
String classloaderPath = generateClassloaderResourcePath(path, systemId);
tracker.setLastLoadedSchema(classloaderPath);
InputStream is = classLoader.getResourceAsStream(classloaderPath);
if (is == null) {
throw new SchemaNotFoundException("Loading the root schema succeeded, but the following referenced schema could not be found: '"
+ classloaderPath
+ "' Make sure the root schema and referenced schemas are all in the same directory. Then verify any <xsd:include>, "
+ "<xsd:import>, or <xsd:redefine> tags all have correct 'schemaLocation' attribute values.");
}
/*
* Store the last used path so the next schema lookup is relative to it.
* This is a hack and will only work if:
* some/dir/a.xsd imports some/dir/another/b.xsd
* and some/dir/another/b.xsd imports some/dir/other/c.xsd
* etc..
*
* It will *not* work for:
* some/dir/a.xsd imports some/dir/another/b.xsd
* some/dir/another/b.xsd imports some/dir/other/c.xsd
* etc..
* AND
* some/dir/a.xsd also imports some/dir/d.xsd
*
* It will fail loading d.xsd because the last stored path
* will be /some/dir/other and the systemId coming in will
* be "./d.xsd"
*/
path = classloaderPath.substring(0, classloaderPath.lastIndexOf("/") + 1);
return new SchemaInput(publicId, systemId, is);
}
private String generateClassloaderResourcePath(String path, String systemId) {
// fullPath may contain ./ or ../ which is not allowed in classloader resource lookups.
String fullPath = path + systemId;
LinkedList<String> linkedList = new LinkedList<String>();
String current = first(fullPath);
while (current != null) {
if (".".equals(current)) {
// Do nothing, dot represents the current directory so we have it already
} else if ("..".equals(current)) {
// Remove the lastly added directory because we need to go up
linkedList.removeLast();
} else {
// The directory is just a normal directory or filename, add it
linkedList.add(current);
}
fullPath = removeFirst(fullPath);
current = first(fullPath);
}
String classLoaderPath = "";
while (linkedList.size() > 0) {
classLoaderPath = classLoaderPath + linkedList.removeFirst() + "/";
}
classLoaderPath = classLoaderPath.substring(0, classLoaderPath.length() - 1);
System.out.println("classLoaderPath: " + classLoaderPath);
System.out.println();
return classLoaderPath;
}
private String first(String path) {
if (path == null) {
return null;
} else if (path.contains("/")) {
return path.substring(0, path.indexOf("/"));
} else {
return path;
}
}
private String removeFirst(String path) {
if (path.contains("/")) {
return path.substring(path.indexOf("/") + 1);
} else {
return null;
}
}
}
你当然需要正确地实例化它(给它正确的根模式路径并使用 schemaFactory 注册它:
schemaFactory.setResourceResolver(new SchemaResolver(pathToSchemas, classLoader, tracker));
【问题讨论】:
-
架构验证不需要自定义资源解析器来处理
xsd:import。 -
那么 xsd:include 和 xsd:redefine 呢?我很高兴尝试您提供的任何代码示例,但我尝试仅加载我的根架构,但它无法加载引用的资源。我的特定架构布局是 a.xsd 包括 b.xsd,它重新定义了 c.xsd。
-
默认资源分辨率就足够了。如果您不能准确说明您的需求为何不同,那么您可能会在错误的树上编写自定义资源解析器。用于验证的示例 Java 代码比比皆是,例如:What's the best way to validate an XML file against an XSD file?
-
另一条评论:试图提供帮助,但我知道它可能不会被欣赏。如果你想从这方面的专家那里得到帮助,那么他们很有可能每天都成功地使用它,并且认为任何认为它被破坏的人都是一个混蛋。他们很可能是错的,但你希望他们站在你这边。所以善待他们,尊重他们最喜欢的技术。顺便说一句,我不是其中之一:我已经实现了这些接口并且我知道它们的错误。但它们并非不可挽回地损坏。
-
Java 是我的主要语言,也是我“最喜欢的技术”。所以我就是那些人中的一员。如果批评我每天将要使用的语言并希望它通过指出它的问题并努力在像 SO 这样的公共网站上解决它们以便其他开发人员可以受益让我成为一个“混蛋”,那么随它吧。 kjhughes 提供的解决方案是我尝试的第一个解决方案,但不起作用。令人惊讶的是,如果手头的问题没有任何具体答案,这些 cmets 会变得多么重要。坚持技术主题或节省您的呼吸。
标签: java xml validation