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.