2010-02-26 91 views
21

我正在使用Java 5 javax.xml.validation.Validator來驗證XML文件。我已經完成了一個只使用導入的模式,一切正常。現在我試圖用另一個使用import和include的模式進行驗證。我遇到的問題是主模式中的元素被忽略,驗證說它找不到它們的聲明。如何使用包含XSD的Java驗證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的代碼複製.xsd,它工作正常。如果我不這樣做,驗證不會找到「元素」的聲明。

回答

53

您需要使用LSResourceResolver才能正常工作。請看下面的示例代碼。

一個validate方法:

// 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 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); 
} 
} 
+0

非常感謝這個全面的答案!我會在今天下午實施,並告訴你它是如何工作的。 我確實需要創建Schema對象,因爲我不知道如何構建將要驗證的文件。我不想依賴他們的聲明。 – Melanie 2010-02-26 16:23:30

+1

沒有問題,示例代碼取自單元測試,因此您可能需要更改一些位以適應您的需求 – 2010-02-27 02:06:27

+0

我快到了。現在我的驗證器確實包含了包含的文件和主文件的內容。但是,在加載導入文件時,我有一個異常,prolog中不允許的內容......這是與一個導入的文件。如果我直接加載該文件(從它構建模式而不是主模式),我不會收到此錯誤。有什麼想法在這種情況下會導致這種異常? – Melanie 2010-03-01 14:08:22

-3
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); 
+3

這將不會驗證對模式導入另一個 – 2015-06-25 13:23:36

0

對我們來說,resolveResour:

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

} 

由資源解析器返回的輸入implemetation ce看起來像這樣。在某些序言異常和奇怪之後 元素類型「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) ; 
} 
-1

如果你不會找到一個元素在xml中,您將得到xml:lang異常。 元素是區分大小寫的

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; 
     } 
    } 
} 
+1

感謝分享:-)我確認這對我們有效,因爲第一次xsd導入其他xsd使用相對路徑,如../../otherSchema.xsd – 2014-11-27 16:35:36

+0

我很高興它幫助你:) – burcakulug 2014-12-12 16:23:04

+1

LSInputImpl不能解決任何東西:O( – PierluigiVernetto 2016-02-11 16:48:16

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

但其中foo.xml放在哪裏? – Line 2017-07-28 11:45:45

1

接受的答案是非常好的,但與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); 
    } 
} 

這種實現也給用戶一個有意義的消息的情況下,該架構不能被閱讀。