【问题标题】:How to validate an XML file using Java with an XSD having an include?如何使用带有包含 XSD 的 Java 验证 XML 文件?
【发布时间】:2011-01-21 12:25:29
【问题描述】:

我正在使用 Java 5 javax.xml.validation.Validator 来验证 XML 文件。我已经为一个只使用导入并且一切正常的模式完成了它。现在我正在尝试使用另一个使用导入和一个包含的模式进行验证。我遇到的问题是主模式中的元素被忽略,验证说它找不到他们的声明。

这是我构建架构的方式:

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>

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

【问题讨论】:

    标签: java xml validation xsd


    【解决方案1】:

    正如用户“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 文件位置的信息。

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

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

    【讨论】:

      【解决方案2】:

      接受的答案完全没问题,但如果不进行一些修改,它就不适用于 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;
                }
            }
        }
        

        【讨论】:

        • 感谢分享 :-) 我确认这对我们有用,因为第一次 xsd 使用 ../../otherSchema.xsd 等相对路径导入其他 xsd
        • 很高兴它对你有所帮助:)
        • 这个答案中的Input stackoverflow.com/a/2342859/2733462 差不多了,我唯一推荐的是修改public String getStringData() 使其可以处理读取大型模式文件的方式,所以它将是更有弹性。
        • 解析依赖时文件过早结束:(
        【解决方案4】:

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

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

        【讨论】:

        • 但是 foo.xml 放在哪里?
        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          对我们来说,resolveResource 看起来像这样。在一些序言异常和奇怪之后 元素类型“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) ;
          }
          

          【讨论】:

            【解决方案7】:
            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);
            

            【讨论】:

            • 这不会针对导入另一个架构的架构进行验证
            【解决方案8】:

            您需要使用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);
            }
            
             }
            

            资源解析器返回的输入实现:

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

            【讨论】:

            • 非常感谢您的全面回答!我将在今天下午实施,并让您知道它是如何工作的。我确实需要创建 Schema 对象,因为我不知道将如何构建要验证的文件。我不想依赖他们的声明。
            • 没有问题,示例代码取自单元测试,因此您可能需要更改一些位以满足您的需要
            • 我快到了。现在我的验证器确实包含了包含的文件和主文件的内容。但是在加载导入文件时出现异常,序言中不允许的内容......它与导入的文件有关。如果我直接加载该文件(从它而不是主文件构建架构),我不会收到此错误。知道在这种情况下什么会导致这种异常吗?
            • @ulab 仅使用 URL 并不总是解决方案。我在我的项目中使用URL,但我仍然需要LSResourceResolver。我最好的猜测是,您的文件使用相对于您的工作目录的导入路径,而我的文件使用相对于我的类路径根目录的导入路径,这不会自动解析。
            • "注意:在此示例中,XSD 应位于类路径的根目录中" - 您能否描述一下如何确保这一点?
            猜你喜欢
            • 1970-01-01
            • 2012-06-11
            • 2014-09-28
            • 2018-03-09
            • 1970-01-01
            • 2020-01-16
            • 2011-07-15
            • 2010-10-23
            • 1970-01-01
            相关资源
            最近更新 更多