2010-02-26 6 views
21

Używam Java 5 javax.xml.validation.Validator do sprawdzania poprawności pliku XML. Zrobiłem to dla jednego schematu, który używa tylko importu i wszystko działa dobrze. Teraz próbuję sprawdzić poprawność za pomocą innego schematu, który używa importu i jeden dołącz. Problemem jest to, że element głównego schematu jest ignorowany, a walidacja mówi, że nie może znaleźć deklaracji.Jak sprawdzić poprawność pliku XML przy użyciu języka Java z XSD z załącznikiem?

Oto jak zbudować schematu:

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

Teraz tutaj jest ekstrakt z deklaracją w 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> 

Gdybym skopiować kod zawarty XSD w moim głównym .xsd, działa dobrze. Jeśli nie, walidacja nie znajduje deklaracji "elementu".

Odpowiedz

53

Aby to działało, należy użyć LSResourceResolver. proszę spojrzeć na przykładowy kod poniżej.

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

realizacja rezolwer zasób:

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

} 

implementacja Wejście zwrócony przez rezolwerem zasobów:

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

Wielkie dzięki za wyczerpującą odpowiedź! Wezmę to dziś po południu i dam ci znać, jak to działa. Potrzebuję utworzyć obiekt Schema, ponieważ nie mam pojęcia, jak zostanie utworzony plik, który ma zostać sprawdzony. Nie chcę polegać na ich deklaracji. – Melanie

+1

brak prob, kod przykładowy pochodzi z testu jednostkowego, więc prawdopodobnie będziesz musiał zmienić kilka bitów, aby spełnić Twoje potrzeby. –

+0

Już prawie jestem. Teraz mój walidator zawiera dołączony plik i zawartość głównego pliku. Ale mam wyjątek podczas ładowania pliku importu, treść niedozwolona w prologu ... Jest z importowanym plikiem. Jeśli załaduję ten plik bezpośrednio (skompiluję schemat z niego zamiast głównego), nie otrzymam tego błędu. Jakieś pojęcie, co może spowodować ten wyjątek w tym stanie? – Melanie

-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

To nie będzie sprawdzane w stosunku do schematu importowania innego –

0

dla nas resolveResour ce wyglądało tak. Po pewnym wyjątku prologu i dziwnym Elementowi typu "xs: schema" muszą towarzyszyć specyfikacje atrybutów ">" lub "/>". Po typie elementu "xs: element" muszą następować specyfikacje atrybutów ">" lub "/>". (z powodu rozpadu wielu linii)

Historia ścieżka była potrzebna ze względu na strukturę obejmuje

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

Więc kod wygląda następująco:

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

Jeśli przyzwyczajenie znaleźć element w xml dostaniesz wyjątek xml: lang. Elements jest wielkość liter

3

Musiałem dokonać pewnych modyfikacji this post przez AMegmondoEmber

Główny plik zawiera schemat miał niektóre z folderów rodzeństwo, a zawarte w nim pliki, miał również niektóre obejmuje od swoich lokalnych folderów. Musiałem również wyśledzić podstawową ścieżkę zasobów i względną ścieżkę bieżącego zasobu. Ten kod działa dla mnie, ale pamiętaj, że zakłada on, że wszystkie pliki xsd mają unikalną nazwę. Jeśli masz jakieś pliki xsd o tej samej nazwie, ale różne treści w różnych ścieżkach, prawdopodobnie spowoduje to problemy.

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

Dzięki za udostępnienie :-) Potwierdzam, że działało to dla nas od pierwszego xsd zaimportuj inne xsd używając względnych ścieżek jak ../../otherSchema.xsd –

+0

Cieszę się, że pomogło ty :) – burcakulug

+1

LSInputImpl niczego nie rozwiązuje: O ( – PierluigiVernetto

0

Przyjęta odpowiedź jest bardzo obszerna i najpierw buduje DOM w pamięci, zawiera chyba dla mnie zadziałanie, w tym odniesienia względne.

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

, ale gdzie jest umieszczony plik foo.xml? – Line

1

Przyjęta odpowiedź jest doskonale dobra, ale nie działa z Javą 8 bez pewnych modyfikacji. Byłoby również miło móc określić ścieżkę podstawową, z której odczytywane są importowane schematy.

użyłem w moim Java 8 następujący kod, który pozwala określić osadzoną ścieżkę schematu innego niż ścieżce root:

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

Implementacja ta daje również użytkownikowi znaczący komunikat w przypadku schematu nie może czytaj.