2010-02-26 58 views
21

Estoy usando Java 5 javax.xml.validation.Validator para validar el archivo XML. Lo hice por un esquema que usa solo importaciones y todo funciona bien. Ahora intento validar con otro esquema que usa importación y uno incluye. El problema que tengo es que ese elemento en el esquema principal se ignora, la validación dice que no puede encontrar su declaración.¿Cómo validar un archivo XML usando Java con un XSD que tenga un include?

Aquí es cómo construir el 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); 

Ahora aquí es un extracto de la declaración en 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> 

Si copio el código de mi XSD incluido en el principal .xsd, funciona bien. Si no lo hago, la validación no encuentra la declaración de "Elemento".

Respuesta

53

necesita usar un LSResourceResolver para que esto funcione. por favor, eche un vistazo al código de muestra a continuación.

un método de validación:

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

la puesta en práctica de resolución de recurso:

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

} 

La Implementación de entrada devuelto por el sistema de resolución de recursos:

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

¡Muchas gracias por esta respuesta integral! Lo implementaré esta tarde y te haré saber cómo funcionó. Necesito crear el objeto Schema ya que no tengo idea de cómo se generará el archivo que se va a validar. No quiero confiar en su declaración. – Melanie

+1

no hay problema, el código de muestra se toma de una prueba unitaria, por lo que es probable que necesite cambiar algunos bits para satisfacer sus necesidades –

+0

Estoy casi allí. Ahora mi validador incluye el archivo incluido y el contenido del archivo principal. Pero tengo una excepción al cargar un archivo de importación, el contenido no está permitido en el prólogo ... Es con un archivo que se importa. Si cargué ese archivo directamente (construyo el esquema desde él en lugar del principal), no obtengo este error. ¿Alguna idea de qué puede causar este tipo de excepción en esa condición? – 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

Esto no se validará contra el esquema importando otro –

0

Para nosotros el resolveResour ce se veía así. Después de una excepción de prólogo y un extraño , el tipo de elemento "xs: schema" debe ir seguido de las especificaciones de atributo, ">" o "/>". El tipo de elemento "xs: elemento" debe ir seguido de las especificaciones de atributo, ">" o "/>". (debido a la ruptura de las múltiples líneas)

La historia ruta era necesaria debido a la estructura de incluye

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

Así que el código es el siguiente:

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

Si no encuentras una elemento en xml obtendrá xml: excepción de lang. Elementos entre mayúsculas y minúsculas

3

que tenía que hacer algunas modificaciones a this post por AMegmondoEmber

Mi principal archivo de esquema tenía algún incluye desde las carpetas de hermanos, y los archivos incluidos también incluye tenido alguna de las carpetas locales. También tuve que rastrear la ruta del recurso base y la ruta relativa del recurso actual. Este código funciona para mí, pero tenga en cuenta que asume que todos los archivos xsd tienen un nombre único. Si tiene algunos archivos xsd con el mismo nombre, pero con contenido diferente en diferentes rutas, probablemente le dará 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<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

Gracias por compartir :-) Confirmo que esto nos ha funcionado desde la primera vez que xsd importamos otro xsd usando rutas relativas como ../../otherSchema.xsd –

+0

Me alegra que haya ayudado usted :) – burcakulug

+1

LSInputImpl no se resuelve en nada: O ( – PierluigiVernetto

0

La respuesta aceptada es muy detallado, y construye un DOM en la memoria primero, incluye parece funcionar fuera de la caja para mí, incluyendo referencias 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"))); 
+0

pero ¿dónde se coloca foo.xml? – Line

1

La respuesta aceptada es perfectamente correcta, pero no funciona con Java 8 sin algunas modificaciones. También sería bueno poder especificar una ruta base desde la cual se leen los esquemas importados.

que he utilizado en mi Java 8 el siguiente código que permite especificar una ruta de esquema incrustado distinto de la ruta raíz:

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

Esta aplicación también le da al usuario un mensaje significativo en el caso de que el esquema no puede ser leido.

Cuestiones relacionadas