Skip to content

Commit a5d7bd3

Browse files
authored
#640: fix xml merger if merge:id is omitted for repated element (#643)
1 parent c35e971 commit a5d7bd3

File tree

10 files changed

+61
-63
lines changed

10 files changed

+61
-63
lines changed

cli/pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@
9696
<version>1.14.15</version>
9797
<scope>test</scope>
9898
</dependency>
99+
<!-- required for XML assertion (XmlMergerTest) -->
100+
<dependency>
101+
<groupId>org.xmlunit</groupId>
102+
<artifactId>xmlunit-core</artifactId>
103+
<version>2.9.0</version>
104+
</dependency>
105+
<dependency>
106+
<groupId>org.xmlunit</groupId>
107+
<artifactId>xmlunit-assertj3</artifactId>
108+
<version>2.9.1</version>
109+
</dependency>
99110
</dependencies>
100111

101112
<build>

cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/MergeStrategy.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,6 @@ protected void updateAndRemoveNsAttributes(MergeElement mergeElement, ElementMat
9090

9191
for (MergeAttribute attribute : mergeElement.getElementAttributes()) {
9292
if (attribute.isMergeNsAttr()) {
93-
if (attribute.isMergeNsIdAttr()) {
94-
elementMatcher.updateId(mergeElement.getQName(), attribute.getValue());
95-
}
9693
mergeElement.getElement().removeAttributeNode(attribute.getAttr());
9794
}
9895
}
@@ -154,7 +151,10 @@ protected void combineChildNodes(MergeElement updateElement, MergeElement target
154151
MergeElement sourceChildElement = new MergeElement((Element) updateChild, updateElement.getDocumentPath());
155152
MergeElement matchedTargetChild = elementMatcher.matchElement(sourceChildElement, targetElement);
156153
if (matchedTargetChild != null) {
157-
MergeStrategy childStrategy = MergeStrategy.of(sourceChildElement.getMergingStrategy());
154+
MergeStrategy childStrategy = sourceChildElement.getMergingStrategy();
155+
if (childStrategy == null) {
156+
childStrategy = this;
157+
}
158158
childStrategy.merge(sourceChildElement, matchedTargetChild, elementMatcher);
159159
} else {
160160
appendElement(sourceChildElement, targetElement, elementMatcher);

cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/XmlMerger.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,10 @@ public void merge(Document sourceDocument, Document targetDocument, Path source,
9999
MergeElement targetRoot = new MergeElement(targetDocument.getDocumentElement(), target);
100100

101101
if (areRootsCompatible(sourceRoot, targetRoot)) {
102-
MergeStrategy strategy = MergeStrategy.of(sourceRoot.getMergingStrategy());
102+
MergeStrategy strategy = sourceRoot.getMergingStrategy();
103+
if (strategy == null) {
104+
strategy = MergeStrategy.KEEP; // default strategy used as fallback
105+
}
103106
strategy.merge(sourceRoot, targetRoot, new ElementMatcher(this.context));
104107
} else {
105108
this.context.warning("Root elements do not match. Skipping merge operation.");

cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/matcher/ElementMatcher.java

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,13 @@ public ElementMatcher(IdeContext context) {
2424
this.qNameIdMap = new HashMap<>();
2525
}
2626

27-
/**
28-
* Updates the ID strategy for a given QName (qualified name) of an XML element.
29-
*
30-
* @param qname the qualified name (ns + local name) of the XML element
31-
* @param id the ID value (value of merge:id) to be used for matching the element
32-
*/
33-
public void updateId(QName qname, String id) {
27+
private IdComputer createIdComputer(String id, QName qname, MergeElement sourceElement) {
3428

35-
IdComputer idComputer = new IdComputer(id);
36-
IdComputer duplicate = this.qNameIdMap.put(qname, idComputer);
37-
if (duplicate != null) {
38-
this.context.debug("ID replaced for element '{}': old ID '{}' -> new ID '{}'", qname, duplicate.getId(), idComputer.getId());
29+
if ((id == null) || id.isEmpty()) {
30+
throw new IllegalStateException(
31+
"No merge:id value defined for element " + sourceElement.getXPath() + " in document " + sourceElement.getDocumentPath());
3932
}
33+
return new IdComputer(id);
4034
}
4135

4236
/**
@@ -51,11 +45,7 @@ public MergeElement matchElement(MergeElement sourceElement, MergeElement target
5145
String id = sourceElement.getId();
5246
QName qName = sourceElement.getQName();
5347

54-
IdComputer idComputer = this.qNameIdMap.get(qName);
55-
if (idComputer == null) {
56-
updateId(qName, id);
57-
idComputer = this.qNameIdMap.get(qName);
58-
}
48+
IdComputer idComputer = this.qNameIdMap.computeIfAbsent(qName, k -> createIdComputer(id, qName, sourceElement));
5949
Element matchedNode = idComputer.evaluateExpression(sourceElement, targetElement);
6050
if (matchedNode != null) {
6151
return new MergeElement(matchedNode, targetElement.getDocumentPath());

cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/matcher/IdComputer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class IdComputer {
2929

3030
public IdComputer(String id) {
3131

32+
super();
3233
this.id = id;
3334
}
3435

cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/model/MergeElement.java

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,15 @@ public Path getDocumentPath() {
5353
}
5454

5555
/**
56-
* Retrieves the merge strategy associated with this MergeElement.
57-
*
58-
* @return the merge strategy
56+
* @return the {@link MergeStrategy} of this element or {@code null} if undefined.
5957
*/
60-
public String getMergingStrategy() {
58+
public MergeStrategy getMergingStrategy() {
6159

6260
String strategy = this.element.getAttributeNS(XmlMerger.MERGE_NS_URI, "strategy").toLowerCase();
6361
if (!strategy.isEmpty()) {
64-
return strategy;
65-
}
66-
67-
// Inherit merging strategy from parent
68-
Element parent = getParentElement();
69-
if (parent != null) {
70-
return new MergeElement(parent, this.documentPath).getMergingStrategy();
62+
return MergeStrategy.of(strategy);
7163
}
72-
return MergeStrategy.KEEP.name(); // should the default be keep?
64+
return null;
7365
}
7466

7567
/**
@@ -90,10 +82,7 @@ public String getId() {
9082
String idAttr = this.element.getAttribute("id");
9183
if (idAttr.isEmpty()) {
9284
idAttr = this.element.getAttribute("name");
93-
if (idAttr.isEmpty()) {
94-
throw new IllegalStateException(
95-
"No merge:id value defined for element " + getXPath() + " in document " + getDocumentPath());
96-
} else {
85+
if (!idAttr.isEmpty()) {
9786
id = "@name";
9887
}
9988
} else {

cli/src/test/java/com/devonfw/tools/ide/merge/XmlMergerTest.java

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
import java.nio.file.Path;
88
import java.util.stream.Stream;
99

10-
import org.assertj.core.api.SoftAssertions;
11-
import org.junit.jupiter.api.Test;
1210
import org.junit.jupiter.api.io.TempDir;
11+
import org.junit.jupiter.params.ParameterizedTest;
12+
import org.junit.jupiter.params.provider.MethodSource;
1313
import org.slf4j.Logger;
1414
import org.slf4j.LoggerFactory;
15+
import org.xmlunit.assertj3.XmlAssert;
1516

1617
import com.devonfw.tools.ide.context.AbstractIdeContextTest;
1718
import com.devonfw.tools.ide.context.IdeContext;
@@ -21,7 +22,7 @@ class XmlMergerTest extends AbstractIdeContextTest {
2122

2223
private static Logger LOG = LoggerFactory.getLogger(XmlMergerTest.class);
2324

24-
private static final Path TEST_RESOURCES = Path.of("src", "test", "resources", "xmlmerger");
25+
private static final Path XML_TEST_RESOURCES = Path.of("src", "test", "resources", "xmlmerger");
2526

2627
private static final String SOURCE_XML = "source.xml";
2728

@@ -37,28 +38,24 @@ class XmlMergerTest extends AbstractIdeContextTest {
3738
* Tests the XML merger functionality across multiple test cases. This test method iterates through all subdirectories in the test resources folder, each
3839
* representing a different test case.
3940
*/
40-
@Test
41-
void testMerger(@TempDir Path tempDir) throws Exception {
42-
43-
try (Stream<Path> folders = Files.list(TEST_RESOURCES)) {
44-
// arrange
45-
SoftAssertions softly = new SoftAssertions();
46-
folders.forEach(folder -> {
47-
LOG.info("Testing XML merger for test-case {}", folder.getFileName());
48-
Path sourcePath = folder.resolve(SOURCE_XML);
49-
Path targetPath = tempDir.resolve(TARGET_XML);
50-
Path resultPath = folder.resolve(RESULT_XML);
51-
try {
52-
Files.copy(folder.resolve(TARGET_XML), targetPath, REPLACE_EXISTING);
53-
// act
54-
this.merger.merge(null, sourcePath, this.context.getVariables(), targetPath);
55-
// assert
56-
softly.assertThat(targetPath).hasContent(Files.readString(resultPath));
57-
} catch (IOException e) {
58-
throw new IllegalStateException(e);
59-
}
60-
});
61-
softly.assertAll();
62-
}
41+
@ParameterizedTest
42+
@MethodSource("xmlMergerTestCases")
43+
void testMerger(Path folder, @TempDir Path tempDir) throws Exception {
44+
45+
// arrange
46+
LOG.info("Testing XML merger for test-case {}", folder.getFileName());
47+
Path sourcePath = folder.resolve(SOURCE_XML);
48+
Path targetPath = tempDir.resolve(TARGET_XML);
49+
Path resultPath = folder.resolve(RESULT_XML);
50+
Files.copy(folder.resolve(TARGET_XML), targetPath, REPLACE_EXISTING);
51+
// act
52+
this.merger.merge(null, sourcePath, this.context.getVariables(), targetPath);
53+
// assert
54+
XmlAssert.assertThat(targetPath).and(resultPath.toFile()).areIdentical();
55+
}
56+
57+
private static Stream<Path> xmlMergerTestCases() throws IOException {
58+
59+
return Files.list(XML_TEST_RESOURCES).filter(Files::isDirectory);
6360
}
6461
}

cli/src/test/resources/xmlmerger/id-fallback/result.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88
<parent attr1="oldSomething2" id="parent2">
99
<child>Old Child Text2</child>
1010
</parent>
11+
<special class="id2">new special2</special>
12+
<special class="id3">old special3</special>
13+
<special class="id1">new special1</special>
1114
</root>

cli/src/test/resources/xmlmerger/id-fallback/source.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@
44
<parent id="parent" attr1="newSomething">
55
<child>New Child Text</child>
66
</parent>
7+
<special class="id1" merge:id="@class">new special1</special>
8+
<special class="id2">new special2</special>
79
</root>

cli/src/test/resources/xmlmerger/id-fallback/target.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
88
<parent id="parent2" attr1="oldSomething2">
99
<child>Old Child Text2</child>
1010
</parent>
11+
<special class="id2">old special2</special>
12+
<special class="id3">old special3</special>
1113
</root>

0 commit comments

Comments
 (0)