Skip to content

Commit

Permalink
Adapt XML Namespaced to be a trait
Browse files Browse the repository at this point in the history
  • Loading branch information
sambsnyd committed Jul 12, 2024
1 parent c24e992 commit cda2cfb
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 382 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@
import org.openrewrite.internal.StringUtils;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.marker.Markers;
import org.openrewrite.xml.trait.Namespaced;
import org.openrewrite.xml.tree.Xml;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static org.openrewrite.Tree.randomId;

Expand All @@ -38,7 +42,6 @@ public class ChangeNamespaceValue extends Recipe {
private static final String VERSION_PREFIX = "version";
private static final String SCHEMA_LOCATION_MATCH_PATTERN = "(?m)(.*)(%s)(\\s+)(.*)";
private static final String SCHEMA_LOCATION_REPLACEMENT_PATTERN = "$1%s$3%s";
private static final String MSG_TAG_UPDATED = "msg-tag-updated";

@Override
public String getDisplayName() {
Expand Down Expand Up @@ -99,39 +102,15 @@ public String getDescription() {
public static final String XML_SCHEMA_INSTANCE_PREFIX = "xsi";
public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3.org/2001/XMLSchema-instance";

/**
* Find the tag that contains the declaration of the {@link #XML_SCHEMA_INSTANCE_URI} namespace.
*
* @param cursor the cursor to search from
* @return the tag that contains the declaration of the given namespace URI.
*/
public static Xml.Tag findTagContainingXmlSchemaInstanceNamespace(Cursor cursor) {
while (cursor != null) {
if (cursor.getValue() instanceof Xml.Document) {
return ((Xml.Document) cursor.getValue()).getRoot();
}
Xml.Tag tag = cursor.firstEnclosing(Xml.Tag.class);
if (tag != null) {
if (tag.getNamespaces().containsValue(XML_SCHEMA_INSTANCE_URI)) {
return tag;
}
}
cursor = cursor.getParent();
}

// Should never happen
throw new IllegalArgumentException("Could not find tag containing namespace '" + XML_SCHEMA_INSTANCE_URI + "' or the enclosing Xml.Document instance.");
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
XPathMatcher elementNameMatcher = elementName != null ? new XPathMatcher(elementName) : null;
return new XmlIsoVisitor<ExecutionContext>() {
@Override
public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
Xml.Document d = super.visitDocument(document, ctx);
if (ctx.pollMessage(MSG_TAG_UPDATED, false)) {
d = d.withRoot(addOrUpdateSchemaLocation(d.getRoot(), getCursor()));
if (d != document) {
d = d.withRoot(addOrUpdateSchemaLocation(d.getRoot()));
}
return d;
}
Expand All @@ -143,7 +122,6 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
if (matchesElementName(getCursor()) && matchesVersion(t)) {
t = t.withAttributes(ListUtils.map(t.getAttributes(), this::maybeReplaceNamespaceAttribute));
t = t.withAttributes(ListUtils.map(t.getAttributes(), this::maybeReplaceVersionAttribute));
ctx.putMessage(MSG_TAG_UPDATED, true);
}

return t;
Expand Down Expand Up @@ -228,12 +206,13 @@ private boolean isVersionMatch(Xml.Attribute attribute) {
return false;
}

private Xml.Tag addOrUpdateSchemaLocation(Xml.Tag root, Cursor cursor) {
private Xml.Tag addOrUpdateSchemaLocation(Xml.Tag root) {
if (StringUtils.isBlank(newSchemaLocation)) {
return root;
}
Xml.Tag newRoot = maybeAddNamespace(root);
Optional<Xml.Attribute> maybeSchemaLocation = maybeGetSchemaLocation(cursor, newRoot);
Namespaced n = new Namespaced(new Cursor(null, newRoot));
Optional<Xml.Attribute> maybeSchemaLocation = maybeGetSchemaLocation(n);
if (maybeSchemaLocation.isPresent() && oldValue != null) {
newRoot = updateSchemaLocation(newRoot, maybeSchemaLocation.get());
} else if (!maybeSchemaLocation.isPresent()) {
Expand All @@ -242,11 +221,10 @@ private Xml.Tag addOrUpdateSchemaLocation(Xml.Tag root, Cursor cursor) {
return newRoot;
}

private Optional<Xml.Attribute> maybeGetSchemaLocation(Cursor cursor, Xml.Tag tag) {
Xml.Tag schemaLocationTag = findTagContainingXmlSchemaInstanceNamespace(cursor);
Map<String, String> namespaces = tag.getNamespaces();
for (Xml.Attribute attribute : schemaLocationTag.getAttributes()) {
String attributeNamespace = namespaces.get(Xml.extractNamespacePrefix(attribute.getKeyAsString()));
private Optional<Xml.Attribute> maybeGetSchemaLocation(Namespaced n) {
Map<String, String> namespaces = n.getNamespaces();
for (Xml.Attribute attribute : n.getAttributes()) {
String attributeNamespace = namespaces.get(Namespaced.extractNamespacePrefix(attribute.getKeyAsString()));
if(XML_SCHEMA_INSTANCE_URI.equals(attributeNamespace)
&& attribute.getKeyAsString().endsWith("schemaLocation")) {
return Optional.of(attribute);
Expand All @@ -255,16 +233,83 @@ private Optional<Xml.Attribute> maybeGetSchemaLocation(Cursor cursor, Xml.Tag ta

return Optional.empty();
}

private Xml.Tag maybeAddNamespace(Xml.Tag root) {
Map<String, String> namespaces = root.getNamespaces();
Namespaced n = new Namespaced(new Cursor(null, root));
Map<String, String> namespaces = n.getNamespaces();
if (namespaces.containsValue(newValue) && !namespaces.containsValue(XML_SCHEMA_INSTANCE_URI)) {
namespaces.put(XML_SCHEMA_INSTANCE_PREFIX, XML_SCHEMA_INSTANCE_URI);
root = root.withNamespaces(namespaces);
Map<String, String> newNamespaces = new LinkedHashMap<>(namespaces);
newNamespaces.put(XML_SCHEMA_INSTANCE_PREFIX, XML_SCHEMA_INSTANCE_URI);
root = withNamespaces(root, newNamespaces);
}
return root;
}

public Xml.Tag withNamespaces(Xml.Tag tag, Map<String, String> namespaces) {
List<Xml.Attribute> attributes = tag.getAttributes();
if (attributes.isEmpty()) {
for (Map.Entry<String, String> ns : namespaces.entrySet()) {
String key = Namespaced.getAttributeNameForPrefix(ns.getKey());
attributes = ListUtils.concat(attributes, new Xml.Attribute(
randomId(),
"",
Markers.EMPTY,
new Xml.Ident(
randomId(),
"",
Markers.EMPTY,
key
),
"",
new Xml.Attribute.Value(
randomId(),
"",
Markers.EMPTY,
Xml.Attribute.Value.Quote.Double, ns.getValue()
)
));
}
} else {
Map<String, Xml.Attribute> attributeByKey = attributes.stream()
.collect(Collectors.toMap(
Xml.Attribute::getKeyAsString,
a -> a
));

for (Map.Entry<String, String> ns : namespaces.entrySet()) {
String key = Namespaced.getAttributeNameForPrefix(ns.getKey());
if (attributeByKey.containsKey(key)) {
Xml.Attribute attribute = attributeByKey.get(key);
if (!ns.getValue().equals(attribute.getValueAsString())) {
ListUtils.map(attributes, a -> a.getKeyAsString().equals(key)
? attribute.withValue(new Xml.Attribute.Value(randomId(), "", Markers.EMPTY, Xml.Attribute.Value.Quote.Double, ns.getValue()))
: a
);
}
} else {
attributes = ListUtils.concat(attributes, new Xml.Attribute(
randomId(),
" ",
Markers.EMPTY,
new Xml.Ident(
randomId(),
"",
Markers.EMPTY,
key
),
"",
new Xml.Attribute.Value(
randomId(),
"",
Markers.EMPTY,
Xml.Attribute.Value.Quote.Double, ns.getValue()
)
));
}
}
}
return tag.withAttributes(attributes);
}

private Xml.Tag updateSchemaLocation(Xml.Tag newRoot, Xml.Attribute attribute) {
if(oldValue == null) {
return newRoot;
Expand Down
15 changes: 8 additions & 7 deletions rewrite-xml/src/main/java/org/openrewrite/xml/XPathMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.openrewrite.internal.StringUtils;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.xml.search.FindTags;
import org.openrewrite.xml.tree.Namespaced;
import org.openrewrite.xml.trait.Namespaced;
import org.openrewrite.xml.tree.Xml;

import java.util.*;
Expand Down Expand Up @@ -281,13 +281,13 @@ private String matchesElementWithConditionFunction(Matcher matcher, Xml.Tag tag,
} else if (isFunctionCondition) { // [local-name()='name'] pattern
if (isAttributeElement) {
for (Xml.Attribute a : tag.getAttributes()) {
if (matchesElementAndFunction(a, cursor, element, selector, value)) {
if (matchesElementAndFunction(new Cursor(cursor, a), element, selector, value)) {
matchCurrentCondition = true;
break;
}
}
} else {
matchCurrentCondition = matchesElementAndFunction(tag, cursor, element, selector, value);
matchCurrentCondition = matchesElementAndFunction(cursor, element, selector, value);
}
} else { // other [] conditions
for (Xml.Tag t : FindTags.find(tag, selector)) {
Expand All @@ -309,13 +309,14 @@ private String matchesElementWithConditionFunction(Matcher matcher, Xml.Tag tag,
return stillMatchesConditions ? element : null;
}

private static boolean matchesElementAndFunction(Namespaced tagOrAttribute, Cursor cursor, String element, String selector, String value) {
if (!element.equals("*") && !tagOrAttribute.getName().equals(element)) {
private static boolean matchesElementAndFunction(Cursor cursor, String element, String selector, String value) {
Namespaced namespaced = new Namespaced(cursor);
if (!element.equals("*") && !Objects.equals(namespaced.getName().orElse(null), element)) {
return false;
} else if (selector.equals("local-name()")) {
return tagOrAttribute.getLocalName().equals(value);
return Objects.equals(namespaced.getLocalName().orElse(null), value);
} else if (selector.equals("namespace-uri()")) {
Optional<String> nsUri = tagOrAttribute.getNamespaceUri(cursor);
Optional<String> nsUri = namespaced.getNamespaceUri();
return nsUri.isPresent() && nsUri.get().equals(value);
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@
import org.openrewrite.Option;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.xml.XPathMatcher;
import org.openrewrite.xml.XmlVisitor;
import org.openrewrite.xml.trait.Namespaced;
import org.openrewrite.xml.tree.Xml;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

@Value
Expand Down Expand Up @@ -59,32 +57,26 @@ public String getDescription() {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
XPathMatcher matcher = StringUtils.isBlank(xPath) ? null : new XPathMatcher(xPath);
return new XmlVisitor<ExecutionContext>() {

@Override
public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx);
if (tag.getNamespaces().containsKey(namespacePrefix) && (matcher == null || matcher.matches(getCursor()))) {
t = SearchResult.found(t);
}
return t;
}
};
return Namespaced.matcher()
.xPath(xPath)
.prefix(namespacePrefix)
.asVisitor(n -> n.getTree() instanceof Xml.Tag ?
SearchResult.found(n.getTree()) :
n.getTree());
}

public static Set<Xml.Tag> find(Xml x, String namespacePrefix, @Nullable String xPath) {
XPathMatcher matcher = StringUtils.isBlank(xPath) ? null : new XPathMatcher(xPath);
Set<Xml.Tag> ts = new HashSet<>();
new XmlVisitor<Set<Xml.Tag>>() {
@Override
public Xml visitTag(Xml.Tag tag, Set<Xml.Tag> ts) {
if (tag.getNamespaces().containsKey(namespacePrefix) && (matcher == null || matcher.matches(getCursor()))) {
ts.add(tag);
}
return super.visitTag(tag, ts);
}
}.visit(x, ts);
Set<Xml.Tag> ts = new LinkedHashSet<>();
Namespaced.matcher()
.xPath(xPath)
.prefix(namespacePrefix)
.asVisitor(n ->
{
if (n.getTree() instanceof Xml.Tag) {
ts.add((Xml.Tag) n.getTree());
}
return n.getTree();
}).visit(x, 0);
return ts;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@
import org.openrewrite.Option;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.xml.XPathMatcher;
import org.openrewrite.xml.XmlVisitor;
import org.openrewrite.xml.trait.Namespaced;
import org.openrewrite.xml.tree.Xml;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

@Value
Expand Down Expand Up @@ -59,32 +57,26 @@ public String getDescription() {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
XPathMatcher matcher = StringUtils.isBlank(xPath) ? null : new XPathMatcher(xPath);
return new XmlVisitor<ExecutionContext>() {

@Override
public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx);
if (tag.getNamespaces().containsValue(namespaceUri) && (matcher == null || matcher.matches(getCursor()))) {
t = SearchResult.found(t);
}
return t;
}
};
return Namespaced.matcher()
.xPath(xPath)
.uri(namespaceUri)
.asVisitor(n -> n.getTree() instanceof Xml.Tag ?
SearchResult.found(n.getTree()) :
n.getTree());
}

public static Set<Xml.Tag> find(Xml x, String namespaceUri, @Nullable String xPath) {
XPathMatcher matcher = StringUtils.isBlank(xPath) ? null : new XPathMatcher(xPath);
Set<Xml.Tag> ts = new HashSet<>();
new XmlVisitor<Set<Xml.Tag>>() {
@Override
public Xml visitTag(Xml.Tag tag, Set<Xml.Tag> ts) {
if (tag.getNamespaces().containsValue(namespaceUri) && (matcher == null || matcher.matches(getCursor()))) {
ts.add(tag);
}
return super.visitTag(tag, ts);
}
}.visit(x, ts);
Set<Xml.Tag> ts = new LinkedHashSet<>();
Namespaced.matcher()
.xPath(xPath)
.uri(namespaceUri)
.asVisitor(n ->
{
if (n.getTree() instanceof Xml.Tag) {
ts.add((Xml.Tag) n.getTree());
}
return n.getTree();
}).visit(x, 0);
return ts;
}
}
Loading

0 comments on commit cda2cfb

Please sign in to comment.