Como validar um arquivo XML usando Java com um XSD com inclusão?

Estou usando o Java 5 javax.xml.validation.Validator para validar o arquivo XML. Eu fiz isso por um esquema que usa apenas importações e tudo funciona bem. Agora estou tentando validar com outro esquema que usa importação e um include. O problema que tenho é que o elemento no esquema principal é ignorado, a validação diz que não pode encontrar sua declaração.

Aqui está como eu construo o esquema:

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

Agora aqui está o extrato da declaração em main.xsd

       

Se eu copiar o código do meu XSD incluído no main.xsd, ele funciona bem. Se não o fizer, a validação não encontrará a declaração de “Elemento”.

você precisa usar um LSResourceResolver para isso funcionar. por favor, dê uma olhada no código de exemplo abaixo.

um método de validação:

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

a implementação do resolvedor de resources:

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

A implementação de input retornada pelo resolvedor de recurso:

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

Eu tive que fazer algumas modificações para este post por AMegmondoEmber

Meu arquivo de esquema principal tinha alguns includes de pastas irmãs, e os arquivos incluídos também tinham alguns includes de suas pastas locais. Eu também tive que rastrear o caminho do recurso base e o caminho relativo do recurso atual. Este código funciona para mim, mas lembre-se de que ele assume que todos os arquivos xsd têm um nome exclusivo. Se você tiver alguns arquivos xsd com o mesmo nome, mas conteúdos diferentes em caminhos diferentes, isso provavelmente causará problemas.

 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 pathMap = new HashMap(); /** * 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 eg () .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; } } } 

A resposta aceita é perfeitamente aceitável, mas não funciona com o Java 8 sem algumas modificações. Também seria bom poder especificar um caminho base a partir do qual os esquemas importados são lidos.

Eu usei no meu Java 8 o seguinte código que permite especificar um caminho de esquema incorporado diferente do caminho da raiz:

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

Essa implementação também fornece ao usuário uma mensagem significativa, caso o esquema não possa ser lido.

Para nós, o resolveResource ficou assim. Após alguma exceção de prólogo e tipo de elemento estranho “xs: schema” deve ser seguido por especificações de atributo, “>” ou “/>”. O tipo de elemento “xs: element” deve ser seguido por especificações de atributo, “>” ou “/>”. (por causa da divisão de várias linhas)

O histórico do caminho foi necessário devido à estrutura de inclusão

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

Então o código parece:

 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 eg () .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) ; } 

Se você não encontrar um elemento no xml, receberá uma exceção xml: lang. Elementos diferenciam maiúsculas de minúsculas

A resposta aceita é muito detalhada, e constrói um DOM na memory primeiro, includes parece funcionar fora da checkbox para mim, incluindo referências relativas.

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