diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/AddToTagVisitor.java b/rewrite-xml/src/main/java/org/openrewrite/xml/AddToTagVisitor.java
index fd3c7e333a..8fc51b09b8 100755
--- a/rewrite-xml/src/main/java/org/openrewrite/xml/AddToTagVisitor.java
+++ b/rewrite-xml/src/main/java/org/openrewrite/xml/AddToTagVisitor.java
@@ -33,19 +33,37 @@ public class AddToTagVisitor
extends XmlVisitor
{
@Nullable
private final Comparator tagComparator;
+ private final boolean allowDuplicates;
+
public AddToTagVisitor(Xml.Tag scope, Xml.Tag tagToAdd) {
- this(scope, tagToAdd, null);
+ this(scope, tagToAdd, null, false);
+ }
+
+ public AddToTagVisitor(Xml.Tag scope, Xml.Tag tagToAdd, boolean allowDuplicates) {
+ this(scope, tagToAdd, null, allowDuplicates);
}
public AddToTagVisitor(Xml.Tag scope, Xml.Tag tagToAdd, @Nullable Comparator tagComparator) {
+ this(scope, tagToAdd, tagComparator, false);
+ }
+
+ public AddToTagVisitor(Xml.Tag scope, Xml.Tag tagToAdd, @Nullable Comparator tagComparator, boolean allowDuplicates) {
this.scope = scope;
this.tagToAdd = tagToAdd;
this.tagComparator = tagComparator;
+ this.allowDuplicates = allowDuplicates;
}
@Override
public Xml visitTag(Xml.Tag t, P p) {
if (scope.isScope(t)) {
+ if (!allowDuplicates && t.getContent() != null) {
+ for (Content existing : t.getContent()) {
+ if (existing instanceof Xml.Tag && SemanticallyEqual.areEqual(existing, tagToAdd)) {
+ return super.visitTag(t, p);
+ }
+ }
+ }
assert getCursor().getParent() != null;
if (t.getClosing() == null) {
t = t.withClosing(autoFormat(new Xml.Tag.Closing(Tree.randomId(), "\n",
diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/AddToTagTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/AddToTagTest.java
index bd0fba786e..b3fa960b0c 100755
--- a/rewrite-xml/src/test/java/org/openrewrite/xml/AddToTagTest.java
+++ b/rewrite-xml/src/test/java/org/openrewrite/xml/AddToTagTest.java
@@ -143,6 +143,102 @@ public Xml visitDocument(Xml.Document x, ExecutionContext ctx) {
);
}
+ @Test
+ void doesNotAddSemanticallyEqualDuplicate() {
+ rewriteRun(
+ spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() {
+ @Override
+ public Xml visitDocument(Xml.Document x, ExecutionContext ctx) {
+ doAfterVisit(new AddToTagVisitor<>(x.getRoot(), Xml.Tag.build("")));
+ return super.visitDocument(x, ctx);
+ }
+ })),
+ xml(
+ """
+
+
+
+ """
+ )
+ );
+ }
+
+ @Test
+ void doesNotAddDuplicateIgnoringAttributeOrder() {
+ rewriteRun(
+ spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() {
+ @Override
+ public Xml visitDocument(Xml.Document x, ExecutionContext ctx) {
+ doAfterVisit(new AddToTagVisitor<>(x.getRoot(),
+ Xml.Tag.build("")));
+ return super.visitDocument(x, ctx);
+ }
+ })),
+ xml(
+ """
+
+
+
+ """
+ )
+ );
+ }
+
+ @Test
+ void addsSemanticallyEqualDuplicateWhenAllowDuplicatesTrue() {
+ rewriteRun(
+ spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() {
+ @Override
+ public Xml visitDocument(Xml.Document x, ExecutionContext ctx) {
+ if (x.getRoot().getChildren().size() == 1) {
+ doAfterVisit(new AddToTagVisitor<>(x.getRoot(),
+ Xml.Tag.build(""), true));
+ }
+ return super.visitDocument(x, ctx);
+ }
+ })),
+ xml(
+ """
+
+
+
+ """,
+ """
+
+
+
+
+ """
+ )
+ );
+ }
+
+ @Test
+ void addsWhenChildrenShareNameButDifferentAttributes() {
+ rewriteRun(
+ spec -> spec.recipe(toRecipe(() -> new XmlVisitor<>() {
+ @Override
+ public Xml visitDocument(Xml.Document x, ExecutionContext ctx) {
+ doAfterVisit(new AddToTagVisitor<>(x.getRoot(), Xml.Tag.build("")));
+ return super.visitDocument(x, ctx);
+ }
+ })),
+ xml(
+ """
+
+
+
+ """,
+ """
+
+
+
+
+ """
+ )
+ );
+ }
+
@Issue("https://github.com/openrewrite/rewrite/issues/1392")
@Test
void preserveNonTagContent() {