La validation d’un fichier XML avec un fichier de schéma XML indépendant est assez simple en Java. En revanche, dès que le schéma XML importe ou inclut d’autres schémas, la validation échoue car ces fichiers se sont pas chargés automatiquement. Après investigation, l’API de validation XML ne peut pas charger les schémas inclus. Heureusement, cette API permet d’enregistrer son propre résolveur afin de fournir le contenu des XSD incluses/importées.

Pour celà, il faut implémenter l’interface “LSInput” qui se charge de représenter le contenu du schéma chargé :

public class LocalInput implements LSInput {
	private String publicId;
	private String systemId;
	private BufferedInputStream inputStream;

	public LocalInput(String publicId, String sysId, InputStream input) {
		this.publicId = publicId;
		this.systemId = sysId;
		this.inputStream = new BufferedInputStream(input);
	}

	@Override
	public String getPublicId() {
		return publicId;
	}

	@Override
	public void setPublicId(String publicId) {
		this.publicId = publicId;
	}

	@Override
	public String getBaseURI() {return null;}

	@Override
	public InputStream getByteStream() {return null;}

	@Override
	public boolean getCertifiedText() {return false;}

	@Override
	public Reader getCharacterStream() {return null;}

	@Override
	public String getEncoding() {return null;}

	@Override
	public String getStringData() {
		synchronized (inputStream) {
			try {
				byte[] input = new byte[inputStream.available()];
				return new String(inputStream.read(input));
			} catch (IOException e) {
				return null;
			}
		}
	}

	@Override
	public void setBaseURI(String baseURI) {}

	@Override
	public void setByteStream(InputStream byteStream) {}

	@Override
	public void setCertifiedText(boolean certifiedText) {}

	@Override
	public void setCharacterStream(Reader characterStream) {}

	@Override
	public void setEncoding(String encoding) {}

	@Override
	public void setStringData(String stringData) {}

	@Override
	public String getSystemId() {
		return systemId;
	}

	@Override
	public void setSystemId(String systemId) {
		this.systemId = systemId;
	}

	public BufferedInputStream getInputStream() {
		return inputStream;
	}

	public void setInputStream(BufferedInputStream inputStream) {
		this.inputStream = inputStream;
	}
}

Ensuite, il faut implémenter le résolveur qui se charge de résoudre les dépendances avec des chemins relatifs par rapport à la XSD d’origine :

public class LocalResourceResolver implements LSResourceResolver {
	private final String relativePath;

	public LocalResourceResolver(final String relativePath) {
		this.relativePath = relativePath;
	}

	@Override
	public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
		String xsdFilePath = relativePath != null ? (relativePath + "/" + systemId) : systemId;
		InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(xsdFilePath);
		LSInput input = new LocalInput(publicId, systemId, resourceAsStream);
		input.setBaseURI(baseURI);

		return input;
	}
}

Enfin, il faut enregistrer ce résolveur auprès de la fabrique de schéma :

String xsdSourcePath;//chemin où se trouve la XSD afin de résoudre les chemins relatifs
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
factory.setResourceResolver(new LocalResourceResolver(xsdSourcePath));

Cette solution se base sur l’article XML validation with imported/included schemas de Nicolas Fränkel et ce resource resolver pour la résolution des chemins relatifs.

Avec ces deux classes, vous serez capable de valider vos fichiers XML avec des XSD factorisées (ie. utilisant les tags <xs:import /> ou <xs:include />). Bonne validation ! 🙂