diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/ConversionUtil.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/ConversionUtil.java
index f25813fbbc8..df608a0f11b 100644
--- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/ConversionUtil.java
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/ConversionUtil.java
@@ -28,6 +28,8 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
@@ -94,4 +96,21 @@ public static Network readCgmesResources(Properties properties, String dir, Stri
         ReadOnlyDataSource ds = new ResourceDataSource("CGMES input file(s)", new ResourceSet(dir, files));
         return Network.read(ds, properties);
     }
+
+    public static String getFirstMatch(String text, Pattern pattern) {
+        Matcher matcher = pattern.matcher(text);
+        if (matcher.find()) {
+            return matcher.group(1);
+        }
+        return null;
+    }
+
+    public static Set<String> getUniqueMatches(String text, Pattern pattern) {
+        Set<String> matches = new HashSet<>();
+        Matcher matcher = pattern.matcher(text);
+        while (matcher.find()) {
+            matches.add(matcher.group(1));
+        }
+        return matches;
+    }
 }
diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/OperationalLimitsGroupTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/OperationalLimitsGroupTest.java
index 21b6a67f6e9..e778289b383 100644
--- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/OperationalLimitsGroupTest.java
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/OperationalLimitsGroupTest.java
@@ -16,9 +16,9 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.util.*;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import static com.powsybl.cgmes.conversion.test.ConversionUtil.getUniqueMatches;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
@@ -65,10 +65,10 @@ void exportSelectedLimitsGroupTest() throws IOException {
         String exportSelectedLimitsGroupXml = Files.readString(tmpDir.resolve("ExportSelectedLimitsGroup_EQ.xml"));
 
         // There is 1 set on side 1 which is selected, and there are 2 sets on side 2 but none of them is selected
-        assertEquals(1, getOccurrences(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_SET).size());
-        assertEquals(3, getOccurrences(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_TYPE).size());
-        assertEquals(0, getOccurrences(exportSelectedLimitsGroupXml, ACTIVE_POWER_LIMIT).size());
-        assertEquals(3, getOccurrences(exportSelectedLimitsGroupXml, CURRENT_LIMIT).size());
+        assertEquals(1, getUniqueMatches(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_SET).size());
+        assertEquals(3, getUniqueMatches(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_TYPE).size());
+        assertEquals(0, getUniqueMatches(exportSelectedLimitsGroupXml, ACTIVE_POWER_LIMIT).size());
+        assertEquals(3, getUniqueMatches(exportSelectedLimitsGroupXml, CURRENT_LIMIT).size());
 
         // Manually select one of the limits group on side 2 and export again
         Line line = network.getLine("Line");
@@ -77,10 +77,10 @@ void exportSelectedLimitsGroupTest() throws IOException {
         exportSelectedLimitsGroupXml = Files.readString(tmpDir.resolve("ExportSelectedLimitsGroup_EQ.xml"));
 
         // That makes 1 set selected on each side = 2 in total
-        assertEquals(2, getOccurrences(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_SET).size());
-        assertEquals(3, getOccurrences(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_TYPE).size());
-        assertEquals(0, getOccurrences(exportSelectedLimitsGroupXml, ACTIVE_POWER_LIMIT).size());
-        assertEquals(6, getOccurrences(exportSelectedLimitsGroupXml, CURRENT_LIMIT).size());
+        assertEquals(2, getUniqueMatches(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_SET).size());
+        assertEquals(3, getUniqueMatches(exportSelectedLimitsGroupXml, OPERATIONAL_LIMIT_TYPE).size());
+        assertEquals(0, getUniqueMatches(exportSelectedLimitsGroupXml, ACTIVE_POWER_LIMIT).size());
+        assertEquals(6, getUniqueMatches(exportSelectedLimitsGroupXml, CURRENT_LIMIT).size());
     }
 
     @Test
@@ -95,19 +95,10 @@ void exportAllLimitsGroupTest() throws IOException {
         String exportAllLimitsGroupXml = Files.readString(tmpDir.resolve("ExportAllLimitsGroup_EQ.xml"));
 
         // All 3 OperationalLimitsGroup are exported, even though only 2 are selected
-        assertEquals(3, getOccurrences(exportAllLimitsGroupXml, OPERATIONAL_LIMIT_SET).size());
-        assertEquals(3, getOccurrences(exportAllLimitsGroupXml, OPERATIONAL_LIMIT_TYPE).size());
-        assertEquals(3, getOccurrences(exportAllLimitsGroupXml, ACTIVE_POWER_LIMIT).size());
-        assertEquals(9, getOccurrences(exportAllLimitsGroupXml, CURRENT_LIMIT).size());
-    }
-
-    private Set<String> getOccurrences(String xml, Pattern pattern) {
-        Set<String> matches = new HashSet<>();
-        Matcher matcher = pattern.matcher(xml);
-        while (matcher.find()) {
-            matches.add(matcher.group(1));
-        }
-        return matches;
+        assertEquals(3, getUniqueMatches(exportAllLimitsGroupXml, OPERATIONAL_LIMIT_SET).size());
+        assertEquals(3, getUniqueMatches(exportAllLimitsGroupXml, OPERATIONAL_LIMIT_TYPE).size());
+        assertEquals(3, getUniqueMatches(exportAllLimitsGroupXml, ACTIVE_POWER_LIMIT).size());
+        assertEquals(9, getUniqueMatches(exportAllLimitsGroupXml, CURRENT_LIMIT).size());
     }
 
 }
diff --git a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/CommonGridModelExportTest.java b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/CommonGridModelExportTest.java
index 26f2917527d..e3d30b1021b 100644
--- a/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/CommonGridModelExportTest.java
+++ b/cgmes/cgmes-conversion/src/test/java/com/powsybl/cgmes/conversion/test/export/CommonGridModelExportTest.java
@@ -15,7 +15,6 @@
 import com.powsybl.cgmes.model.CgmesMetadataModel;
 import com.powsybl.cgmes.model.CgmesNamespace;
 import com.powsybl.cgmes.model.CgmesSubset;
-import com.powsybl.commons.PowsyblException;
 import com.powsybl.commons.datasource.DataSource;
 import com.powsybl.commons.datasource.DirectoryDataSource;
 import com.powsybl.commons.datasource.MemDataSource;
@@ -35,9 +34,9 @@
 import java.nio.file.Path;
 import java.time.ZonedDateTime;
 import java.util.*;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import static com.powsybl.cgmes.conversion.test.ConversionUtil.*;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
@@ -80,8 +79,8 @@ void testIgmExportNoModelsNoPropertiesVersion() throws IOException {
         String exportedBeSvXml = Files.readString(tmpDir.resolve(basename + "_SV.xml"));
 
         // There is no version number for original models, so if exported as IGM they would have version equals to 1
-        assertEquals("1", getFirstOccurrence(exportedBeSshXml, REGEX_VERSION));
-        assertEquals("1", getFirstOccurrence(exportedBeSvXml, REGEX_VERSION));
+        assertEquals("1", getFirstMatch(exportedBeSshXml, REGEX_VERSION));
+        assertEquals("1", getFirstMatch(exportedBeSvXml, REGEX_VERSION));
     }
 
     @Test
@@ -99,9 +98,9 @@ void testCgmExportWithModelsVersion() throws IOException {
         String updatedCgmSvXml = Files.readString(tmpDir.resolve(basename + "_SV.xml"));
 
         // Version number should be increased from original models and be the same for all instance files
-        assertEquals("1", getFirstOccurrence(updatedBeSshXml, REGEX_VERSION));
-        assertEquals("1", getFirstOccurrence(updatedNlSshXml, REGEX_VERSION));
-        assertEquals("1", getFirstOccurrence(updatedCgmSvXml, REGEX_VERSION));
+        assertEquals("1", getFirstMatch(updatedBeSshXml, REGEX_VERSION));
+        assertEquals("1", getFirstMatch(updatedNlSshXml, REGEX_VERSION));
+        assertEquals("1", getFirstMatch(updatedCgmSvXml, REGEX_VERSION));
     }
 
     @Test
@@ -119,20 +118,20 @@ void testCgmExportNoModelsNoProperties() throws IOException {
         String updatedCgmSvXml = Files.readString(tmpDir.resolve(basename + "_SV.xml"));
 
         // Scenario time should be the same for all models
-        assertEquals("2021-02-03T04:30:00Z", getFirstOccurrence(updatedBeSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2021-02-03T04:30:00Z", getFirstOccurrence(updatedNlSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2021-02-03T04:30:00Z", getFirstOccurrence(updatedCgmSvXml, REGEX_SCENARIO_TIME));
+        assertEquals("2021-02-03T04:30:00Z", getFirstMatch(updatedBeSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2021-02-03T04:30:00Z", getFirstMatch(updatedNlSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2021-02-03T04:30:00Z", getFirstMatch(updatedCgmSvXml, REGEX_SCENARIO_TIME));
 
         // Description should be the default one
-        assertEquals("SSH Model", getFirstOccurrence(updatedBeSshXml, REGEX_DESCRIPTION));
-        assertEquals("SSH Model", getFirstOccurrence(updatedNlSshXml, REGEX_DESCRIPTION));
-        assertEquals("SV Model", getFirstOccurrence(updatedCgmSvXml, REGEX_DESCRIPTION));
+        assertEquals("SSH Model", getFirstMatch(updatedBeSshXml, REGEX_DESCRIPTION));
+        assertEquals("SSH Model", getFirstMatch(updatedNlSshXml, REGEX_DESCRIPTION));
+        assertEquals("SV Model", getFirstMatch(updatedCgmSvXml, REGEX_DESCRIPTION));
 
         // There is no version number for original models, so if exported as IGM they would have version equals to 1
         // Version number for updated models is increased by 1, so it equals to 2 in the end
-        assertEquals("2", getFirstOccurrence(updatedBeSshXml, REGEX_VERSION));
-        assertEquals("2", getFirstOccurrence(updatedNlSshXml, REGEX_VERSION));
-        assertEquals("2", getFirstOccurrence(updatedCgmSvXml, REGEX_VERSION));
+        assertEquals("2", getFirstMatch(updatedBeSshXml, REGEX_VERSION));
+        assertEquals("2", getFirstMatch(updatedNlSshXml, REGEX_VERSION));
+        assertEquals("2", getFirstMatch(updatedCgmSvXml, REGEX_VERSION));
 
         // The updated CGM SV should depend on the updated IGMs SSH and on the original IGMs TP
         // Here the version number part of the id 1 for original models and 2 for updated ones
@@ -143,27 +142,27 @@ void testCgmExportNoModelsNoProperties() throws IOException {
         String originalBeTpBdId = "urn:uuid:Network_BE_N_TOPOLOGY_BOUNDARY_2021-02-03T04:30:00Z_1_1D__FM";
         String originalNlTpBdId = "urn:uuid:Network_NL_N_TOPOLOGY_BOUNDARY_2021-02-03T04:30:00Z_1_1D__FM";
         Set<String> expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId, originalBeTpBdId, originalNlTpBdId);
-        assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON));
+        assertEquals(expectedDependencies, getUniqueMatches(updatedCgmSvXml, REGEX_DEPENDENT_ON));
 
         // Each updated IGM SSH should supersede the original one and depend on the original EQ
         String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM";
         String originalBeEqId = "urn:uuid:Network_BE_N_EQUIPMENT_2021-02-03T04:30:00Z_1_1D__FM";
         String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM";
         String originalNlEqId = "urn:uuid:Network_NL_N_EQUIPMENT_2021-02-03T04:30:00Z_1_1D__FM";
-        assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES));
-        assertEquals(originalBeEqId, getFirstOccurrence(updatedBeSshXml, REGEX_DEPENDENT_ON));
-        assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES));
-        assertEquals(originalNlEqId, getFirstOccurrence(updatedNlSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(originalBeSshId, getFirstMatch(updatedBeSshXml, REGEX_SUPERSEDES));
+        assertEquals(originalBeEqId, getFirstMatch(updatedBeSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(originalNlSshId, getFirstMatch(updatedNlSshXml, REGEX_SUPERSEDES));
+        assertEquals(originalNlEqId, getFirstMatch(updatedNlSshXml, REGEX_DEPENDENT_ON));
 
         // Profiles should be consistent with the instance files
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedBeSshXml, REGEX_PROFILE));
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedNlSshXml, REGEX_PROFILE));
-        assertEquals("http://entsoe.eu/CIM/StateVariables/4/1", getFirstOccurrence(updatedCgmSvXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedBeSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedNlSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/StateVariables/4/1", getFirstMatch(updatedCgmSvXml, REGEX_PROFILE));
 
         // All MAS should be equal to the default one since none has been provided
-        assertEquals("powsybl.org", getFirstOccurrence(updatedBeSshXml, REGEX_MAS));
-        assertEquals("powsybl.org", getFirstOccurrence(updatedNlSshXml, REGEX_MAS));
-        assertEquals("powsybl.org", getFirstOccurrence(updatedCgmSvXml, REGEX_MAS));
+        assertEquals("powsybl.org", getFirstMatch(updatedBeSshXml, REGEX_MAS));
+        assertEquals("powsybl.org", getFirstMatch(updatedNlSshXml, REGEX_MAS));
+        assertEquals("powsybl.org", getFirstMatch(updatedCgmSvXml, REGEX_MAS));
     }
 
     @Test
@@ -182,19 +181,19 @@ void testCgmExportWithModelsForSubnetworks() throws IOException {
         String updatedCgmSvXml = Files.readString(tmpDir.resolve(basename + "_SV.xml"));
 
         // Scenario time should be the same for all models
-        assertEquals("2021-02-03T04:30:00Z", getFirstOccurrence(updatedBeSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2021-02-03T04:30:00Z", getFirstOccurrence(updatedNlSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2021-02-03T04:30:00Z", getFirstOccurrence(updatedCgmSvXml, REGEX_SCENARIO_TIME));
+        assertEquals("2021-02-03T04:30:00Z", getFirstMatch(updatedBeSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2021-02-03T04:30:00Z", getFirstMatch(updatedNlSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2021-02-03T04:30:00Z", getFirstMatch(updatedCgmSvXml, REGEX_SCENARIO_TIME));
 
         // IGM descriptions should be the ones provided in subnetwork models, CGM description should be the default one
-        assertEquals("BE network description", getFirstOccurrence(updatedBeSshXml, REGEX_DESCRIPTION));
-        assertEquals("NL network description", getFirstOccurrence(updatedNlSshXml, REGEX_DESCRIPTION));
-        assertEquals("SV Model", getFirstOccurrence(updatedCgmSvXml, REGEX_DESCRIPTION));
+        assertEquals("BE network description", getFirstMatch(updatedBeSshXml, REGEX_DESCRIPTION));
+        assertEquals("NL network description", getFirstMatch(updatedNlSshXml, REGEX_DESCRIPTION));
+        assertEquals("SV Model", getFirstMatch(updatedCgmSvXml, REGEX_DESCRIPTION));
 
         // Version number should be increased from original models and be the same for all instance files
-        assertEquals("2", getFirstOccurrence(updatedBeSshXml, REGEX_VERSION));
-        assertEquals("2", getFirstOccurrence(updatedNlSshXml, REGEX_VERSION));
-        assertEquals("2", getFirstOccurrence(updatedCgmSvXml, REGEX_VERSION));
+        assertEquals("2", getFirstMatch(updatedBeSshXml, REGEX_VERSION));
+        assertEquals("2", getFirstMatch(updatedNlSshXml, REGEX_VERSION));
+        assertEquals("2", getFirstMatch(updatedCgmSvXml, REGEX_VERSION));
 
         // The updated CGM SV should depend on the updated IGMs SSH and on the original IGMs TP
         String updatedBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_2_1D__FM";
@@ -203,25 +202,25 @@ void testCgmExportWithModelsForSubnetworks() throws IOException {
         String originalNlTpId = "urn:uuid:Network_NL_N_TOPOLOGY_2021-02-03T04:30:00Z_1_1D__FM";
         String originalTpBdId = "Common TP_BD model ID";
         Set<String> expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId, originalTpBdId);
-        assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON));
+        assertEquals(expectedDependencies, getUniqueMatches(updatedCgmSvXml, REGEX_DEPENDENT_ON));
 
         // Each updated IGM SSH should supersede the original one and depend on the original EQ
         String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM";
         String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM";
-        assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES));
-        assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES));
-        assertEquals(Set.of("BE EQ model ID"), getOccurrences(updatedBeSshXml, REGEX_DEPENDENT_ON));
-        assertEquals(Set.of("NL EQ model ID"), getOccurrences(updatedNlSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(originalBeSshId, getFirstMatch(updatedBeSshXml, REGEX_SUPERSEDES));
+        assertEquals(originalNlSshId, getFirstMatch(updatedNlSshXml, REGEX_SUPERSEDES));
+        assertEquals(Set.of("BE EQ model ID"), getUniqueMatches(updatedBeSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(Set.of("NL EQ model ID"), getUniqueMatches(updatedNlSshXml, REGEX_DEPENDENT_ON));
 
         // Profiles should be consistent with the instance files
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedBeSshXml, REGEX_PROFILE));
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedNlSshXml, REGEX_PROFILE));
-        assertEquals("http://entsoe.eu/CIM/StateVariables/4/1", getFirstOccurrence(updatedCgmSvXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedBeSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedNlSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/StateVariables/4/1", getFirstMatch(updatedCgmSvXml, REGEX_PROFILE));
 
         // IGM MAS should be the ones provided in subnetwork models, CGM MAS should be the default one
-        assertEquals("http://elia.be/CGMES/2.4.15", getFirstOccurrence(updatedBeSshXml, REGEX_MAS));
-        assertEquals("http://tennet.nl/CGMES/2.4.15", getFirstOccurrence(updatedNlSshXml, REGEX_MAS));
-        assertEquals("powsybl.org", getFirstOccurrence(updatedCgmSvXml, REGEX_MAS));
+        assertEquals("http://elia.be/CGMES/2.4.15", getFirstMatch(updatedBeSshXml, REGEX_MAS));
+        assertEquals("http://tennet.nl/CGMES/2.4.15", getFirstMatch(updatedNlSshXml, REGEX_MAS));
+        assertEquals("powsybl.org", getFirstMatch(updatedCgmSvXml, REGEX_MAS));
     }
 
     @Test
@@ -242,20 +241,20 @@ void testCgmExportWithModelsForAllNetworks() throws IOException {
 
         // The main network has a different scenario time than the subnetworks
         // All updated models should get that scenario time
-        assertEquals("2022-03-04T05:30:00Z", getFirstOccurrence(updatedBeSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2022-03-04T05:30:00Z", getFirstOccurrence(updatedNlSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2022-03-04T05:30:00Z", getFirstOccurrence(updatedCgmSvXml, REGEX_SCENARIO_TIME));
+        assertEquals("2022-03-04T05:30:00Z", getFirstMatch(updatedBeSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2022-03-04T05:30:00Z", getFirstMatch(updatedNlSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2022-03-04T05:30:00Z", getFirstMatch(updatedCgmSvXml, REGEX_SCENARIO_TIME));
 
         // IGM descriptions should be the ones provided in subnetwork models, CGM description should be the one provided in main network model
-        assertEquals("BE network description", getFirstOccurrence(updatedBeSshXml, REGEX_DESCRIPTION));
-        assertEquals("NL network description", getFirstOccurrence(updatedNlSshXml, REGEX_DESCRIPTION));
-        assertEquals("Merged network description", getFirstOccurrence(updatedCgmSvXml, REGEX_DESCRIPTION));
+        assertEquals("BE network description", getFirstMatch(updatedBeSshXml, REGEX_DESCRIPTION));
+        assertEquals("NL network description", getFirstMatch(updatedNlSshXml, REGEX_DESCRIPTION));
+        assertEquals("Merged network description", getFirstMatch(updatedCgmSvXml, REGEX_DESCRIPTION));
 
         // The main network has a different version number (3) than the subnetworks (1)
         // Updated models should use next version taking into account the max version number of inputs (next version is 4)
-        assertEquals("4", getFirstOccurrence(updatedBeSshXml, REGEX_VERSION));
-        assertEquals("4", getFirstOccurrence(updatedNlSshXml, REGEX_VERSION));
-        assertEquals("4", getFirstOccurrence(updatedCgmSvXml, REGEX_VERSION));
+        assertEquals("4", getFirstMatch(updatedBeSshXml, REGEX_VERSION));
+        assertEquals("4", getFirstMatch(updatedNlSshXml, REGEX_VERSION));
+        assertEquals("4", getFirstMatch(updatedCgmSvXml, REGEX_VERSION));
 
         // The updated CGM SV should depend on the updated IGMs SSH and on the original IGMs TP
         // The model of the main network brings an additional dependency
@@ -267,27 +266,27 @@ void testCgmExportWithModelsForAllNetworks() throws IOException {
         String additionalDependency = "Additional dependency";
         Set<String> expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId,
                 originalNlTpId, originalTpBdId, additionalDependency);
-        assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON));
+        assertEquals(expectedDependencies, getUniqueMatches(updatedCgmSvXml, REGEX_DEPENDENT_ON));
 
         // Each updated IGM SSH should supersede the original one and depend on the original EQ
         String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_1_1D__FM";
         String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_1_1D__FM";
-        assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES));
-        assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES));
-        assertEquals(Set.of("BE EQ model ID"), getOccurrences(updatedBeSshXml, REGEX_DEPENDENT_ON));
-        assertEquals(Set.of("NL EQ model ID"), getOccurrences(updatedNlSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(originalBeSshId, getFirstMatch(updatedBeSshXml, REGEX_SUPERSEDES));
+        assertEquals(originalNlSshId, getFirstMatch(updatedNlSshXml, REGEX_SUPERSEDES));
+        assertEquals(Set.of("BE EQ model ID"), getUniqueMatches(updatedBeSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(Set.of("NL EQ model ID"), getUniqueMatches(updatedNlSshXml, REGEX_DEPENDENT_ON));
 
         // Profiles should be consistent with the instance files
         // The model of the main network brings an additional profile
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedBeSshXml, REGEX_PROFILE));
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedNlSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedBeSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedNlSshXml, REGEX_PROFILE));
         Set<String> expectedProfiles = Set.of("Additional profile", "http://entsoe.eu/CIM/StateVariables/4/1");
-        assertEquals(expectedProfiles, getOccurrences(updatedCgmSvXml, REGEX_PROFILE));
+        assertEquals(expectedProfiles, getUniqueMatches(updatedCgmSvXml, REGEX_PROFILE));
 
         // IGM MAS should be the ones provided in subnetwork models, CGM MAS should be the one provided in main network model
-        assertEquals("http://elia.be/CGMES/2.4.15", getFirstOccurrence(updatedBeSshXml, REGEX_MAS));
-        assertEquals("http://tennet.nl/CGMES/2.4.15", getFirstOccurrence(updatedNlSshXml, REGEX_MAS));
-        assertEquals("Modeling Authority", getFirstOccurrence(updatedCgmSvXml, REGEX_MAS));
+        assertEquals("http://elia.be/CGMES/2.4.15", getFirstMatch(updatedBeSshXml, REGEX_MAS));
+        assertEquals("http://tennet.nl/CGMES/2.4.15", getFirstMatch(updatedNlSshXml, REGEX_MAS));
+        assertEquals("Modeling Authority", getFirstMatch(updatedCgmSvXml, REGEX_MAS));
     }
 
     @Test
@@ -309,19 +308,19 @@ void testCgmExportWithProperties() throws IOException {
         String updatedCgmSvXml = Files.readString(tmpDir.resolve(basename + "_SV.xml"));
 
         // Scenario time should be the same for all models
-        assertEquals("2021-02-03T04:30:00Z", getFirstOccurrence(updatedBeSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2021-02-03T04:30:00Z", getFirstOccurrence(updatedNlSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2021-02-03T04:30:00Z", getFirstOccurrence(updatedCgmSvXml, REGEX_SCENARIO_TIME));
+        assertEquals("2021-02-03T04:30:00Z", getFirstMatch(updatedBeSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2021-02-03T04:30:00Z", getFirstMatch(updatedNlSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2021-02-03T04:30:00Z", getFirstMatch(updatedCgmSvXml, REGEX_SCENARIO_TIME));
 
         // Description should be the one provided as parameter and be the same for all instance files
-        assertEquals("Common Grid Model export", getFirstOccurrence(updatedBeSshXml, REGEX_DESCRIPTION));
-        assertEquals("Common Grid Model export", getFirstOccurrence(updatedNlSshXml, REGEX_DESCRIPTION));
-        assertEquals("Common Grid Model export", getFirstOccurrence(updatedCgmSvXml, REGEX_DESCRIPTION));
+        assertEquals("Common Grid Model export", getFirstMatch(updatedBeSshXml, REGEX_DESCRIPTION));
+        assertEquals("Common Grid Model export", getFirstMatch(updatedNlSshXml, REGEX_DESCRIPTION));
+        assertEquals("Common Grid Model export", getFirstMatch(updatedCgmSvXml, REGEX_DESCRIPTION));
 
         // Version number should be the one provided as parameter and be the same for all instance files
-        assertEquals("4", getFirstOccurrence(updatedBeSshXml, REGEX_VERSION));
-        assertEquals("4", getFirstOccurrence(updatedNlSshXml, REGEX_VERSION));
-        assertEquals("4", getFirstOccurrence(updatedCgmSvXml, REGEX_VERSION));
+        assertEquals("4", getFirstMatch(updatedBeSshXml, REGEX_VERSION));
+        assertEquals("4", getFirstMatch(updatedNlSshXml, REGEX_VERSION));
+        assertEquals("4", getFirstMatch(updatedCgmSvXml, REGEX_VERSION));
 
         // The updated CGM SV should depend on the updated IGMs SSH and on the original IGMs TP
         String updatedBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_4_1D__FM";
@@ -330,27 +329,27 @@ void testCgmExportWithProperties() throws IOException {
         String originalNlTpId = "urn:uuid:Network_NL_N_TOPOLOGY_2021-02-03T04:30:00Z_1_1D__FM";
         String originalTpBdId = "ENTSOE TP_BD model ID";
         Set<String> expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId, originalNlTpId, originalTpBdId);
-        assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON));
+        assertEquals(expectedDependencies, getUniqueMatches(updatedCgmSvXml, REGEX_DEPENDENT_ON));
 
         // Each updated IGM SSH should supersede the original one and depend on the original EQ
         String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM";
         String originalBeEqId = "urn:uuid:Network_BE_N_EQUIPMENT_2021-02-03T04:30:00Z_1_1D__FM";
         String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2021-02-03T04:30:00Z_1_1D__FM";
         String originalNlEqId = "urn:uuid:Network_NL_N_EQUIPMENT_2021-02-03T04:30:00Z_1_1D__FM";
-        assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES));
-        assertEquals(originalBeEqId, getFirstOccurrence(updatedBeSshXml, REGEX_DEPENDENT_ON));
-        assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES));
-        assertEquals(originalNlEqId, getFirstOccurrence(updatedNlSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(originalBeSshId, getFirstMatch(updatedBeSshXml, REGEX_SUPERSEDES));
+        assertEquals(originalBeEqId, getFirstMatch(updatedBeSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(originalNlSshId, getFirstMatch(updatedNlSshXml, REGEX_SUPERSEDES));
+        assertEquals(originalNlEqId, getFirstMatch(updatedNlSshXml, REGEX_DEPENDENT_ON));
 
         // Profiles should be consistent with the instance files
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedBeSshXml, REGEX_PROFILE));
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedNlSshXml, REGEX_PROFILE));
-        assertEquals("http://entsoe.eu/CIM/StateVariables/4/1", getFirstOccurrence(updatedCgmSvXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedBeSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedNlSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/StateVariables/4/1", getFirstMatch(updatedCgmSvXml, REGEX_PROFILE));
 
         // IGM MAS should be the default ones, CGM MAS should be the one provided as parameter
-        assertEquals("powsybl.org", getFirstOccurrence(updatedBeSshXml, REGEX_MAS));
-        assertEquals("powsybl.org", getFirstOccurrence(updatedNlSshXml, REGEX_MAS));
-        assertEquals("Regional Coordination Center", getFirstOccurrence(updatedCgmSvXml, REGEX_MAS));
+        assertEquals("powsybl.org", getFirstMatch(updatedBeSshXml, REGEX_MAS));
+        assertEquals("powsybl.org", getFirstMatch(updatedNlSshXml, REGEX_MAS));
+        assertEquals("Regional Coordination Center", getFirstMatch(updatedCgmSvXml, REGEX_MAS));
     }
 
     @Test
@@ -375,19 +374,19 @@ void testCgmExportWithModelsAndProperties() throws IOException {
 
         // The main network has a different scenario time than the subnetworks
         // All updated models should get that scenario time
-        assertEquals("2022-03-04T05:30:00Z", getFirstOccurrence(updatedBeSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2022-03-04T05:30:00Z", getFirstOccurrence(updatedNlSshXml, REGEX_SCENARIO_TIME));
-        assertEquals("2022-03-04T05:30:00Z", getFirstOccurrence(updatedCgmSvXml, REGEX_SCENARIO_TIME));
+        assertEquals("2022-03-04T05:30:00Z", getFirstMatch(updatedBeSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2022-03-04T05:30:00Z", getFirstMatch(updatedNlSshXml, REGEX_SCENARIO_TIME));
+        assertEquals("2022-03-04T05:30:00Z", getFirstMatch(updatedCgmSvXml, REGEX_SCENARIO_TIME));
 
         // Both the models and a property define the description. The property should prevail.
-        assertEquals("Common Grid Model export", getFirstOccurrence(updatedBeSshXml, REGEX_DESCRIPTION));
-        assertEquals("Common Grid Model export", getFirstOccurrence(updatedNlSshXml, REGEX_DESCRIPTION));
-        assertEquals("Common Grid Model export", getFirstOccurrence(updatedCgmSvXml, REGEX_DESCRIPTION));
+        assertEquals("Common Grid Model export", getFirstMatch(updatedBeSshXml, REGEX_DESCRIPTION));
+        assertEquals("Common Grid Model export", getFirstMatch(updatedNlSshXml, REGEX_DESCRIPTION));
+        assertEquals("Common Grid Model export", getFirstMatch(updatedCgmSvXml, REGEX_DESCRIPTION));
 
         // Both the models and a property define the version number. The property should prevail.
-        assertEquals("4", getFirstOccurrence(updatedBeSshXml, REGEX_VERSION));
-        assertEquals("4", getFirstOccurrence(updatedNlSshXml, REGEX_VERSION));
-        assertEquals("4", getFirstOccurrence(updatedCgmSvXml, REGEX_VERSION));
+        assertEquals("4", getFirstMatch(updatedBeSshXml, REGEX_VERSION));
+        assertEquals("4", getFirstMatch(updatedNlSshXml, REGEX_VERSION));
+        assertEquals("4", getFirstMatch(updatedCgmSvXml, REGEX_VERSION));
 
         // The updated CGM SV should depend on the updated IGMs SSH and on the original IGMs TP
         // The model of the main network brings an additional dependency
@@ -399,26 +398,26 @@ void testCgmExportWithModelsAndProperties() throws IOException {
         String additionalDependency = "Additional dependency";
         Set<String> expectedDependencies = Set.of(updatedBeSshId, updatedNlSshId, originalBeTpId,
                 originalNlTpId, originalTpBdId, additionalDependency);
-        assertEquals(expectedDependencies, getOccurrences(updatedCgmSvXml, REGEX_DEPENDENT_ON));
+        assertEquals(expectedDependencies, getUniqueMatches(updatedCgmSvXml, REGEX_DEPENDENT_ON));
 
         // Each updated IGM SSH should supersede the original one and depend on the original EQ
         String originalBeSshId = "urn:uuid:Network_BE_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_1_1D__FM";
         String originalNlSshId = "urn:uuid:Network_NL_N_STEADY_STATE_HYPOTHESIS_2022-03-04T05:30:00Z_1_1D__FM";
-        assertEquals(originalBeSshId, getFirstOccurrence(updatedBeSshXml, REGEX_SUPERSEDES));
-        assertEquals(originalNlSshId, getFirstOccurrence(updatedNlSshXml, REGEX_SUPERSEDES));
-        assertEquals(Set.of("BE EQ model ID"), getOccurrences(updatedBeSshXml, REGEX_DEPENDENT_ON));
-        assertEquals(Set.of("NL EQ model ID"), getOccurrences(updatedNlSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(originalBeSshId, getFirstMatch(updatedBeSshXml, REGEX_SUPERSEDES));
+        assertEquals(originalNlSshId, getFirstMatch(updatedNlSshXml, REGEX_SUPERSEDES));
+        assertEquals(Set.of("BE EQ model ID"), getUniqueMatches(updatedBeSshXml, REGEX_DEPENDENT_ON));
+        assertEquals(Set.of("NL EQ model ID"), getUniqueMatches(updatedNlSshXml, REGEX_DEPENDENT_ON));
 
         // Profiles should be consistent with the instance files, CGM SV has an additional profile
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedBeSshXml, REGEX_PROFILE));
-        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstOccurrence(updatedNlSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedBeSshXml, REGEX_PROFILE));
+        assertEquals("http://entsoe.eu/CIM/SteadyStateHypothesis/1/1", getFirstMatch(updatedNlSshXml, REGEX_PROFILE));
         Set<String> expectedProfiles = Set.of("Additional profile", "http://entsoe.eu/CIM/StateVariables/4/1");
-        assertEquals(expectedProfiles, getOccurrences(updatedCgmSvXml, REGEX_PROFILE));
+        assertEquals(expectedProfiles, getUniqueMatches(updatedCgmSvXml, REGEX_PROFILE));
 
         // Both the model and a property define the main network MAS. The property should prevail.
-        assertEquals("http://elia.be/CGMES/2.4.15", getFirstOccurrence(updatedBeSshXml, REGEX_MAS));
-        assertEquals("http://tennet.nl/CGMES/2.4.15", getFirstOccurrence(updatedNlSshXml, REGEX_MAS));
-        assertEquals("Regional Coordination Center", getFirstOccurrence(updatedCgmSvXml, REGEX_MAS));
+        assertEquals("http://elia.be/CGMES/2.4.15", getFirstMatch(updatedBeSshXml, REGEX_MAS));
+        assertEquals("http://tennet.nl/CGMES/2.4.15", getFirstMatch(updatedNlSshXml, REGEX_MAS));
+        assertEquals("Regional Coordination Center", getFirstMatch(updatedCgmSvXml, REGEX_MAS));
     }
 
     @Test
@@ -569,8 +568,8 @@ void testFaraoUseCaseManualExport() throws IOException {
             assertEquals(expectedOutputVersion, sshVersionInOutput);
 
             String outputSshXml = Files.readString(tmpFolder.resolve(filenameFromCgmesExport));
-            assertEquals(Set.of("myDependency"), getOccurrences(outputSshXml, REGEX_DEPENDENT_ON));
-            assertEquals(Set.of("mySupersede"), getOccurrences(outputSshXml, REGEX_SUPERSEDES));
+            assertEquals(Set.of("myDependency"), getUniqueMatches(outputSshXml, REGEX_DEPENDENT_ON));
+            assertEquals(Set.of("mySupersede"), getUniqueMatches(outputSshXml, REGEX_SUPERSEDES));
         }
         String filenameFromCgmesExport = basename + "_SV.xml";
         // We read it from inside the SV file ...
@@ -578,7 +577,7 @@ void testFaraoUseCaseManualExport() throws IOException {
         assertEquals(expectedOutputVersion, svVersionInOutput);
         // Check the dependencies
         String outputSvXml = Files.readString(tmpFolder.resolve(filenameFromCgmesExport));
-        assertEquals(Set.of("mySvDependency1", "mySvDependency2"), getOccurrences(outputSvXml, REGEX_DEPENDENT_ON));
+        assertEquals(Set.of("mySvDependency1", "mySvDependency2"), getUniqueMatches(outputSvXml, REGEX_DEPENDENT_ON));
     }
 
     private static final Map<Country, String> TSO_BY_COUNTRY = Map.of(
@@ -729,21 +728,4 @@ private void addModelForNetwork(Network network, int version) {
                 .add();
     }
 
-    private String getFirstOccurrence(String xml, Pattern pattern) {
-        Matcher matcher = pattern.matcher(xml);
-        if (matcher.find()) {
-            return matcher.group(1);
-        }
-        throw new PowsyblException("Pattern not found " + pattern);
-    }
-
-    private Set<String> getOccurrences(String xml, Pattern pattern) {
-        Set<String> matches = new HashSet<>();
-        Matcher matcher = pattern.matcher(xml);
-        while (matcher.find()) {
-            matches.add(matcher.group(1));
-        }
-        return matches;
-    }
-
 }