Skip to content

Commit 33132c3

Browse files
committed
test extensions specific to the version to validate
Signed-off-by: Samir Romdhani <samir.romdhani_externe@rte-france.com>
1 parent f80c689 commit 33132c3

File tree

3 files changed

+88
-111
lines changed

3 files changed

+88
-111
lines changed

iidm/iidm-serde/src/main/java/com/powsybl/iidm/serde/NetworkSerDe.java

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
import org.xml.sax.SAXException;
4141

4242
import javax.xml.XMLConstants;
43+
import javax.xml.stream.XMLInputFactory;
44+
import javax.xml.stream.XMLStreamConstants;
4345
import javax.xml.stream.XMLStreamException;
46+
import javax.xml.stream.XMLStreamReader;
4447
import javax.xml.transform.Source;
4548
import javax.xml.transform.stream.StreamSource;
4649
import javax.xml.validation.Schema;
@@ -179,7 +182,7 @@ private static void validate(InputStream is, IidmVersion version, ExtensionsSupp
179182
}
180183

181184
private static Map<IidmVersion, Schema> createDefaultSchemas(ExtensionsSupplier extensionsSupplier) {
182-
Map<IidmVersion, Schema> schemasByIIdmVersion = new HashMap<>();
185+
Map<IidmVersion, Schema> schemasByIIdmVersion = new EnumMap<>(IidmVersion.class);
183186
for (IidmVersion version : IidmVersion.values()) {
184187
Schema schema = createSchema(extensionsSupplier, version);
185188
schemasByIIdmVersion.put(version, schema);
@@ -196,35 +199,15 @@ private static Schema createSchema(ExtensionsSupplier extensionsSupplier, IidmVe
196199
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
197200
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
198201
List<Source> sources = new ArrayList<>();
199-
// iidm: validation
202+
// iidm: source
200203
sources.add(new StreamSource(NetworkSerDe.class.getResourceAsStream("/xsd/" + version.getXsd())));
201-
// equipement: validation
204+
// equipment: source
202205
if (version.supportEquipmentValidationLevel()) {
203206
sources.add(new StreamSource(NetworkSerDe.class.getResourceAsStream("/xsd/" + version.getXsd(false))));
204207
}
205-
// extension: validation
206-
/**
207-
* TODO docs to complete
208-
* In some xsd extension there is the mention of other iidm version,
209-
* try to resolve those iidm version first then resolve extension xsd,
210-
*
211-
* Extension XSD look like (the extension v 1_0 needs iidm v 1_10)
212-
* <xs:schema version="1.0"
213-
* xmlns:xs="http://www.w3.org/2001/XMLSchema"
214-
* xmlns:iidm="http://www.powsybl.org/schema/iidm/1_10"
215-
* targetNamespace="http://www.powsybl.org/schema/iidm/ext/remote_reactive_power_control/1_0"
216-
* elementFormDefault="qualified">
217-
* <xs:import namespace="http://www.powsybl.org/schema/iidm/1_10" schemaLocation="iidm_V1_10.xsd"/>
218-
* ...
219-
*/
220-
List<Source> extensionsSchemas = new ArrayList<>();
221-
for (ExtensionSerDe<?, ?> extension : getExtensionsByVersion(extensionsSupplier.get().getProviders(), version)) {
222-
byte[] extensionXsd = extension.getXsdAsStream().readAllBytes();
223-
extractReferencedIidmVersions(extensionXsd)
224-
.forEach(requiredIidmVersion -> sources.add(new StreamSource(NetworkSerDe.class.getResourceAsStream("/xsd/" + requiredIidmVersion.getXsd()))));
225-
extensionsSchemas.add(new StreamSource(new ByteArrayInputStream(extensionXsd)));
226-
}
227-
sources.addAll(extensionsSchemas);
208+
// extension: source
209+
sources.addAll(getExtensionSources(extensionsSupplier, version));
210+
228211
return factory.newSchema(sources.toArray(Source[]::new));
229212
} catch (SAXException e) {
230213
throw new UncheckedSaxException(e);
@@ -233,30 +216,82 @@ private static Schema createSchema(ExtensionsSupplier extensionsSupplier, IidmVe
233216
}
234217
}
235218

236-
private static List<ExtensionSerDe<?, ?>> getExtensionsByVersion(Collection<ExtensionSerDe> extensionList, IidmVersion version) {
219+
/**
220+
* Build the list of XSD required to validate extensions for a given IIDM version.
221+
*
222+
* <p>Some extension XSDs import an IIDM schema through {@code xs:import/@schemaLocation}</p>
223+
* This method parses each supported extension XSD, extracts such schema locations,
224+
* and adds the corresponding IIDM XSD resources from {@code /xsd/...} to the returned sources.
225+
*
226+
* <p>Extension snippet:</p>
227+
* <pre>{@code
228+
* ...
229+
* targetNamespace="http://www.powsybl.org/schema/iidm/ext/extension-name/1_0"
230+
* xmlns:iidm="http://www.powsybl.org/schema/iidm/1_10">
231+
* <xs:import namespace="http://www.powsybl.org/schema/iidm/1_10" schemaLocation="iidm_V1_10.xsd"/>
232+
* </xs:schema>
233+
* }</pre>
234+
*
235+
* <p>Only extensions supported by the provided IIDM version are considered.</p>
236+
*
237+
* @param extensionsSupplier extension provider used to discover available extension serializers
238+
* @param version IIDM version used to filter compatible extensions
239+
* @return list of additional schema sources required by extension
240+
*/
241+
private static List<Source> getExtensionSources(ExtensionsSupplier extensionsSupplier, IidmVersion version) throws IOException {
242+
List<Source> sources = new ArrayList<>();
243+
for (ExtensionSerDe<?, ?> extension : getSupportedExtensionsByIIdmVersion(extensionsSupplier.get().getProviders(), version)) {
244+
byte[] extensionXsd = extension.getXsdAsStream().readAllBytes();
245+
extractSchemaLocations(extensionXsd)
246+
.forEach(schemaLocation -> sources.add(new StreamSource(NetworkSerDe.class.getResourceAsStream("/xsd/" + schemaLocation))));
247+
sources.add(new StreamSource(new ByteArrayInputStream(extensionXsd)));
248+
}
249+
return sources;
250+
}
251+
252+
private static List<ExtensionSerDe<?, ?>> getSupportedExtensionsByIIdmVersion(Collection<ExtensionSerDe> extensionSerDes, IidmVersion version) {
237253
List<ExtensionSerDe<?, ?>> extensions = new ArrayList<>();
238-
for (ExtensionSerDe<?, ?> extension : extensionList) {
239-
if (extension instanceof AbstractVersionableNetworkExtensionSerDe<?, ?, ?> ab) {
254+
for (ExtensionSerDe<?, ?> extensionSerDe : extensionSerDes) {
255+
if (extensionSerDe instanceof AbstractVersionableNetworkExtensionSerDe<?, ?, ?> ab) {
240256
if (ab.versionExists(version)) {
241-
extensions.add(extension);
257+
extensions.add(extensionSerDe);
242258
}
243259
} else {
244-
// keep non versionable extensions
245-
extensions.add(extension);
260+
// no versionable extensions
261+
extensions.add(extensionSerDe);
246262
}
247263
}
248264
return extensions;
249265
}
250266

251-
private static Set<IidmVersion> extractReferencedIidmVersions(byte[] xsdBytes) {
252-
String xsdContent = new String(xsdBytes, StandardCharsets.UTF_8);
253-
Set<IidmVersion> versions = new HashSet<>();
254-
for (IidmVersion v : IidmVersion.values()) {
255-
if (xsdContent.contains(v.getXsd()) || v.supportEquipmentValidationLevel() && xsdContent.contains(v.getXsd(false))) {
256-
versions.add(v);
267+
/**
268+
* Extract {@code xs:import/@schemaLocation} in an XSD document
269+
* @param xsdBytes XSD content as bytes
270+
* @return schema locations found in {@code xs:import}
271+
*/
272+
private static List<String> extractSchemaLocations(byte[] xsdBytes) {
273+
List<String> locations = new ArrayList<>();
274+
XMLInputFactory xif = XMLInputFactory.newFactory();
275+
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
276+
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
277+
try {
278+
XMLStreamReader reader = xif.createXMLStreamReader(new ByteArrayInputStream(xsdBytes));
279+
while (reader.hasNext()) {
280+
int event = reader.next();
281+
if (event == XMLStreamConstants.START_ELEMENT
282+
&& XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(reader.getNamespaceURI())
283+
&& ("import".equals(reader.getLocalName()))) {
284+
String schemaLocation = reader.getAttributeValue(null, "schemaLocation");
285+
if (schemaLocation != null && !schemaLocation.isBlank()) {
286+
locations.add(schemaLocation);
287+
}
288+
}
257289
}
290+
reader.close();
291+
} catch (XMLStreamException e) {
292+
throw new RuntimeException(e);
258293
}
259-
return versions;
294+
return locations;
260295
}
261296

262297
private static void throwExceptionIfOption(AbstractOptions<?> options, String message) {

iidm/iidm-serde/src/test/java/com/powsybl/iidm/serde/NetworkSerDeTest.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -391,25 +391,17 @@ void testExportWithoutFlatten() {
391391
}
392392

393393
@Test
394-
void validateAllShouldSucceed() throws IOException {
395-
try (InputStream is = getVersionedNetworkAsStream("shuntRoundTripRef.xml", IidmVersion.V_1_16)) {
396-
assertDoesNotThrow(() -> NetworkSerDe.validate(is));
394+
void testValidateByVersionWhenValidNetwork() throws IOException {
395+
try (InputStream is = getClass().getResourceAsStream("/V1_2/shuntRoundTripRef.xml")) {
396+
assertDoesNotThrow(() -> NetworkSerDe.validate(is, IidmVersion.V_1_2));
397397
}
398-
//validate network contain extension: initial version of validate (TODO to remove)
399-
try (InputStream is = getClass().getResourceAsStream("/network-with-extensions.xiidm")) {
400-
assertDoesNotThrow(() -> NetworkSerDe.validate(is));
401-
}
402-
}
403-
404-
@Test
405-
void validateByVersionShouldSucceed() throws IOException {
406398
try (InputStream is = getClass().getResourceAsStream("/V1_16/shuntRoundTripRef.xml")) {
407399
assertDoesNotThrow(() -> NetworkSerDe.validate(is, IidmVersion.V_1_16));
408400
}
409401
}
410402

411403
@Test
412-
void validateByVersionShouldFail() throws IOException {
404+
void testValidateByVersionWhenInvalidNetwork() throws IOException {
413405
try (InputStream is = getClass().getResourceAsStream("/V1_16/shuntOldTagName.xml")) {
414406
assertThatCode(() -> NetworkSerDe.validate(is, IidmVersion.V_1_16))
415407
.isInstanceOf(com.powsybl.commons.exceptions.UncheckedSaxException.class)
@@ -418,8 +410,16 @@ void validateByVersionShouldFail() throws IOException {
418410
}
419411

420412
@Test
421-
void validateByVersionWhenExtensionExistShouldSucceed() throws IOException {
422-
try (InputStream is = getClass().getResourceAsStream("/network-with-extensions.xiidm")) {
413+
void testValidateByVersionWithSlackTerminalExtension() throws IOException {
414+
// Given extension: slack_terminal, version 1.5 that require iidm version 1.8 when validate should succeed
415+
try (InputStream is = getClass().getResourceAsStream("/slackTerminal.xml")) {
416+
assertDoesNotThrow(() -> NetworkSerDe.validate(is, IidmVersion.V_1_16));
417+
}
418+
}
419+
420+
@Test
421+
void testValidateByVersionWithTerminalMockExtension() throws IOException {
422+
try (InputStream is = getClass().getResourceAsStream("/V1_16/eurostag-tutorial-example1-with-terminalMock-ext.xml")) {
423423
assertDoesNotThrow(() -> NetworkSerDe.validate(is, IidmVersion.V_1_16));
424424
}
425425
}

iidm/iidm-serde/src/test/resources/network-with-extensions.xiidm

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)