sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 03/03: Handle the corner-case of coordinate system WKT (the "CS" keyword) in definition of aliases.
Date Wed, 18 Nov 2020 23:52:19 GMT
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 85cfff7a0c67f3367f1f0ed4ca0da9e7443b2a52
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Nov 19 00:51:38 2020 +0100

    Handle the corner-case of coordinate system WKT (the "CS" keyword) in definition of aliases.
---
 .../java/org/apache/sis/io/wkt/AbstractParser.java |  19 +++-
 .../main/java/org/apache/sis/io/wkt/Element.java   |   2 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |   4 +-
 .../org/apache/sis/io/wkt/SingletonElement.java    |  83 +++++++++++++++
 .../java/org/apache/sis/io/wkt/StoredTree.java     | 116 +++++++++++++++++----
 .../java/org/apache/sis/io/wkt/WKTDictionary.java  |  30 ++++--
 .../main/java/org/apache/sis/io/wkt/WKTFormat.java |  53 +++++++---
 .../resources/org/apache/sis/io/wkt/ExtraCRS.txt   |  30 ++----
 8 files changed, 267 insertions(+), 70 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
index 09d9e77..74196b9 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
@@ -237,10 +237,11 @@ abstract class AbstractParser implements Parser {
      */
     @Override
     public final Object createFromWKT(final String wkt) throws FactoryException {
+        final ParsePosition position = new ParsePosition(0);
         Object result = null;
         Warnings warnings;
         try {
-            result = createFromWKT(wkt, new ParsePosition(0));
+            result = createFromWKT(wkt, position);
         } catch (ParseException exception) {
             final Throwable cause = exception.getCause();
             if (cause instanceof FactoryException) {
@@ -250,6 +251,12 @@ abstract class AbstractParser implements Parser {
         } finally {
             warnings = getAndClearWarnings(result);
         }
+        final CharSequence unparsed = CharSequences.token(wkt, position.getIndex());
+        if (unparsed.length() != 0) {
+            throw new FactoryException(Errors.getResources(errorLocale).getString(
+                        Errors.Keys.UnexpectedCharactersAfter_2,
+                        CharSequences.token(wkt, 0) + "[…]", unparsed));
+        }
         if (warnings != null) {
             log(new LogRecord(Level.WARNING, warnings.toString()));
         }
@@ -259,8 +266,10 @@ abstract class AbstractParser implements Parser {
     /**
      * Parses a <cite>Well-Know Text</cite> from specified position as a geodetic
object.
      * Caller should invoke {@link #getAndClearWarnings(Object)} in a {@code finally} block
-     * after this method.
+     * after this method and should decide what to do with remaining character at the end
of the string.
      *
+     * @param  text       the Well-Known Text (WKT) to parse.
+     * @param  position   index of the first character to parse (on input) or after last
parsed character (on output).
      * @return the parsed object.
      * @throws ParseException if the string can not be parsed.
      */
@@ -296,7 +305,7 @@ abstract class AbstractParser implements Parser {
      * @return the parsed object as a tree of {@link Element}s.
      * @throws ParseException if the string can not be parsed.
      *
-     * @see WKTFormat#textToTree(String, ParsePosition)
+     * @see WKTFormat#textToTree(String, ParsePosition, String)
      */
     final Element textToTree(final String wkt, final ParsePosition position) throws ParseException
{
         int lower = CharSequences.skipLeadingWhitespaces(wkt, position.getIndex(), wkt.length());
@@ -316,7 +325,9 @@ abstract class AbstractParser implements Parser {
             throw new UnparsableObjectException(errorLocale, Errors.Keys.NoSuchValue_1, new
Object[] {id}, lower);
         }
         position.setIndex(upper);
-        return fragment.toElement(this, ~0);
+        final SingletonElement singleton = new SingletonElement();
+        fragment.toElements(this, singleton, ~0);
+        return singleton.value;
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Element.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Element.java
index 711170d..192df2d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Element.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Element.java
@@ -267,7 +267,7 @@ final class Element {
                     position.setErrorIndex(lower);
                     throw new UnparsableObjectException(errorLocale, Errors.Keys.NoSuchValue_1,
new Object[] {id}, lower);
                 }
-                children.add(fragment.toElement(parser, ~lower));               // Set offset
to '$' in "$FOO".
+                fragment.toElements(parser, children, ~lower);                  // Set offset
to '$' in "$FOO".
                 lower = upper;
             } else if (Character.isUnicodeIdentifierStart(firstChar)) {
                 /*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
index 66ee90d..706f0f4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@ -232,8 +232,8 @@ class GeodeticObjectParser extends MathTransformParser implements Comparator<Coo
      * Caller should invoke {@link #getAndClearWarnings(Object)} in a {@code finally} block
      * after this method.
      *
-     * @param  text      the text to be parsed.
-     * @param  position  the position to start parsing from.
+     * @param  text       the Well-Known Text (WKT) to parse.
+     * @param  position   index of the first character to parse (on input) or after last
parsed character (on output).
      * @return the parsed object.
      * @throws ParseException if the string can not be parsed.
      */
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/SingletonElement.java
b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/SingletonElement.java
new file mode 100644
index 0000000..8fce746
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/SingletonElement.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.io.wkt;
+
+import java.util.AbstractSet;
+import java.util.Collections;
+import java.util.Collection;
+import java.util.Iterator;
+
+
+/**
+ * A mutable set containing either {@code null} or a single element.
+ * If more than one element is added, only the first one is kept.
+ * This is for use with {@link StoredTree#toElements(AbstractParser, Collection, int)}
+ * in the common case where we expect exactly one element.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class SingletonElement extends AbstractSet<Element> {
+    /**
+     * The singleton element, or {@code null} if none.
+     */
+    Element value;
+
+    /**
+     * Creates an initially empty singleton.
+     */
+    SingletonElement() {
+    }
+
+    /**
+     * Returns {@code true} if no value has been specified yet.
+     */
+    @Override
+    public boolean isEmpty() {
+        return value == null;
+    }
+
+    /**
+     * Returns the number of elements in this set, which can not be greater than 1.
+     */
+    @Override
+    public int size() {
+        return isEmpty() ? 0 : 1;
+    }
+
+    /**
+     * Returns an iterator over the elements in this set.
+     */
+    @Override
+    public Iterator<Element> iterator() {
+        return (isEmpty() ? Collections.<Element>emptySet() : Collections.singleton(value)).iterator();
+    }
+
+    /**
+     * Adds the given value if this set is empty.
+     */
+    @Override
+    public boolean add(final Element n) {
+        if (isEmpty()) {
+            value = n;
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/StoredTree.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/StoredTree.java
index d29c13c..8e6259c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/StoredTree.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/StoredTree.java
@@ -17,7 +17,9 @@
 package org.apache.sis.io.wkt;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.stream.Stream;
@@ -71,7 +73,9 @@ final class StoredTree implements Serializable {
         private static final long serialVersionUID = 1463070931527783896L;
 
         /**
-         * Copy of {@link Element#keyword} reference. Never {@code null}.
+         * Copy of {@link Element#keyword} reference, or {@code null} if this node is anonymous.
+         * Anonymous nodes are used only as wrappers for array of roots in the corner cases
+         * documented by {@link StoredTree#root}.
          *
          * @see StoredTree#keyword()
          */
@@ -86,9 +90,25 @@ final class StoredTree implements Serializable {
         private final Object[] children;
 
         /**
+         * Creates an anonymous node for an array of roots. This constructor is only for
the corner
+         * case documented in <cite>"Multi roots"</cite> section of {@link StoredTree#root}
javadoc.
+         *
+         * @see StoredTree#StoredTree(List, Map)
+         */
+        Node(final Deflater deflater, final List<Element> elements) {
+            keyword = null;
+            children = new Node[elements.size()];
+            for (int i=0; i<children.length; i++) {
+                children[i] = deflater.unique(new Node(deflater, elements.get(i)));
+            }
+        }
+
+        /**
          * Creates an immutable copy of the given element. Keywords and children references
          * are copied in this new {@code Node} but {@link Element#offset}s are copied in
a
          * separated array for making possible to share {@code Node} instances.
+         *
+         * @see StoredTree#StoredTree(Element, Map)
          */
         Node(final Deflater deflater, final Element element) {
             keyword  = (String) deflater.unique(element.keyword);
@@ -106,12 +126,28 @@ final class StoredTree implements Serializable {
         }
 
         /**
+         * Copies this node in modifiable {@link Element}s and add them to the given list.
+         * This is the converse of the {@link #Node(Deflater, List)} constructor.
+         * This method usually adds exactly one element to the given list, except
+         * for the "multi-roots" corner case documented in {@link StoredTree#root}.
+         *
+         * @see StoredTree#toElements(AbstractParser, Collection, int)
+         */
+        final void toElements(final Inflater inflater, final Collection<? super Element>
addTo) {
+            if (keyword != null) {
+                addTo.add(toElement(inflater));         // Standard case.
+            } else {
+                for (final Node child : (Node[]) children) {
+                    addTo.add(child.toElement(inflater));
+                }
+            }
+        }
+
+        /**
          * Copies this node in a modifiable {@link Element}.
          * This is the converse of the {@link #Node(Deflater, Element)} constructor.
-         *
-         * @see StoredTree#toElement(AbstractParser, int)
          */
-        final Element toElement(final Inflater inflater) {
+        private Element toElement(final Inflater inflater) {
             final LinkedList<Object> list;
             if (children == null) {
                 list = null;
@@ -144,6 +180,7 @@ final class StoredTree implements Serializable {
                         final Node node = (Node) object;
                         if (node.children != null) {
                             for (final String key : keys) {
+                                // Keyword is never null for children.
                                 if (node.keyword.equalsIgnoreCase(key)) {
                                     return node;
                                 }
@@ -180,7 +217,9 @@ final class StoredTree implements Serializable {
          * @see StoredTree#forEachValue(Consumer)
          */
         final void forEachValue(final Consumer<Object> addTo) {
-            addTo.accept(keyword);
+            if (keyword != null) {
+                addTo.accept(keyword);
+            }
             if (children != null) {
                 for (final Object child : children) {
                     addTo.accept(child);
@@ -215,6 +254,7 @@ final class StoredTree implements Serializable {
          */
         @Override
         public int hashCode() {
+            // We never use hashCode()/equals(Object) with anonymous node (null keyword).
             int hash = keyword.hashCode();
             if (children != null) {
                 for (final Object value : children) {
@@ -235,6 +275,7 @@ final class StoredTree implements Serializable {
         public boolean equals(final Object other) {
             if (other instanceof Node) {
                 final Node that = (Node) other;
+                // We never use hashCode()/equals(Object) with anonymous node (null keyword).
                 if (keyword.equals(that.keyword)) {
                     if (children == that.children) {
                         return true;
@@ -258,6 +299,23 @@ final class StoredTree implements Serializable {
 
     /**
      * Root of a tree of {@link Element} snapshots.
+     *
+     * <h4>Multi-roots</h4>
+     * There is exactly one root in the vast majority of cases. However there is a situation
+     * where we need to allow more roots: when user wants to represent a coordinate system.
+     * A WKT 2 coordinate system looks like:
+     *
+     * {@preformat wkt
+     *   CS[Cartesian, 2],
+     *     Axis["Easting (E)", east],
+     *     Axis["Northing (N)", north],
+     *     Unit["metre", 1]
+     * }
+     *
+     * While axes are conceptually parts of coordinate system, they are not declared inside
the {@code CS[…]}
+     * element for historical reasons (for compatibility with WKT 1). For representing such
"flattened tree",
+     * we need an array of roots. We do that by wrapping that array in a synthetic {@link
Node} with null
+     * {@link Node#keyword} (an "anonymous node").
      */
     private final Node root;
 
@@ -272,28 +330,42 @@ final class StoredTree implements Serializable {
     private final short[] offsets;
 
     /**
-     * Creates a new {@code StoredTree} with a copy of given arrays.
-     * Changes to the given array after construction will not affect this {@code StoredTree}.
+     * Creates a new {@code StoredTree} with a snapshot of given tree of elements.
+     *
+     * @param  tree          root of the tree of WKT elements.
+     * @param  sharedValues  pool to use for sharing unique instances of values.
+     */
+    StoredTree(final Element tree, final Map<Object,Object> sharedValues) {
+        final Deflater deflater = new Deflater(sharedValues);
+        root = (Node) deflater.unique(new Node(deflater, tree));
+        offsets = deflater.offsets();
+    }
+
+    /**
+     * Creates a new {@code StoredTree} with a snapshot of given trees of elements.
+     * This is for a corner case only; see <cite>"Multi roots"</cite> in {@link
#root}.
      *
-     * @param  root          root of the tree of WKT elements.
+     * @param  trees         roots of the trees of WKT elements.
      * @param  sharedValues  pool to use for sharing unique instances of values.
      */
-    StoredTree(final Element root, final Map<Object,Object> sharedValues) {
+    StoredTree(final List<Element> trees, final Map<Object,Object> sharedValues)
{
         final Deflater deflater = new Deflater(sharedValues);
-        this.root = (Node) deflater.unique(new Node(deflater, root));
+        root = new Node(deflater, trees);       // Do not invoke `unique(…)` on anymous
node.
         offsets = deflater.offsets();
     }
 
     /**
-     * Recreates {@link Element} tree. This method is the converse of the constructor.
+     * Recreates {@link Element} trees. This method is the converse of the constructor.
+     * This method usually adds exactly one element to the given list, except
+     * for the "multi-roots" corner case documented in {@link #root}.
      *
      * @param  parser      the parser which will be used for parsing the tree.
+     * @param  addTo       where to add the elements.
      * @param  isFragment  non-zero if and only if {@link Element#isFragment} shall be {@code
true}.
      *                     In such case, this value must be <code>~{@linkplain Element#offset}</code>.
-     * @return root of {@link Element} tree.
      */
-    final Element toElement(final AbstractParser parser, final int isFragment) {
-        return root.toElement(new Inflater(parser, offsets, isFragment));
+    final void toElements(final AbstractParser parser, final Collection<? super Element>
addTo, final int isFragment) {
+        root.toElements(new Inflater(parser, offsets, isFragment), addTo);
     }
 
     /**
@@ -302,7 +374,7 @@ final class StoredTree implements Serializable {
      * Each instances shall be used for constructing only one {@link Node}. After node construction,
this
      * instance lives longer in the {@link #sharedValues} map for sharing {@link #offsets}
arrays.
      *
-     * @see StoredTree#StoredTree(Element, Map)
+     * @see StoredTree#StoredTree(List, Map)
      */
     private static final class Deflater {
         /**
@@ -336,16 +408,18 @@ final class StoredTree implements Serializable {
         }
 
         /**
-         * Returns a unique instance of given node.
+         * Returns a unique instance of given object. The given value can be a {@link Node}
instance
+         * provided that it is not an anonymous node (i.e. {@link Node#keyword} shall be
non-null).
          *
-         * @return a previous instance from the pool, or {@code node} if none.
+         * @param  value  the value for which to get a unique instance.
+         * @return a previous instance from the pool, or {@code value} if none.
          *
          * @see Node#hashCode()
          * @see Node#equals(Object)
          */
-        final Object unique(final Object node) {
-            final Object existing = sharedValues.putIfAbsent(node, node);
-            return (existing != null) ? existing : node;
+        final Object unique(final Object value) {
+            final Object existing = sharedValues.putIfAbsent(value, value);
+            return (existing != null) ? existing : value;
         }
 
         /**
@@ -403,7 +477,7 @@ final class StoredTree implements Serializable {
      * A helper class for decompressing a tree of {@link Element}s from a tree of {@link
Node}s.
      * This is the converse of {@link Deflater}.
      *
-     * @see StoredTree#toElement(AbstractParser, int)
+     * @see StoredTree#toElements(AbstractParser, Collection, int)
      */
     private static final class Inflater {
         /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java
index 996bbc9..854ed10 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTDictionary.java
@@ -591,11 +591,10 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
             if (buffer.length() != 0) {
                 pos.setIndex(0);
                 final String wkt = buffer.toString();
-                final StoredTree tree = parser.textToTree(wkt, pos);
+                final StoredTree tree = parser.textToTree(wkt, pos, aliasKey);
                 final int end = pos.getIndex();
                 if (end < wkt.length()) {           // Trailing white spaces already removed
by `read(…)`.
-                    throw new FactoryDataException(resources().getString(Resources.Keys.UnexpectedTextAtLine_2,
-                                getLineNumber(), CharSequences.token(wkt, end)));
+                    throw new FactoryDataException(unexpectedText(getLineNumber(), wkt, end));
                 }
                 if (aliasKey != null) {
                     parser.addFragment(aliasKey, tree);
@@ -653,10 +652,8 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
 
     /**
      * Adds definitions of CRS (or other geodetic objects) from Well-Known Texts. Blank strings
are ignored.
-     * Each non-blank {@link String} shall contain the complete definition of at least one
geodetic object.
-     * More than one geodetic object can appear in the same {@link String} if they are separated
by spaces
-     * or line separators. However the same geodetic object can not have its definition splitted
in two or
-     * more {@link String}s.
+     * Each non-blank {@link String} shall contain the complete definition of exactly one
geodetic object.
+     * A geodetic object can not have its definition splitted in two or more {@link String}s.
      *
      * <p>The key associated to each object is given by the {@code ID[…]} or {@code
AUTHORITY[…]} element,
      * which is typically the last element of a WKT string and is mandatory. WKT strings
can contain line
@@ -682,9 +679,10 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
             try {
                 while (it.hasNext()) {
                     final String wkt = it.next();
-                    final int length = CharSequences.skipTrailingWhitespaces(wkt, 0, wkt.length());
-                    while (pos.getIndex() < length) {
-                        addDefinition(parser.textToTree(wkt, pos));
+                    addDefinition(parser.textToTree(wkt, pos, null));
+                    final int end = pos.getIndex();
+                    if (end < CharSequences.skipTrailingWhitespaces(wkt, 0, wkt.length()))
{
+                        throw new FactoryDataException(unexpectedText(lineNumber, wkt, end));
                     }
                     pos.setIndex(0);
                     lineNumber++;
@@ -702,6 +700,18 @@ public class WKTDictionary extends GeodeticAuthorityFactory {
     }
 
     /**
+     * Produces an error message for unexpected characters at the end of WKT string.
+     *
+     * @param  lineNumber  line where the error occurred.
+     * @param  wkt         the WKT being parsed.
+     * @param  end         end of WKT parsing.
+     * @return message to give to exception constructor.
+     */
+    private String unexpectedText(final int lineNumber, final String wkt, final int end)
{
+        return resources().getString(Resources.Keys.UnexpectedTextAtLine_2, lineNumber, CharSequences.token(wkt,
end));
+    }
+
+    /**
      * Convenience methods for resources in the language used for error messages.
      */
     private Resources resources() {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
index 95a78af..a8d5f7c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
@@ -23,6 +23,8 @@ import java.util.Set;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.TreeMap;
+import java.util.List;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.io.IOException;
 import java.text.Format;
@@ -761,7 +763,7 @@ public class WKTFormat extends CompoundFormat<Object> {
             throw new IllegalArgumentException(errors().getString(Errors.Keys.NotAUnicodeIdentifier_1,
name));
         }
         final ParsePosition pos = new ParsePosition(0);
-        final StoredTree definition = textToTree(wkt, pos);
+        final StoredTree definition = textToTree(wkt, pos, name);
         final int length = wkt.length();
         final int index = CharSequences.skipLeadingWhitespaces(wkt, pos.getIndex(), length);
         if (index < length) {
@@ -790,23 +792,45 @@ public class WKTFormat extends CompoundFormat<Object> {
      * This method should be invoked only for WKT trees to be stored for a long time.
      * It should not be invoked for immediate {@link IdentifiedObject} parsing.
      *
-     * @param  wkt  the Well Know Text (WKT) fragment to parse.
-     * @param  pos  index of the first character to parse (on input) or after last parsed
character (on output).
+     * <p>If {@code aliasKey} is non-null, this method may return a multi-roots tree.
+     * See {@link StoredTree#root} for a discussion. Note that in both cases (single
+     * root or multi-roots), we may have some unparsed characters at the end of the string.</p>
+     *
+     * @param  wkt       the Well Know Text (WKT) fragment to parse.
+     * @param  pos       index of the first character to parse (on input) or after last parsed
character (on output).
+     * @param  aliasKey  key of the alias, or {@code null} if this method is not invoked
+     *                   for defining a {@linkplain #addFragment(String, String) fragment}.
      * @return root of the tree of elements.
      */
-    final StoredTree textToTree(final String wkt, final ParsePosition pos) throws ParseException
{
-        final AbstractParser parser = parser(true);
-        Element result = null;
+    final StoredTree textToTree(final String wkt, final ParsePosition pos, final String aliasKey)
throws ParseException {
+        final AbstractParser parser  = parser(true);
+        final List<Element>  results = new ArrayList<>(4);
         warnings = null;
         try {
-            result = parser.textToTree(wkt, pos);
+            for (;;) {
+                results.add(parser.textToTree(wkt, pos));
+                if (aliasKey == null) break;
+                /*
+                 * If we find a separator (usually a coma), search for another element. Contrarily
to equivalent
+                 * loop in `Element(AbstractParser, …)` constructor, we do not parse number
or dates because we
+                 * do not have a way as reliable as above-cited constructor to differentiate
the kind of value.
+                 */
+                final int p = CharSequences.skipLeadingWhitespaces(wkt, pos.getIndex(), wkt.length());
+                final String separator = parser.symbols.trimmedSeparator();
+                if (!wkt.startsWith(separator, p)) break;
+                pos.setIndex(p + separator.length());
+            }
         } finally {
-            warnings = parser.getAndClearWarnings(result);
+            warnings = parser.getAndClearWarnings(results.isEmpty() ? null : results.get(0));
         }
         if (sharedValues == null) {
             sharedValues = new HashMap<>();
         }
-        return new StoredTree(result, sharedValues);
+        if (results.size() == 1) {
+            return new StoredTree(results.get(0), sharedValues);      // Standard case.
+        } else {
+            return new StoredTree(results, sharedValues);             // Anonymous wrapper
around multi-roots.
+        }
     }
 
     /**
@@ -824,7 +848,7 @@ public class WKTFormat extends CompoundFormat<Object> {
      * In case of error, {@link ParseException#getErrorOffset()} gives the position of the
first illegal character.
      *
      * @param  wkt  the character sequence for the object to parse.
-     * @param  pos  the position where to start the parsing.
+     * @param  pos  index of the first character to parse (on input) or after last parsed
character (on output).
      * @return the parsed object (never {@code null}).
      * @throws ParseException if an error occurred while parsing the WKT.
      */
@@ -844,8 +868,9 @@ public class WKTFormat extends CompoundFormat<Object> {
     }
 
     /**
-     * Parses a tree of {@link Element}s to produce a geodetic object. The {@code root} argument
-     * should be a value returned by {@link #textToTree(String, ParsePosition)}.
+     * Parses a tree of {@link Element}s to produce a geodetic object. The {@code tree} argument
+     * should be a value returned by {@link #textToTree(String, ParsePosition, String)}.
+     * This method is for {@link WKTDictionary#createObject(String)} usage.
      *
      * @param  tree  the tree of WKT elements.
      * @return the parsed object (never {@code null}).
@@ -854,7 +879,9 @@ public class WKTFormat extends CompoundFormat<Object> {
     final Object buildFromTree(StoredTree tree) throws ParseException {
         clear();
         final AbstractParser parser = parser(false);
-        final Element root = new Element(tree.toElement(parser, 0));
+        final SingletonElement singleton = new SingletonElement();
+        tree.toElements(parser, singleton, 0);
+        final Element root = new Element(singleton.value);
         Object result = null;
         try {
             result = parser.buildFromTree(root);
diff --git a/core/sis-referencing/src/test/resources/org/apache/sis/io/wkt/ExtraCRS.txt b/core/sis-referencing/src/test/resources/org/apache/sis/io/wkt/ExtraCRS.txt
index 816038e..8240374 100644
--- a/core/sis-referencing/src/test/resources/org/apache/sis/io/wkt/ExtraCRS.txt
+++ b/core/sis-referencing/src/test/resources/org/apache/sis/io/wkt/ExtraCRS.txt
@@ -6,12 +6,17 @@
 #
 # Alias for WGS84 geographic CRS.
 #
-SET DEGREE = Unit["Degree", 0.0174532925199433]
 SET WGS84_BASE =
  BaseGeodCRS["GCS_WGS_1984",
   Datum["D_WGS_1984",
    Ellipsoid["WGS_1984", 6378137, 298.257223563]],
-  $DEGREE]
+  AngleUnit["Degree", 0.0174532925199433]]
+
+SET ELLIPSOIDAL_CS =
+ CS[ellipsoidal, 2],
+  Axis["Latitude", north],
+  Axis["Longitude", east],
+  AngleUnit["Degree", 0.0174532925199433]
 
 
 #
@@ -50,19 +55,13 @@ ProjectedCRS["South_Pole_Stereographic",
 GeodCRS["Anguilla 1957",
  Datum["Anguilla 1957",
   Ellipsoid["Clarke 1880", 6378249.145, 293.465]],
- CS[ellipsoidal, 2],
-  Axis["Latitude", north],
-  Axis["Longitude", east],
-  $DEGREE,
+ $ELLIPSOIDAL_CS,
  Id["TEST", 102021]]
 
 GeodCRS["Anguilla 1957 (bis)",
  Datum["Anguilla 1957",
   Ellipsoid["Clarke 1880", 6378249.145, 293.465]],
- CS[ellipsoidal, 2],
-  Axis["Latitude", north],
-  Axis["Longitude", east],
-  $DEGREE,
+ $ELLIPSOIDAL_CS,
  Id["TEST", 102021, "v2"]]
 
 
@@ -71,19 +70,12 @@ GeodCRS["Anguilla 1957 (bis)",
 # The erroneous element should be on the first line for avoiding platform-dependency
 # caused by various line separators ("\n" versus "\r\n").
 #
-
 SET BAD_DATUM = Datum["Erroneous", Ellipsoid["Missing axis length"]]
 
 GeodCRS["Error index 69 (on Ellipsoid)", Datum["Erroneous", Ellipsoid["Missing axis length"]],
- CS[ellipsoidal, 2],
-  Axis["Latitude", north],
-  Axis["Longitude", east],
-  $DEGREE,
+ $ELLIPSOIDAL_CS,
  Id["TEST", "E1"]]
 
 GeodCRS["Error index 42 (on $BAD_DATUM)", $BAD_DATUM,
- CS[ellipsoidal, 2],
-  Axis["Latitude", north],
-  Axis["Longitude", east],
-  $DEGREE,
+ $ELLIPSOIDAL_CS,
  Id["TEST", "E2"]]


Mime
View raw message