You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/11/23 12:52:23 UTC
(sis) 02/02: In XPath parsing, the "Q{namespace}name" syntax should be translated to "namespace:name", not "namespace/name".
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 <ma...@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.