如何使用带有包含XSD的Java来验证XML文件?

问题描述 投票:26回答:8

我正在使用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);

现在这里是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>

如果我在main.xsd中复制我包含的XSD的代码,它可以正常工作。如果我不这样做,验证不会找到“元素”的声明。

java xml validation xsd
8个回答
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));
}

资源解析器实现:

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);
}

 }

资源解析器返回的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);
}
}

4
投票

接受的答案是完全可以的,但如果没有一些修改就无法使用Java 8。能够指定从中读取导入的模式的基本路径也是很好的。

我在Java 8中使用了以下代码,它允许指定除根路径之外的嵌入式架构路径:

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);
    }
}

在无法读取模式的情况下,该实现还向用户提供有意义的消息。


3
投票

我不得不通过AMegmondoEmber对this post进行一些修改

我的主模式文件包含一些来自兄弟文件夹的文件,包含的文件也有一些来自本地文件夹的包含。我还必须追踪当前资源的基本资源路径和相对路径。这段代码对我来说很有用,但请记住,它假定所有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;
        }
    }
}

2
投票

正如用户“ulab”在对另一个答案的评论中指出的那样,this answer中描述的解决方案(对于单独的stackoverflow问题)将适用于许多人。以下是该方法的概要:

SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
URL xsdURL = this.getResource("/xsd/my-schema.xsd");
Schema schema = schemaFactory.newSchema(xsdURL);

这种方法的关键是避免将模式工厂交给流,而是给它一个URL。这样它就可以获取有关XSD文件位置的信息。

这里要记住的一件事是,include和/或import元素的“schemaLocation”属性将被视为相对于XSD文件的类路径位置,当您使用简单文件路径时,您已将其传递给验证器的URL形式为“my-common.xsd”或“common / some-concept.xsd”。

注意: - 在上面的示例中,我将模式文件放在“xsd”文件夹下的jar文件中。 - “getResource”参数中的前导斜杠告诉Java从类加载器的根开始,而不是从“this”对象的包名开始。


0
投票

对我们来说,resolveResource看起来像这样。在一些prolog异常和奇怪的元素类型“xs:schema”之后必须跟随属性规范,“>”或“/>”。元素类型“xs:element”必须后跟属性规范,“>”或“/>”。 (因为多行故障)

由于包含的结构,需要路径历史

main.xsd (this has include "includes/subPart.xsd")
/includes/subPart.xsd (this has include "./subSubPart.xsd")
/includes/subSubPart.xsd

所以代码看起来像:

String pathHistory = "";

@Override
public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
    systemId = systemId.replace("./", "");// we dont need this since getResourceAsStream cannot understand it
    InputStream resourceAsStream = Message.class.getClassLoader().getResourceAsStream(systemId);
    if (resourceAsStream == null) {
        resourceAsStream = Message.class.getClassLoader().getResourceAsStream(pathHistory + systemId);
    } else {
        pathHistory = getNormalizedPath(systemId);
    }
    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);
}

private String getNormalizedPath(String baseURI) {
    return baseURI.substring(0, baseURI.lastIndexOf(System.getProperty("file.separator"))+ 1) ;
}

0
投票

接受的答案非常冗长,首先在内存中构建一个DOM,包括似乎为我开箱即用,包括相对引用。

    SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    Schema schema = schemaFactory.newSchema(new File("../foo.xsd"));
    Validator validator = schema.newValidator();
    validator.validate(new StreamSource(new File("./foo.xml")));

-1
投票

如果你在xml中找不到元素,你会得到xml:lang异常。元素区分大小写


-4
投票
SchemaFactory schemaFactory = SchemaFactory
                                .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Source schemaFile = new StreamSource(getClass().getClassLoader()
                                .getResourceAsStream("cars-fleet.xsd"));
Schema schema = schemaFactory.newSchema(schemaFile);
Validator validator = schema.newValidator();
StreamSource source = new StreamSource(xml);
validator.validate(source);
© www.soinside.com 2019 - 2024. All rights reserved.