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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 4e6074db4efbb9727a5057a80c41f56d8e1dee13
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Nov 23 13:20:48 2023 +0100

    In XPath parsing, the "Q{namespace}name" syntax should be translated to 
"namespace:name", not "namespace/name".
---
 .../org/apache/sis/filter/AssociationValue.java    |   2 +-
 .../main/org/apache/sis/filter/PropertyValue.java  |   2 +-
 .../main/org/apache/sis/filter/internal/XPath.java | 136 +++++++++++++--------
 .../test/org/apache/sis/filter/XPathTest.java      |  12 +-
 .../main/org/apache/sis/xml/ValueConverter.java    |   4 +-
 .../main/org/apache/sis/xml/XPointer.java          |   2 +-
 .../main/org/apache/sis/storage/FeatureQuery.java  |   2 +-
 .../apache/sis/util/resources/Errors_fr.properties |   2 +-
 8 files changed, 98 insertions(+), 64 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
index b4fd17293b..3b8242bf4c 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
@@ -39,7 +39,7 @@ import org.opengis.filter.ValueReference;
 
 /**
  * Expression whose value is computed by retrieving the value indicated by the 
provided path.
- * This is used for value reference given by x-path such as "a/b/c". The last 
element of the path
+ * This is used for value reference given by XPath such as "a/b/c". The last 
element of the path
  * (the tip) is evaluated by a {@link PropertyValue}.
  *
  * @author  Martin Desruisseaux (Geomatys)
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
index bd930ebdbe..6b77bf77e6 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
@@ -74,7 +74,7 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V>
     protected final boolean isVirtual;
 
     /**
-     * The prefix in a x-path for considering a property as virtual.
+     * The prefix in a XPath for considering a property as virtual.
      */
     static final String VIRTUAL_PREFIX = "/*/";
 
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/XPath.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/XPath.java
index a267b691fa..7e73f9c30d 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/XPath.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/XPath.java
@@ -20,12 +20,13 @@ import java.util.List;
 import java.util.Arrays;
 import java.util.ArrayList;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.iso.DefaultNameSpace;
 
 import static org.apache.sis.util.CharSequences.*;
 
 
 /**
- * Basic support of X-Path in {@code ValueReference} expression.
+ * Basic support of XPath in {@code ValueReference} expression.
  * This is intended to be only a lightweight support, not a replacement for 
{@link javax.xml.xpath} implementations.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -34,7 +35,7 @@ public final class XPath {
     /**
      * The separator between path components.
      * Should not be used for URL or Unix name separator, even if the 
character is the same.
-     * We use this constant for identifying locations in the code where there 
is some X-Path parsing.
+     * We use this constant for identifying locations in the code where there 
is some XPath parsing.
      */
     public static final char SEPARATOR = '/';
 
@@ -71,6 +72,20 @@ public final class XPath {
      */
     public boolean isAbsolute;
 
+    /**
+     * Creates a XPath with the given path components.
+     * The components are assumed already parsed (no braced URI literals).
+     *
+     * @param  path  components of the XPath before the tip, or {@code null} 
if none.
+     * @param  tip   the last component of the XPath.
+     */
+    public XPath(final String[] path, final String tip) {
+        if (path != null) {
+            this.path = Arrays.asList(path);
+        }
+        this.tip = tip;
+    }
+
     /**
      * Splits the given XPath around the {@code '/'} separator, except for the 
part between curly brackets.
      * If a leading {@code '/'} character is present, it is removed and {@link 
#isAbsolute} is set to true.
@@ -84,7 +99,7 @@ public final class XPath {
          * Check whether the XPath is absolute.
          * This is identified by a leading "/".
          */
-        int length = xpath.length();
+        final int length = xpath.length();
         int start  = skipLeadingWhitespaces(xpath, 0, length);
         if (start >= length) {
             throw new 
IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "xpath"));
@@ -93,59 +108,84 @@ public final class XPath {
             start = skipLeadingWhitespaces(xpath, start+1, length);
             isAbsolute = true;
         }
-        /*
-         * Check for braced URI literal, for example "Q{http://example.com}";.
-         * The "Q" prefix is mandated by XPath 3.1 specification, but optional 
in this implementation.
-         * Any other prefix is considered an error, as the brackets may have 
another signification.
-         */
-        int open = xpath.indexOf(OPEN, start);
-        if (open >= 0) {
-            final int before = skipLeadingWhitespaces(xpath, start, open);
-            if (before != open && (before != open-1 || xpath.charAt(before) != 
BRACED_URI_PREFIX)) {
-                throw new 
IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedXPath_1, 
xpath.substring(before)));
-            }
-            final int close = xpath.indexOf(CLOSE, ++open);
-            if (close < 0) {
-                throw new 
IllegalArgumentException(Errors.format(Errors.Keys.MissingCharacterInElement_2, 
xpath.substring(before), CLOSE));
-            }
-            final String part = trimWhitespaces(xpath, open, close).toString();
-            if (part.indexOf(OPEN) >= 0) {
-                throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalCharacter_2, part, 
OPEN));
+        String namespace;
+        for (;;) {
+            /*
+             * Check for braced URI literal, for example 
"Q{http://example.com}";.
+             * The "Q" prefix is mandated by XPath 3.1 specification, but 
optional in this implementation.
+             * Any other prefix is considered an error, as the brackets may 
have another signification.
+             */
+            namespace = null;
+            int open = xpath.indexOf(OPEN, start);
+            if (open >= 0) {
+                final int before = skipLeadingWhitespaces(xpath, start, open);
+                if (before != open && (before != open-1 || 
xpath.charAt(before) != BRACED_URI_PREFIX)) {
+                    throw new 
IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedXPath_1, 
xpath.substring(before)));
+                }
+                final int close = xpath.indexOf(CLOSE, ++open);
+                if (close < 0) {
+                    throw new 
IllegalArgumentException(Errors.format(Errors.Keys.MissingCharacterInElement_2, 
xpath.substring(before), CLOSE));
+                }
+                namespace = trimWhitespaces(xpath, open, close).toString();
+                if (namespace.indexOf(OPEN) >= 0) {
+                    throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalCharacter_2, 
namespace, OPEN));
+                }
+                start = close + 1;
             }
-            path = new ArrayList<>(4);
-            path.add(part);
-            start = close + 1;
-        }
-        /*
-         * Add all components before the last "/" characters.
-         * The remaining is the tip, stored separately.
-         */
-        int next;
-        while ((next = xpath.indexOf(SEPARATOR, start)) >= 0) {
+            /*
+             * Add the name component before the next "/" character.
+             * The loop is repeated for all components except the last one.
+             */
+            final int next = xpath.indexOf(SEPARATOR, start);
+            if (next < 0) break;
             if (path == null) {
                 path = new ArrayList<>(4);
             }
-            path.add(trimWhitespaces(xpath, start, next).toString());
+            path.add(toQualifiedName(namespace, xpath, start, next));
             start = next + 1;
         }
-        tip = trimWhitespaces(xpath, start, length).toString();
+        /*
+         * The remaining is the tip, stored separately.
+         */
+        tip = toQualifiedName(namespace, xpath, start, length);
         if (tip.isEmpty() || (path != null && 
path.stream().anyMatch(String::isEmpty))) {
             throw new 
IllegalArgumentException(Errors.format(Errors.Keys.UnsupportedXPath_1, xpath));
         }
     }
 
     /**
-     * Creates a XPath with the given path components.
-     * The components are assumed already parsed (no braced URI literals).
+     * Creates a qualified name with a name space (optional) followed by a 
name extracted
+     * from a sub-string of the XPath. Leading and trailing white spaces are 
omitted.
      *
-     * @param  path  components of the XPath before the tip, or {@code null} 
if none.
-     * @param  tip   the last component of the XPath.
+     * @param  namespace  the name space, or {@code null} if none.
+     * @param  xpath      the XPath containing the name.
+     * @param  start      index of the first character of the name.
+     * @param  end        index after the last character of the name.
+     * @return {@code namespace:name}, or only the name if the name spae is 
null.
      */
-    public XPath(final String[] path, final String tip) {
-        if (path != null) {
-            this.path = Arrays.asList(path);
+    private static String toQualifiedName(final String namespace, final String 
xpath, final int start, final int end) {
+        String name = trimWhitespaces(xpath, start, end).toString();
+        if (namespace != null) {
+            name = namespace + DefaultNameSpace.DEFAULT_SEPARATOR + name;
+        }
+        return name;
+    }
+
+    /**
+     * Reformat a name component as a braced URI, if needed.
+     *
+     * @param  component  the name component to reformat.
+     * @param  sb  where to write the name component.
+     * @return the builder, for chained method calls.
+     */
+    private static StringBuilder toBracedURI(final String component, final 
StringBuilder sb) {
+        final int end = 
component.lastIndexOf(DefaultNameSpace.DEFAULT_SEPARATOR);
+        if (end >= 0 && component.lastIndexOf(SEPARATOR, end) >= 0) {
+            return sb.append(BRACED_URI_PREFIX).append(OPEN).append(component, 
0, end).append(CLOSE)
+                     .append(component, end+1, component.length());
+        } else {
+            return sb.append(component);
         }
-        this.tip = tip;
     }
 
     /**
@@ -155,22 +195,16 @@ public final class XPath {
      */
     @Override
     public String toString() {
-        if (!isAbsolute && path == null) {
+        if (!isAbsolute && path == null && tip.indexOf(SEPARATOR) < 0) {
             return tip;
         }
         final var sb = new StringBuilder(40);
         if (isAbsolute) sb.append(SEPARATOR);
         if (path != null) {
-            final int size = path.size();
-            for (int i=0; i<size; i++) {
-                final String part = path.get(i);
-                if (i == 0 && part.indexOf(SEPARATOR) >= 0) {
-                    
sb.append(BRACED_URI_PREFIX).append(OPEN).append(part).append(CLOSE);
-                } else {
-                    sb.append(part).append(SEPARATOR);
-                }
+            for (final String component : path) {
+                toBracedURI(component, sb).append(SEPARATOR);
             }
         }
-        return sb.append(tip).toString();
+        return toBracedURI(tip, sb).toString();
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/XPathTest.java 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/XPathTest.java
index 541157101a..5dedf5a84d 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/XPathTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/XPathTest.java
@@ -38,9 +38,9 @@ public final class XPathTest extends TestCase {
     }
 
     /**
-     * Splits a x-path and verifies the result.
+     * Splits a XPath and verifies the result.
      *
-     * @param xpath       the x-path to parse.
+     * @param xpath       the XPath to parse.
      * @param isAbsolute  expected value if {@link XPath#isAbsolute}.
      * @param path        expected value if {@link XPath#path}. Can be null.
      * @param tip         expected value if {@link XPath#tip}.
@@ -67,12 +67,12 @@ public final class XPathTest extends TestCase {
     }
 
     /**
-     * Tests with a x-path containing an URL as the property namespace.
+     * Tests with a XPath containing an URL as the property namespace.
      */
     @Test
     public void testQualifiedName() {
-        split("Q{http://example.com/foo/bar}property";,       false, new 
String[] {"http://example.com/foo/bar"}, "property");
-        split("Q{http://example.com/foo/bar}property/child";, false, new 
String[] {"http://example.com/foo/bar";,  "property"}, "child");
-        split("/Q{http://example.com/foo/bar}property";,      true,  new 
String[] {"http://example.com/foo/bar"}, "property");
+        split("Q{http://example.com/foo/bar}property";,       false, null,      
   "http://example.com/foo/bar:property";);
+        split("/Q{http://example.com/foo/bar}property";,      true,  null,      
   "http://example.com/foo/bar:property";);
+        split("Q{http://example.com/foo/bar}property/child";, false, new 
String[] {"http://example.com/foo/bar:property"}, "child");
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ValueConverter.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ValueConverter.java
index e2cb09c881..94d4d4d75f 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ValueConverter.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ValueConverter.java
@@ -325,11 +325,11 @@ public class ValueConverter {
         value = Strings.trimOrNull(value);
         if (value != null) try {
             /*
-             * First, check for X-Paths like below:
+             * First, check for XPaths like below:
              *
              *     
http://www.isotc211.org/2005/resources/uom/gmxUom.xml#xpointer(//*[@gml:id='m'])
              *
-             * Technically the 'm' value in the X-Path is not necessarily a 
unit symbol.
+             * Technically the 'm' value in the XPath is not necessarily a 
unit symbol.
              * It is rather a reference to a definition like below:
              *
              * <uomItem>
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XPointer.java 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XPointer.java
index 438038a5c0..4b4cf84dc2 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XPointer.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/XPointer.java
@@ -21,7 +21,7 @@ import static 
org.apache.sis.util.internal.DefinitionURI.regionMatches;
 
 
 /**
- * Parsers of pointers in x-paths, adapted to the syntax found in GML 
documents.
+ * Parsers of pointers in XPaths, adapted to the syntax found in GML documents.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
index 52a6ec31d8..ae094209a9 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
@@ -716,7 +716,7 @@ public class FeatureQuery extends Query implements 
Cloneable, Serializable {
      *
      * <ul>
      *   <li>If the expression is an instance of {@link ValueReference}, the 
name of the
-     *       property referenced by the {@linkplain ValueReference#getXPath() 
x-path}.</li>
+     *       property referenced by the {@linkplain ValueReference#getXPath() 
XPath}.</li>
      *   <li>Otherwise the localized string "Unnamed #1" with increasing 
numbers.</li>
      * </ul>
      *
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
index d22d6076bd..0abec550f7 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
@@ -220,7 +220,7 @@ UnsupportedAxisDirection_1        = Les axes de direction 
\u00ab\u202f{0}\u202f\
 UnsupportedCoordinateSystem_1     = Le syst\u00e8me de coordonn\u00e9es 
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas support\u00e9 par cette 
op\u00e9ration.
 UnsupportedDatum_1                = Le r\u00e9f\u00e9rentiel 
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas support\u00e9 par cette 
op\u00e9ration.
 UnsupportedType_1                 = Le type \u2018{0}\u2019 n\u2019est pas 
support\u00e9 dans ce contexte.
-UnsupportedXPath_1                = Le chemin x-path 
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas reconnu. L\u2019impl\u00e9mentation 
actuelle ne supporte que des chemins simples.
+UnsupportedXPath_1                = Le chemin XPath 
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas reconnu. L\u2019impl\u00e9mentation 
actuelle ne supporte que des chemins simples.
 ValueAlreadyDefined_1             = Une valeur est d\u00e9j\u00e0 d\u00e9finie 
pour \u00ab\u202f{0}\u202f\u00bb.
 ValueNotGreaterThanZero_2         = La valeur \u2018{0}\u2019 = {1,number} 
n\u2019est pas valide. On attendait un nombre positif non-nul.
 ValueOutOfRange_4                 = La valeur \u2018{0}\u2019 = {3} est 
invalide. Une valeur dans la plage [{1} \u2026 {2}] \u00e9tait attendue.

Reply via email to