Repository: tefra/xsdata

Issue:
default substitution "Class"->"Type" does infix substitution
When I create a python class model from the [Ecore.xsd](https://git.eclipse.org/c/emf/org.eclipse.emf.git/plain/plugins/org.eclipse.emf.ecore/model/Ecore.xsd), the [xsdata.models.config.py:GeneratorConfig.create()](https://github.com/tefra/xsdata/blob/040692db47e6e51028fd959c793e757858c392d7/xsdata/models/config.py#L443) default substitution will mangle names EClass and EClassifier to EType and ETypeifier.

anchoring the search wit `^`and `$`like so `^Class$` fixed the problem.

`<Substitution type="class" search="^Class$" replace="Type"/>`


Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=755)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=755&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=755&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=755&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=755&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=755&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=755&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=755&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=755&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=755&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=755&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=755&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=755&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=755) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=755&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=755&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/xsdata/formats/dataclass/filters.py b/xsdata/formats/dataclass/filters.py
index cb19f6ce..c5c6fed7 100644
--- a/xsdata/formats/dataclass/filters.py
+++ b/xsdata/formats/dataclass/filters.py
@@ -147,7 +147,7 @@ class Filters:
 
     def apply_substitutions(self, name: str, obj_type: ObjectType) -> str:
         for search, replace in self.substitutions[obj_type].items():
-            name = re.sub(search, replace, name)
+            name = re.sub(rf"{search}", rf"{replace}", name)
 
         return name
 
diff --git a/xsdata/models/config.py b/xsdata/models/config.py
index bf085abe..9777f113 100644
--- a/xsdata/models/config.py
+++ b/xsdata/models/config.py
@@ -443,7 +443,9 @@ class GeneratorConfig:
             )
 
         obj.substitutions.substitution.append(
-            GeneratorSubstitution(type=ObjectType.CLASS, search="Class", replace="Type")
+            GeneratorSubstitution(
+                type=ObjectType.CLASS, search="(.*)Class$", replace="\\1Type"
+            )
         )
 
         return obj


Test patch:
diff --git a/tests/formats/dataclass/test_filters.py b/tests/formats/dataclass/test_filters.py
index 7fd222b5..aa095d30 100644
--- a/tests/formats/dataclass/test_filters.py
+++ b/tests/formats/dataclass/test_filters.py
@@ -111,6 +111,12 @@ class FiltersTests(FactoryTestCase):
         attr = AttrFactory.create(types=[AttrTypeFactory.create(alias="alias")])
         self.assertEqual("Alias", self.filters.constant_value(attr))
 
+    def test_apply_substitutions_with_regexes(self):
+        self.filters.substitutions[ObjectType.CLASS]["(.*)Class"] = "\\1Type"
+
+        actual = self.filters.apply_substitutions("FooClass", ObjectType.CLASS)
+        self.assertEqual("FooType", actual)
+
     @mock.patch.object(Filters, "field_default_value")
     def test_field_definition(self, mock_field_default_value):
         mock_field_default_value.side_effect = [1, False]
diff --git a/tests/models/test_config.py b/tests/models/test_config.py
index e046e444..cbf737a8 100644
--- a/tests/models/test_config.py
+++ b/tests/models/test_config.py
@@ -58,7 +58,7 @@ class GeneratorConfigTests(TestCase):
             '    <Substitution type="package" search="http://schemas.xmlsoap.org/wsdl/soap/" replace="soap"/>\n'
             '    <Substitution type="package" search="http://schemas.xmlsoap.org/wsdl/soap12/" replace="soap12"/>\n'
             '    <Substitution type="package" search="http://schemas.xmlsoap.org/soap/envelope/" replace="soapenv"/>\n'
-            '    <Substitution type="class" search="Class" replace="Type"/>\n'
+            '    <Substitution type="class" search="(.*)Class$" replace="\\1Type"/>\n'
             "  </Substitutions>\n"
             "</Config>\n"
         )