Skip to content

Commit ae2e27c

Browse files
SureshMuthukaruppasamygreg-at-modernetimtebeekgithub-actions[bot]
authored
Added a new recipe to add an XML tag attribute AddTagAttribute (#5733)
* Added a new recipe to add an XML tag attribute AddTagAttribute * Update rewrite-xml/src/test/java/org/openrewrite/xml/AddTagAttributeTest.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update rewrite-xml/src/main/java/org/openrewrite/xml/AddTagAttribute.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update rewrite-xml/src/main/java/org/openrewrite/xml/AddTagAttribute.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Polish --------- Co-authored-by: Greg Oledzki <greg.oledzki@moderne.io> Co-authored-by: Tim te Beek <timtebeek@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim te Beek <tim@moderne.io>
1 parent c1443c0 commit ae2e27c

File tree

3 files changed

+299
-0
lines changed

3 files changed

+299
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.xml;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.openrewrite.ExecutionContext;
21+
import org.openrewrite.Option;
22+
import org.openrewrite.Recipe;
23+
import org.openrewrite.TreeVisitor;
24+
import org.openrewrite.internal.ListUtils;
25+
import org.openrewrite.marker.Markers;
26+
import org.openrewrite.xml.tree.Xml;
27+
28+
import static org.openrewrite.Tree.randomId;
29+
30+
@Value
31+
@EqualsAndHashCode(callSuper = false)
32+
public class AddTagAttribute extends Recipe {
33+
34+
@Override
35+
public String getDisplayName() {
36+
return "Add new XML attribute for an Element";
37+
}
38+
39+
@Override
40+
public String getDescription() {
41+
return "Add new XML attribute with value on a specified element.";
42+
}
43+
44+
@Option(displayName = "Element name",
45+
description = "The name of the element whose attribute's value is to be added. Interpreted as an XPath expression.",
46+
example = "//beans/bean")
47+
String elementName;
48+
49+
@Option(displayName = "Attribute name",
50+
description = "The name of the new attribute.",
51+
example = "attribute-name")
52+
String attributeName;
53+
54+
@Option(displayName = "New value",
55+
description = "The new value to be used for key specified by `attributeName`.",
56+
example = "value-to-add")
57+
String newValue;
58+
59+
@Override
60+
public TreeVisitor<?, ExecutionContext> getVisitor() {
61+
return new XmlVisitor<ExecutionContext>() {
62+
@Override
63+
public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
64+
Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx);
65+
if (!new XPathMatcher(elementName).matches(getCursor())) {
66+
return t;
67+
}
68+
69+
for (Xml.Attribute attr : t.getAttributes()) {
70+
if (attributeName.equals(attr.getKeyAsString())) {
71+
return t;
72+
}
73+
}
74+
75+
Xml.Ident name = new Xml.Ident(randomId(), "", Markers.EMPTY, attributeName);
76+
Xml.Attribute.Value value = new Xml.Attribute.Value(randomId(), "", Markers.EMPTY, Xml.Attribute.Value.Quote.Double, newValue);
77+
return t.withAttributes(ListUtils.concat(t.getAttributes(),
78+
new Xml.Attribute(randomId(), " ", Markers.EMPTY, name, "", value)));
79+
}
80+
};
81+
}
82+
}

rewrite-xml/src/main/resources/META-INF/rewrite/examples.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,27 @@ examples:
116116
language: xml
117117
---
118118
type: specs.openrewrite.org/v1beta/example
119+
recipeName: org.openrewrite.xml.AddTagAttribute
120+
examples:
121+
- description: Add an attribute to a tag if it doesn't already exist
122+
parameters:
123+
- bean
124+
- scope
125+
- singleton
126+
sources:
127+
- before: |
128+
<beans>
129+
<bean id="myBean"/>
130+
<bean id="anotherBean" scope="prototype"/>
131+
</beans>
132+
after: |
133+
<beans>
134+
<bean id="myBean" scope="singleton"/>
135+
<bean id="anotherBean" scope="prototype"/>
136+
</beans>
137+
language: xml
138+
---
139+
type: specs.openrewrite.org/v1beta/example
119140
recipeName: org.openrewrite.xml.ChangeTagName
120141
examples:
121142
- description: ''
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.xml;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.openrewrite.DocumentExample;
20+
import org.openrewrite.test.RewriteTest;
21+
22+
import static org.openrewrite.xml.Assertions.xml;
23+
24+
class AddTagAttributeTest implements RewriteTest {
25+
26+
@DocumentExample
27+
@Test
28+
void addsAttributeWhenMissing() {
29+
rewriteRun(
30+
spec -> spec.recipe(new AddTagAttribute("bean", "scope", "singleton")),
31+
xml(
32+
"""
33+
<beans>
34+
<bean id="myBean"/>
35+
<bean id="anotherBean" scope="prototype"/>
36+
</beans>
37+
""",
38+
"""
39+
<beans>
40+
<bean id="myBean" scope="singleton"/>
41+
<bean id="anotherBean" scope="prototype"/>
42+
</beans>
43+
"""
44+
)
45+
);
46+
}
47+
48+
@Test
49+
void doesNothingIfAttributeExists() {
50+
rewriteRun(
51+
spec -> spec.recipe(new AddTagAttribute("bean", "id", "newId")),
52+
xml(
53+
"""
54+
<beans>
55+
<bean id="myBean"/>
56+
</beans>
57+
"""
58+
)
59+
);
60+
}
61+
62+
@Test
63+
void addsAttributeToMultipleMatchingTags() {
64+
rewriteRun(
65+
spec -> spec.recipe(new AddTagAttribute("bean", "type", "service")),
66+
xml(
67+
"""
68+
<beans>
69+
<bean id="one"/>
70+
<bean id="two" type="repository"/>
71+
<bean id="three"/>
72+
</beans>
73+
""",
74+
"""
75+
<beans>
76+
<bean id="one" type="service"/>
77+
<bean id="two" type="repository"/>
78+
<bean id="three" type="service"/>
79+
</beans>
80+
"""
81+
)
82+
);
83+
}
84+
85+
@Test
86+
void addAttributeToTagWithNoAttributes() {
87+
rewriteRun(
88+
spec -> spec.recipe(new AddTagAttribute("bean", "scope", "singleton")),
89+
xml(
90+
"""
91+
<beans>
92+
<bean/>
93+
</beans>
94+
""",
95+
"""
96+
<beans>
97+
<bean scope="singleton"/>
98+
</beans>
99+
"""
100+
)
101+
);
102+
}
103+
104+
@Test
105+
void addAttributeToNonSelfClosingEmptyTagWithoutAttributes() {
106+
rewriteRun(
107+
spec -> spec.recipe(new AddTagAttribute("bean", "scope", "singleton")),
108+
xml(
109+
"""
110+
<beans>
111+
<bean></bean>
112+
</beans>
113+
""",
114+
"""
115+
<beans>
116+
<bean scope="singleton"></bean>
117+
</beans>
118+
"""
119+
)
120+
);
121+
}
122+
123+
@Test
124+
void addAttributeToTagWithNewlineBetweenOpenAndClose() {
125+
rewriteRun(
126+
spec -> spec.recipe(new AddTagAttribute("bean", "scope", "singleton")),
127+
xml(
128+
"""
129+
<beans>
130+
<bean>
131+
</bean>
132+
</beans>
133+
""",
134+
"""
135+
<beans>
136+
<bean scope="singleton">
137+
</bean>
138+
</beans>
139+
"""
140+
)
141+
);
142+
}
143+
144+
@Test
145+
void addAttributeWhenSimilarAttributeExists() {
146+
rewriteRun(
147+
spec -> spec.recipe(new AddTagAttribute("bean", "scope", "singleton")),
148+
xml(
149+
"""
150+
<beans>
151+
<bean scope1="singleton">
152+
</bean>
153+
</beans>
154+
""",
155+
"""
156+
<beans>
157+
<bean scope1="singleton" scope="singleton">
158+
</bean>
159+
</beans>
160+
"""
161+
)
162+
);
163+
}
164+
165+
@Test
166+
void addAttributeUsingXPathExpression() {
167+
rewriteRun(
168+
spec -> spec.recipe(new AddTagAttribute("//bean/property", "scope", "singleton")),
169+
xml(
170+
// before
171+
"""
172+
<beans>
173+
<bean>
174+
<property name="myProperty" />
175+
</bean>
176+
<notbean>
177+
<property name="shouldNotChange"/>
178+
</notbean>
179+
</beans>
180+
""",
181+
// after
182+
"""
183+
<beans>
184+
<bean>
185+
<property name="myProperty" scope="singleton" />
186+
</bean>
187+
<notbean>
188+
<property name="shouldNotChange"/>
189+
</notbean>
190+
</beans>
191+
"""
192+
)
193+
);
194+
}
195+
196+
}

0 commit comments

Comments
 (0)