Mel*_*nie 27 java xml validation xsd
我正在使用Java 5 javax.xml.validation.Validator来验证XML文件.我已经为一个仅使用导入的模式完成了它,一切正常.现在我正在尝试使用另一个使用import和一个include的模式进行验证.我遇到的问题是主模式中的元素被忽略,验证表明它无法找到它们的声明.
以下是我构建Schema的方法:
InputStream includeInputStream = getClass().getClassLoader().getResource("include.xsd").openStream();
InputStream importInputStream = getClass().getClassLoader().getResource("import.xsd").openStream();
InputStream mainInputStream = getClass().getClassLoader().getResource("main.xsd").openStream();
Source[] sourceSchema = new SAXSource[]{includeInputStream , importInputStream,
mainInputStream };
Schema schema = factory.newSchema(sourceSchema);
Run Code Online (Sandbox Code Playgroud)
现在这里是main.xsd中声明的摘录
<xsd:schema xmlns="http://schema.omg.org/spec/BPMN/2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:import="http://www.foo.com/import" targetNamespace="http://main/namespace" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.foo.com/import" schemaLocation="import.xsd"/>
<xsd:include schemaLocation="include.xsd"/>
<xsd:element name="element" type="tElement"/>
<...>
</xsd:schema>
Run Code Online (Sandbox Code Playgroud)
如果我在main.xsd中复制我包含的XSD的代码,它可以正常工作.如果我不这样做,验证不会找到"元素"的声明.
Ste*_*oey 59
你需要使用LSResourceResolver才能工作.请看下面的示例代码.
验证方法:
// note that if your XML already declares the XSD to which it has to conform, then there's no need to declare the schemaName here
void validate(String xml, String schemaName) throws Exception {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
DocumentBuilder parser = builderFactory
.newDocumentBuilder();
// parse the XML into a document object
Document document = parser.parse(new StringInputStream(xml));
SchemaFactory factory = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// associate the schema factory with the resource resolver, which is responsible for resolving the imported XSD's
factory.setResourceResolver(new ResourceResolver());
// note that if your XML already declares the XSD to which it has to conform, then there's no need to create a validator from a Schema object
Source schemaFile = new StreamSource(getClass().getClassLoader()
.getResourceAsStream(schemaName));
Schema schema = factory.newSchema(schemaFile);
Validator validator = schema.newValidator();
validator.validate(new DOMSource(document));
}
Run Code Online (Sandbox Code Playgroud)
资源解析器实现:
public class ResourceResolver implements LSResourceResolver {
public LSInput resolveResource(String type, String namespaceURI,
String publicId, String systemId, String baseURI) {
// note: in this sample, the XSD's are expected to be in the root of the classpath
InputStream resourceAsStream = this.getClass().getClassLoader()
.getResourceAsStream(systemId);
return new Input(publicId, systemId, resourceAsStream);
}
}
Run Code Online (Sandbox Code Playgroud)
资源解析器返回的Input实现:
public class Input implements LSInput {
private String publicId;
private String systemId;
public String getPublicId() {
return publicId;
}
public void setPublicId(String publicId) {
this.publicId = publicId;
}
public String getBaseURI() {
return null;
}
public InputStream getByteStream() {
return null;
}
public boolean getCertifiedText() {
return false;
}
public Reader getCharacterStream() {
return null;
}
public String getEncoding() {
return null;
}
public String getStringData() {
synchronized (inputStream) {
try {
byte[] input = new byte[inputStream.available()];
inputStream.read(input);
String contents = new String(input);
return contents;
} catch (IOException e) {
e.printStackTrace();
System.out.println("Exception " + e);
return null;
}
}
}
public void setBaseURI(String baseURI) {
}
public void setByteStream(InputStream byteStream) {
}
public void setCertifiedText(boolean certifiedText) {
}
public void setCharacterStream(Reader characterStream) {
}
public void setEncoding(String encoding) {
}
public void setStringData(String stringData) {
}
public String getSystemId() {
return systemId;
}
public void setSystemId(String systemId) {
this.systemId = systemId;
}
public BufferedInputStream getInputStream() {
return inputStream;
}
public void setInputStream(BufferedInputStream inputStream) {
this.inputStream = inputStream;
}
private BufferedInputStream inputStream;
public Input(String publicId, String sysId, InputStream input) {
this.publicId = publicId;
this.systemId = sysId;
this.inputStream = new BufferedInputStream(input);
}
}
Run Code Online (Sandbox Code Playgroud)
我必须对AMegmondoEmber 的这篇文章进行一些修改
我的主架构文件包含来自同级文件夹的一些包含内容,并且包含的文件也包含来自其本地文件夹的一些包含内容。我还必须跟踪当前资源的基本资源路径和相对路径。这段代码现在对我有用,但请记住,它假设所有 xsd 文件都有唯一的名称。如果您有一些名称相同但内容不同且位于不同路径的 xsd 文件,则可能会给您带来问题。
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
/**
* The Class ResourceResolver.
*/
public class ResourceResolver implements LSResourceResolver {
/** The logger. */
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/** The schema base path. */
private final String schemaBasePath;
/** The path map. */
private Map<String, String> pathMap = new HashMap<String, String>();
/**
* Instantiates a new resource resolver.
*
* @param schemaBasePath the schema base path
*/
public ResourceResolver(String schemaBasePath) {
this.schemaBasePath = schemaBasePath;
logger.warn("This LSResourceResolver implementation assumes that all XSD files have a unique name. "
+ "If you have some XSD files with same name but different content (at different paths) in your schema structure, "
+ "this resolver will fail to include the other XSD files except the first one found.");
}
/* (non-Javadoc)
* @see org.w3c.dom.ls.LSResourceResolver#resolveResource(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public LSInput resolveResource(String type, String namespaceURI,
String publicId, String systemId, String baseURI) {
// The base resource that includes this current resource
String baseResourceName = null;
String baseResourcePath = null;
// Extract the current resource name
String currentResourceName = systemId.substring(systemId
.lastIndexOf("/") + 1);
// If this resource hasn't been added yet
if (!pathMap.containsKey(currentResourceName)) {
if (baseURI != null) {
baseResourceName = baseURI
.substring(baseURI.lastIndexOf("/") + 1);
}
// we dont need "./" since getResourceAsStream cannot understand it
if (systemId.startsWith("./")) {
systemId = systemId.substring(2, systemId.length());
}
// If the baseResourcePath has already been discovered, get that
// from pathMap
if (pathMap.containsKey(baseResourceName)) {
baseResourcePath = pathMap.get(baseResourceName);
} else {
// The baseResourcePath should be the schemaBasePath
baseResourcePath = schemaBasePath;
}
// Read the resource as input stream
String normalizedPath = getNormalizedPath(baseResourcePath, systemId);
InputStream resourceAsStream = this.getClass().getClassLoader()
.getResourceAsStream(normalizedPath);
// if the current resource is not in the same path with base
// resource, add current resource's path to pathMap
if (systemId.contains("/")) {
pathMap.put(currentResourceName, normalizedPath.substring(0,normalizedPath.lastIndexOf("/")+1));
} else {
// The current resource should be at the same path as the base
// resource
pathMap.put(systemId, baseResourcePath);
}
Scanner s = new Scanner(resourceAsStream).useDelimiter("\\A");
String s1 = s.next().replaceAll("\\n", " ") // the parser cannot understand elements broken down multiple lines e.g. (<xs:element \n name="buxing">)
.replace("\\t", " ") // these two about whitespaces is only for decoration
.replaceAll("\\s+", " ").replaceAll("[^\\x20-\\x7e]", ""); // some files has a special character as a first character indicating utf-8 file
InputStream is = new ByteArrayInputStream(s1.getBytes());
return new LSInputImpl(publicId, systemId, is); // same as Input class
}
// If this resource has already been added, do not add the same resource again. It throws
// "org.xml.sax.SAXParseException: sch-props-correct.2: A schema cannot contain two global components with the same name; this schema contains two occurrences of ..."
// return null instead.
return null;
}
/**
* Gets the normalized path.
*
* @param basePath the base path
* @param relativePath the relative path
* @return the normalized path
*/
private String getNormalizedPath(String basePath, String relativePath){
if(!relativePath.startsWith("../")){
return basePath + relativePath;
}
else{
while(relativePath.startsWith("../")){
basePath = basePath.substring(0,basePath.substring(0, basePath.length()-1).lastIndexOf("/")+1);
relativePath = relativePath.substring(3);
}
return basePath+relativePath;
}
}
}
Run Code Online (Sandbox Code Playgroud)
The accepted answer is perfectly ok, but does not work with Java 8 without some modifications. It would also be nice to be able to specify a base path from which the imported schemas are read.
I have used in my Java 8 the following code which allows to specify an embedded schema path other than the root path:
import com.sun.org.apache.xerces.internal.dom.DOMInputImpl;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import java.io.InputStream;
import java.util.Objects;
public class ResourceResolver implements LSResourceResolver {
private String basePath;
public ResourceResolver(String basePath) {
this.basePath = basePath;
}
@Override
public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
// note: in this sample, the XSD's are expected to be in the root of the classpath
InputStream resourceAsStream = this.getClass().getClassLoader()
.getResourceAsStream(buildPath(systemId));
Objects.requireNonNull(resourceAsStream, String.format("Could not find the specified xsd file: %s", systemId));
return new DOMInputImpl(publicId, systemId, baseURI, resourceAsStream, "UTF-8");
}
private String buildPath(String systemId) {
return basePath == null ? systemId : String.format("%s/%s", basePath, systemId);
}
}
Run Code Online (Sandbox Code Playgroud)
This implementation also gives to the user a meaningful message in case the schema cannot be read.
小智 5
正如用户“ulab”在对另一个答案的评论中指出的那样,此答案中描述的解决方案(针对单独的 stackoverflow 问题)将适用于许多人。这是该方法的粗略概述:
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
URL xsdURL = this.getResource("/xsd/my-schema.xsd");
Schema schema = schemaFactory.newSchema(xsdURL);
Run Code Online (Sandbox Code Playgroud)
这种方法的关键是避免将模式工厂交给一个流,而是给它一个 URL。通过这种方式,它可以获取有关 XSD 文件位置的信息。
这里要记住的一件事是,include 和/或 import 元素上的“schemaLocation”属性将被视为相对于 XSD 文件的类路径位置,当您在形式“my-common.xsd”或“common/some-concept.xsd”。
注意: - 在上面的示例中,我将架构文件放入“xsd”文件夹下的 jar 文件中。- “getResource”参数中的前导斜杠告诉 Java 从类加载器的根开始,而不是从“this”对象的包名开始。