You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2023/11/27 23:11:39 UTC

(maven) branch master updated: [MNG-6036] Add namespace to XmlNode (#1318)

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 05fcf5b2d3 [MNG-6036] Add namespace to XmlNode (#1318)
05fcf5b2d3 is described below

commit 05fcf5b2d3b529385563619d0693eb4a2b7847d0
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Tue Nov 28 00:11:34 2023 +0100

    [MNG-6036] Add namespace to XmlNode (#1318)
---
 .../java/org/apache/maven/api/xml/XmlNode.java     |  10 +-
 .../org/apache/maven/model/v4/ModelXmlTest.java    |  36 ++++++
 .../apache/maven/internal/xml/XmlNodeBuilder.java  |  48 +++----
 .../org/apache/maven/internal/xml/XmlNodeImpl.java |  98 +++++++++-----
 .../apache/maven/internal/xml/XmlNodeWriter.java   | 141 +++++++++++++++++----
 5 files changed, 251 insertions(+), 82 deletions(-)

diff --git a/api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java b/api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java
index df6335ee8d..87ad7ae7e3 100644
--- a/api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java
+++ b/api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java
@@ -30,8 +30,6 @@ import org.apache.maven.api.annotations.ThreadSafe;
 /**
  * An immutable xml node.
  *
- * TODO: v4: add support for namespaces
- *
  * @since 4.0.0
  */
 @Experimental
@@ -82,6 +80,12 @@ public interface XmlNode {
     @Nonnull
     String getName();
 
+    @Nonnull
+    String getNamespaceUri();
+
+    @Nonnull
+    String getPrefix();
+
     @Nullable
     String getValue();
 
@@ -106,8 +110,6 @@ public interface XmlNode {
 
     XmlNode merge(@Nullable XmlNode source, @Nullable Boolean childMergeOverride);
 
-    XmlNode clone();
-
     /**
      * Merge recessive into dominant and return either {@code dominant}
      * with merged information or a clone of {@code recessive} if
diff --git a/maven-model/src/test/java/org/apache/maven/model/v4/ModelXmlTest.java b/maven-model/src/test/java/org/apache/maven/model/v4/ModelXmlTest.java
index 682ff7d238..ece1d046ea 100644
--- a/maven-model/src/test/java/org/apache/maven/model/v4/ModelXmlTest.java
+++ b/maven-model/src/test/java/org/apache/maven/model/v4/ModelXmlTest.java
@@ -27,9 +27,13 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Plugin;
+import org.apache.maven.api.xml.XmlNode;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 class ModelXmlTest {
 
@@ -50,6 +54,38 @@ class ModelXmlTest {
         }
     }
 
+    @Test
+    void testNamespaceInXmlNode() throws XMLStreamException {
+        String xml = "<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+                + "         xmlns=\"http://maven.apache.org/POM/4.0.0\"\n"
+                + "         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/POM/4.0.0\">\n"
+                + "  <build>\n"
+                + "    <plugins>\n"
+                + "      <plugin>\n"
+                + "         <m:configuration xmlns:m=\"http://maven.apache.org/POM/4.0.0\" xmlns=\"http://fabric8.io/fabric8-maven-plugin\">\n"
+                + "             <myConfig>foo</myConfig>\n"
+                + "         </m:configuration>\n"
+                + "      </plugin>\n"
+                + "    </plugins>\n"
+                + "  </build>\n"
+                + "</project>";
+
+        Model model = fromXml(xml);
+        Plugin plugin = model.getBuild().getPlugins().get(0);
+        XmlNode node = plugin.getConfiguration();
+        assertNotNull(node);
+        assertEquals("http://maven.apache.org/POM/4.0.0", node.getNamespaceUri());
+        assertEquals("m", node.getPrefix());
+        assertEquals("configuration", node.getName());
+        assertEquals(1, node.getChildren().size());
+        XmlNode myConfig = node.getChildren().get(0);
+        assertEquals("http://fabric8.io/fabric8-maven-plugin", myConfig.getNamespaceUri());
+        assertEquals("", myConfig.getPrefix());
+        assertEquals("myConfig", myConfig.getName());
+        String config = node.toString();
+        assertFalse(config.isEmpty());
+    }
+
     String toXml(Model model) throws IOException, XMLStreamException {
         StringWriter sw = new StringWriter();
         MavenStaxWriter writer = new MavenStaxWriter();
diff --git a/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeBuilder.java b/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeBuilder.java
index 75a83d0a1b..91b199ee96 100644
--- a/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeBuilder.java
+++ b/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeBuilder.java
@@ -176,8 +176,10 @@ public class XmlNodeBuilder {
     public static XmlNodeImpl build(XMLStreamReader parser, boolean trim, InputLocationBuilderStax locationBuilder)
             throws XMLStreamException {
         boolean spacePreserve = false;
-        String name = null;
-        String value = null;
+        String lPrefix = null;
+        String lNamespaceUri = null;
+        String lName = null;
+        String lValue = null;
         Object location = null;
         Map<String, String> attrs = null;
         List<XmlNode> children = null;
@@ -187,31 +189,29 @@ public class XmlNodeBuilder {
             if (eventType == XMLStreamReader.START_ELEMENT) {
                 lastStartTag = parser.getLocation().getLineNumber() * 1000
                         + parser.getLocation().getColumnNumber();
-                if (name == null) {
+                if (lName == null) {
                     int namespacesSize = parser.getNamespaceCount();
-                    name = parser.getLocalName();
-                    String pfx = parser.getPrefix();
-                    if (pfx != null && !pfx.isEmpty()) {
-                        name = pfx + ":" + name;
-                    }
+                    lPrefix = parser.getPrefix();
+                    lNamespaceUri = parser.getNamespaceURI();
+                    lName = parser.getLocalName();
                     location = locationBuilder != null ? locationBuilder.toInputLocation(parser) : null;
                     int attributesSize = parser.getAttributeCount();
                     if (attributesSize > 0 || namespacesSize > 0) {
                         attrs = new HashMap<>();
                         for (int i = 0; i < namespacesSize; i++) {
-                            String prefix = parser.getNamespacePrefix(i);
-                            String namespace = parser.getNamespaceURI(i);
-                            attrs.put(prefix != null && !prefix.isEmpty() ? "xmlns:" + prefix : "xmlns", namespace);
+                            String nsPrefix = parser.getNamespacePrefix(i);
+                            String nsUri = parser.getNamespaceURI(i);
+                            attrs.put(nsPrefix != null && !nsPrefix.isEmpty() ? "xmlns:" + nsPrefix : "xmlns", nsUri);
                         }
                         for (int i = 0; i < attributesSize; i++) {
-                            String aname = parser.getAttributeLocalName(i);
-                            String avalue = parser.getAttributeValue(i);
-                            String apfx = parser.getAttributePrefix(i);
-                            if (apfx != null && !apfx.isEmpty()) {
-                                aname = apfx + ":" + aname;
+                            String aName = parser.getAttributeLocalName(i);
+                            String aValue = parser.getAttributeValue(i);
+                            String aPrefix = parser.getAttributePrefix(i);
+                            if (aPrefix != null && !aPrefix.isEmpty()) {
+                                aName = aPrefix + ":" + aName;
                             }
-                            attrs.put(aname, avalue);
-                            spacePreserve = spacePreserve || ("xml:space".equals(aname) && "preserve".equals(avalue));
+                            attrs.put(aName, aValue);
+                            spacePreserve = spacePreserve || ("xml:space".equals(aName) && "preserve".equals(aValue));
                         }
                     }
                 } else {
@@ -223,17 +223,19 @@ public class XmlNodeBuilder {
                 }
             } else if (eventType == XMLStreamReader.CHARACTERS || eventType == XMLStreamReader.CDATA) {
                 String text = parser.getText();
-                value = value != null ? value + text : text;
+                lValue = lValue != null ? lValue + text : text;
             } else if (eventType == XMLStreamReader.END_ELEMENT) {
                 boolean emptyTag = lastStartTag
                         == parser.getLocation().getLineNumber() * 1000
                                 + parser.getLocation().getColumnNumber();
-                if (value != null && trim && !spacePreserve) {
-                    value = value.trim();
+                if (lValue != null && trim && !spacePreserve) {
+                    lValue = lValue.trim();
                 }
                 return new XmlNodeImpl(
-                        name,
-                        children == null ? (value != null ? value : emptyTag ? null : "") : null,
+                        lPrefix,
+                        lNamespaceUri,
+                        lName,
+                        children == null ? (lValue != null ? lValue : emptyTag ? null : "") : null,
                         attrs,
                         children,
                         location);
diff --git a/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeImpl.java b/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeImpl.java
index f405112791..1210b81320 100644
--- a/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeImpl.java
+++ b/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeImpl.java
@@ -18,7 +18,8 @@
  */
 package org.apache.maven.internal.xml;
 
-import java.io.IOException;
+import javax.xml.stream.XMLStreamException;
+
 import java.io.Serializable;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -31,14 +32,11 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.apache.maven.api.xml.XmlNode;
-import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
-import org.codehaus.plexus.util.xml.SerializerXMLWriter;
-import org.codehaus.plexus.util.xml.XMLWriter;
-import org.codehaus.plexus.util.xml.pull.XmlSerializer;
 
 /**
  *  NOTE: remove all the util code in here when separated, this class should be pure data.
@@ -46,6 +44,10 @@ import org.codehaus.plexus.util.xml.pull.XmlSerializer;
 public class XmlNodeImpl implements Serializable, XmlNode {
     private static final long serialVersionUID = 2567894443061173996L;
 
+    protected final String prefix;
+
+    protected final String namespaceUri;
+
     protected final String name;
 
     protected final String value;
@@ -70,6 +72,19 @@ public class XmlNodeImpl implements Serializable, XmlNode {
 
     public XmlNodeImpl(
             String name, String value, Map<String, String> attributes, List<XmlNode> children, Object location) {
+        this("", "", name, value, attributes, children, location);
+    }
+
+    public XmlNodeImpl(
+            String prefix,
+            String namespaceUri,
+            String name,
+            String value,
+            Map<String, String> attributes,
+            List<XmlNode> children,
+            Object location) {
+        this.prefix = prefix == null ? "" : prefix;
+        this.namespaceUri = namespaceUri == null ? "" : namespaceUri;
         this.name = Objects.requireNonNull(name);
         this.value = value;
         this.attributes =
@@ -84,14 +99,21 @@ public class XmlNodeImpl implements Serializable, XmlNode {
         return merge(this, source, childMergeOverride);
     }
 
-    public XmlNode clone() {
-        return this;
-    }
-
     // ----------------------------------------------------------------------
     // Name handling
     // ----------------------------------------------------------------------
 
+    @Override
+    public String getPrefix() {
+        return prefix;
+    }
+
+    @Override
+    public String getNamespaceUri() {
+        return namespaceUri;
+    }
+
+    @Override
     public String getName() {
         return name;
     }
@@ -158,16 +180,6 @@ public class XmlNodeImpl implements Serializable, XmlNode {
     // Helpers
     // ----------------------------------------------------------------------
 
-    public void writeToSerializer(String namespace, XmlSerializer serializer) throws IOException {
-        // TODO: WARNING! Later versions of plexus-utils psit out an <?xml ?> header due to thinking this is a new
-        // document - not the desired behaviour!
-        SerializerXMLWriter xmlWriter = new SerializerXMLWriter(namespace, serializer);
-        XmlNodeWriter.write(xmlWriter, this);
-        if (xmlWriter.getExceptions().size() > 0) {
-            throw (IOException) xmlWriter.getExceptions().get(0);
-        }
-    }
-
     /**
      * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm.<p>
      * The algorithm is as follows:
@@ -238,7 +250,7 @@ public class XmlNodeImpl implements Serializable, XmlNode {
                 }
             }
 
-            if (recessive.getChildren().size() > 0) {
+            if (!recessive.getChildren().isEmpty()) {
                 boolean mergeChildren = true;
                 if (childMergeOverride != null) {
                     mergeChildren = childMergeOverride;
@@ -256,7 +268,7 @@ public class XmlNodeImpl implements Serializable, XmlNode {
                     List<XmlNode> dominantChildren = dominant.getChildren().stream()
                             .filter(n -> n.getName().equals(name))
                             .collect(Collectors.toList());
-                    if (dominantChildren.size() > 0) {
+                    if (!dominantChildren.isEmpty()) {
                         commonChildren.put(name, dominantChildren.iterator());
                     }
                 }
@@ -267,7 +279,7 @@ public class XmlNodeImpl implements Serializable, XmlNode {
                     String idValue = recessiveChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE);
 
                     XmlNode childDom = null;
-                    if (isNotEmpty(idValue)) {
+                    if (!isEmpty(idValue)) {
                         for (XmlNode dominantChild : dominant.getChildren()) {
                             if (idValue.equals(dominantChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE))) {
                                 childDom = dominantChild;
@@ -275,7 +287,7 @@ public class XmlNodeImpl implements Serializable, XmlNode {
                                 mergeChildren = true;
                             }
                         }
-                    } else if (isNotEmpty(keysValue)) {
+                    } else if (!isEmpty(keysValue)) {
                         String[] keys = keysValue.split(",");
                         Map<String, Optional<String>> recessiveKeyValues = Stream.of(keys)
                                 .collect(Collectors.toMap(
@@ -395,23 +407,47 @@ public class XmlNodeImpl implements Serializable, XmlNode {
 
     @Override
     public String toString() {
+        try {
+            return toStringXml();
+        } catch (XMLStreamException e) {
+            return toStringObject();
+        }
+    }
+
+    public String toStringXml() throws XMLStreamException {
         StringWriter writer = new StringWriter();
         XmlNodeWriter.write(writer, this);
         return writer.toString();
     }
 
-    public String toUnescapedString() {
-        StringWriter writer = new StringWriter();
-        XMLWriter xmlWriter = new PrettyPrintXMLWriter(writer);
-        XmlNodeWriter.write(xmlWriter, this, false);
-        return writer.toString();
+    public String toStringObject() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("XmlNode[");
+        boolean w = false;
+        w = addToStringField(sb, prefix, o -> !o.isEmpty(), "prefix", w);
+        w = addToStringField(sb, namespaceUri, o -> !o.isEmpty(), "namespaceUri", w);
+        w = addToStringField(sb, name, o -> !o.isEmpty(), "name", w);
+        w = addToStringField(sb, value, o -> !o.isEmpty(), "value", w);
+        w = addToStringField(sb, attributes, o -> !o.isEmpty(), "attributes", w);
+        w = addToStringField(sb, children, o -> !o.isEmpty(), "children", w);
+        w = addToStringField(sb, location, Objects::nonNull, "location", w);
+        sb.append("]");
+        return sb.toString();
     }
 
-    private static boolean isNotEmpty(String str) {
-        return ((str != null) && (str.length() > 0));
+    private static <T> boolean addToStringField(StringBuilder sb, T o, Function<T, Boolean> p, String n, boolean w) {
+        if (!p.apply(o)) {
+            if (w) {
+                sb.append(", ");
+            } else {
+                w = true;
+            }
+            sb.append(n).append("='").append(o).append('\'');
+        }
+        return w;
     }
 
     private static boolean isEmpty(String str) {
-        return ((str == null) || (str.length() == 0));
+        return str == null || str.isEmpty();
     }
 }
diff --git a/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeWriter.java b/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeWriter.java
index 812d7d8d4f..ee50fca1da 100644
--- a/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeWriter.java
+++ b/maven-xml-impl/src/main/java/org/apache/maven/internal/xml/XmlNodeWriter.java
@@ -18,49 +18,142 @@
  */
 package org.apache.maven.internal.xml;
 
-import java.io.PrintWriter;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
 import java.io.Writer;
 import java.util.Map;
 
 import org.apache.maven.api.xml.XmlNode;
-import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
-import org.codehaus.plexus.util.xml.XMLWriter;
+import org.codehaus.stax2.util.StreamWriterDelegate;
 
 /**
  *
  */
 public class XmlNodeWriter {
-    public static void write(Writer writer, XmlNode dom) {
-        write(new PrettyPrintXMLWriter(writer), dom);
-    }
-
-    public static void write(PrintWriter writer, XmlNode dom) {
-        write(new PrettyPrintXMLWriter(writer), dom);
-    }
-
-    public static void write(XMLWriter xmlWriter, XmlNode dom) {
-        write(xmlWriter, dom, true);
+    public static void write(Writer writer, XmlNode dom) throws XMLStreamException {
+        XMLOutputFactory factory = new com.ctc.wstx.stax.WstxOutputFactory();
+        factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, false);
+        factory.setProperty(com.ctc.wstx.api.WstxOutputProperties.P_USE_DOUBLE_QUOTES_IN_XML_DECL, true);
+        factory.setProperty(com.ctc.wstx.api.WstxOutputProperties.P_ADD_SPACE_AFTER_EMPTY_ELEM, true);
+        XMLStreamWriter serializer = new IndentingXMLStreamWriter(factory.createXMLStreamWriter(writer));
+        write(serializer, dom);
+        serializer.close();
     }
 
-    public static void write(XMLWriter xmlWriter, XmlNode dom, boolean escape) {
-        // TODO: move to XMLWriter?
-        xmlWriter.startElement(dom.getName());
+    public static void write(XMLStreamWriter xmlWriter, XmlNode dom) throws XMLStreamException {
+        xmlWriter.writeStartElement(dom.getPrefix(), dom.getName(), dom.getNamespaceUri());
         for (Map.Entry<String, String> attr : dom.getAttributes().entrySet()) {
-            xmlWriter.addAttribute(attr.getKey(), attr.getValue());
+            xmlWriter.writeAttribute(attr.getKey(), attr.getValue());
         }
         for (XmlNode aChildren : dom.getChildren()) {
-            write(xmlWriter, aChildren, escape);
+            write(xmlWriter, aChildren);
         }
-
         String value = dom.getValue();
         if (value != null) {
-            if (escape) {
-                xmlWriter.writeText(value);
-            } else {
-                xmlWriter.writeMarkup(value);
+            xmlWriter.writeCharacters(value);
+        }
+        xmlWriter.writeEndElement();
+    }
+
+    static class IndentingXMLStreamWriter extends StreamWriterDelegate {
+
+        int depth = 0;
+        boolean hasChildren = false;
+        boolean anew = true;
+
+        IndentingXMLStreamWriter(XMLStreamWriter parent) {
+            super(parent);
+        }
+
+        @Override
+        public void writeStartDocument() throws XMLStreamException {
+            super.writeStartDocument();
+            anew = false;
+        }
+
+        @Override
+        public void writeStartDocument(String version) throws XMLStreamException {
+            super.writeStartDocument(version);
+            anew = false;
+        }
+
+        @Override
+        public void writeStartDocument(String encoding, String version) throws XMLStreamException {
+            super.writeStartDocument(encoding, version);
+            anew = false;
+        }
+
+        @Override
+        public void writeEmptyElement(String localName) throws XMLStreamException {
+            indent();
+            super.writeEmptyElement(localName);
+            hasChildren = true;
+            anew = false;
+        }
+
+        @Override
+        public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
+            indent();
+            super.writeEmptyElement(namespaceURI, localName);
+            hasChildren = true;
+            anew = false;
+        }
+
+        @Override
+        public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
+            indent();
+            super.writeEmptyElement(prefix, localName, namespaceURI);
+            hasChildren = true;
+            anew = false;
+        }
+
+        @Override
+        public void writeStartElement(String localName) throws XMLStreamException {
+            indent();
+            super.writeStartElement(localName);
+            depth++;
+            hasChildren = false;
+            anew = false;
+        }
+
+        @Override
+        public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
+            indent();
+            super.writeStartElement(namespaceURI, localName);
+            depth++;
+            hasChildren = false;
+            anew = false;
+        }
+
+        @Override
+        public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
+            indent();
+            super.writeStartElement(prefix, localName, namespaceURI);
+            depth++;
+            hasChildren = false;
+            anew = false;
+        }
+
+        @Override
+        public void writeEndElement() throws XMLStreamException {
+            depth--;
+            if (hasChildren) {
+                indent();
             }
+            super.writeEndElement();
+            hasChildren = true;
+            anew = false;
         }
 
-        xmlWriter.endElement();
+        private void indent() throws XMLStreamException {
+            if (!anew) {
+                super.writeCharacters("\n");
+            }
+            for (int i = 0; i < depth; i++) {
+                super.writeCharacters("  ");
+            }
+        }
     }
 }