sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1689051 [1/2] - in /sis/branches/JDK6: ./ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadata/src/main/java/org/apache/sis/io/wkt/ core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ core/sis-referencing/...
Date Fri, 03 Jul 2015 17:50:05 GMT
Author: desruisseaux
Date: Fri Jul  3 17:50:05 2015
New Revision: 1689051

URL: http://svn.apache.org/r1689051
Log:
Merge WKT 2 consolidation from the JDK7 branch.

Modified:
    sis/branches/JDK6/   (props changed)
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java
    sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java
    sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
    sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
    sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
    sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java
    sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/util/StringBuildersTest.java

Propchange: sis/branches/JDK6/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Fri Jul  3 17:50:05 2015
@@ -1,4 +1,4 @@
 /sis/branches/Android:1430670-1480699
-/sis/branches/JDK7:1394913-1688858
-/sis/branches/JDK8:1584960-1688856
+/sis/branches/JDK7:1394913-1689050
+/sis/branches/JDK8:1584960-1689049
 /sis/trunk:1394364-1508466,1519089-1519674

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.metadata;
 
 import javax.measure.unit.Unit;
+import javax.measure.quantity.Angle;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
@@ -360,6 +361,36 @@ public final class AxisDirections extend
     private static final byte[] DISPLAY_ORDER = {1, 3, 0, 2};
 
     /**
+     * Returns the angular unit of the specified coordinate system.
+     * The preference will be given to the longitude axis, if found.
+     *
+     * @param  cs The coordinate system from which to get the angular unit, or {@code null}.
+     * @param  unit The default unit to return if no angular unit is found.
+     * @return The angular unit, of {@code null} if no angular unit was found.
+     *
+     * @since 0.6
+     *
+     * @see org.apache.sis.internal.referencing.ReferencingUtilities#getUnit(CoordinateSystem)
+     */
+    public static Unit<Angle> getAngularUnit(final CoordinateSystem cs, Unit<Angle> unit) {
+        if (cs != null) {
+            for (int i = cs.getDimension(); --i>=0;) {
+                final CoordinateSystemAxis axis = cs.getAxis(i);
+                if (axis != null) {  // Paranoiac check.
+                    final Unit<?> candidate = axis.getUnit();
+                    if (Units.isAngular(candidate)) {
+                        unit = candidate.asType(Angle.class);
+                        if (AxisDirection.EAST.equals(absolute(axis.getDirection()))) {
+                            break; // Found the longitude axis.
+                        }
+                    }
+                }
+            }
+        }
+        return unit;
+    }
+
+    /**
      * Finds the dimension of an axis having the given direction or its opposite.
      * If more than one axis has the given direction, only the first occurrence is returned.
      * If both the given direction and its opposite exist, then the dimension for the given

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -20,7 +20,6 @@ import java.io.Console;
 import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicReference;
 import javax.xml.bind.annotation.XmlTransient;
-import org.opengis.util.InternationalString;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.util.X364;
@@ -181,9 +180,14 @@ public abstract class FormattableObject
         try {
             formatter.append(this);
             if (strict) {
-                final InternationalString message = formatter.getErrorMessage();
-                if (message != null) {
-                    throw new UnformattableObjectException(message.toString(), formatter.getErrorCause());
+                /*
+                 * If a warning occurred, consider the object as non-formattable.
+                 * We take the last message since it is more likely to be about the enclosing element.
+                 */
+                final Warnings warnings = formatter.getWarnings();
+                if (warnings != null) {
+                    final int n = warnings.getNumMessages() - 1;
+                    throw new UnformattableObjectException(warnings.getMessage(n), warnings.getException(n));
                 }
             }
             wkt = formatter.toWKT();

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -60,6 +60,7 @@ import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.Localized;
+import org.apache.sis.util.Characters;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
@@ -297,20 +298,12 @@ public class Formatter implements Locali
     private boolean highlightError;
 
     /**
-     * Non-null if the WKT is invalid. If non-null, then this field contains a keyword that identify the
-     * problematic part.
+     * The warnings that occurred during WKT formatting, or {@code null} if none.
      *
      * @see #isInvalidWKT()
-     * @see #getErrorMessage()
+     * @see #getWarnings()
      */
-    private String invalidElement;
-
-    /**
-     * Error that occurred during WKT formatting, or {@code null} if none.
-     *
-     * @see #getErrorCause()
-     */
-    private Exception errorCause;
+    private Warnings warnings;
 
     /**
      * Creates a new formatter instance with the default configuration.
@@ -626,11 +619,10 @@ public class Formatter implements Locali
         if (keyword == null) {
             if (info != null) {
                 setInvalidWKT(info, null);
-                keyword = getName(info.getClass());
             } else {
                 setInvalidWKT(object.getClass(), null);
-                keyword = invalidElement;
             }
+            keyword = getName(object.getClass());
         } else if (toUpperCase != 0) {
             final Locale locale = symbols.getLocale();
             keyword = (toUpperCase >= 0) ? keyword.toUpperCase(locale) : keyword.toLowerCase(locale);
@@ -976,14 +968,34 @@ public class Formatter implements Locali
      * that character will be doubled (WKT 2) or deleted (WKT 1). We check for the closing quote only because
      * it is the character that the parser will look for determining the text end.
      */
-    private void quote(final String text, final ElementKind type) {
+    private void quote(String text, final ElementKind type) {
         setColor(type);
         final int base = buffer.appendCodePoint(symbols.getOpeningQuote(0)).length();
-        if (type == ElementKind.REMARKS) {
-            buffer.append(text);
-        } else {
-            buffer.append(transliterator.filter(text));
+        if (type != ElementKind.REMARKS) {
+            text = transliterator.filter(text);
+            int startAt = 0; // Index of the last space character.
+            final int length = text.length();
+            for (int i = 0; i < length;) {
+                int c = text.codePointAt(i);
+                int n = Character.charCount(c);
+                if (!Characters.isValidWKT(c)) {
+                    final String illegal = text.substring(i, i+n);
+                    while ((i += n) < length) {
+                        c = text.codePointAt(i);
+                        n = Character.charCount(c);
+                        if (c == ' ' || c == '_') break;
+                    }
+                    warnings().add(Errors.formatInternational(Errors.Keys.IllegalCharacterForFormat_3,
+                            "Well-Known Text", text.substring(startAt, i), illegal), null, null);
+                    break;
+                }
+                i += n;
+                if (c == ' ' || c == '_') {
+                    startAt = i;
+                }
+            }
         }
+        buffer.append(text);
         closeQuote(base);
         resetColor();
     }
@@ -1451,7 +1463,7 @@ public class Formatter implements Locali
      * @return {@code true} if the WKT is invalid.
      */
     public boolean isInvalidWKT() {
-        return (invalidElement != null) || (buffer != null && buffer.length() == 0);
+        return (warnings != null) || (buffer != null && buffer.length() == 0);
         /*
          * Note: we really use a "and" condition (not an other "or") for the buffer test because
          *       the buffer is reset to 'null' by WKTFormat after a successfull formatting.
@@ -1459,15 +1471,21 @@ public class Formatter implements Locali
     }
 
     /**
+     * Returns the object where to store warnings.
+     */
+    private Warnings warnings() {
+        if (warnings == null) {
+            warnings = new Warnings(locale, (byte) 0, Collections.<String, List<String>>emptyMap());
+        }
+        return warnings;
+    }
+
+    /**
      * Marks the current WKT representation of the given object as not strictly compliant with the WKT specification.
      * This method can be invoked by implementations of {@link FormattableObject#formatTo(Formatter)} when the object
      * to format is more complex than what the WKT specification allows.
      * Applications can test {@link #isInvalidWKT()} later for checking WKT validity.
      *
-     * <p>If any {@code setInvalidWKT(…)} method is invoked more than once during formatting,
-     * then only information about the last failure will be retained. The reason is that the
-     * last failure is typically the enclosing element.</p>
-     *
      * @param unformattable The object that can not be formatted,
      * @param cause The cause for the failure to format, or {@code null} if the cause is not an exception.
      */
@@ -1478,9 +1496,7 @@ public class Formatter implements Locali
         if (id == null || (name = id.getCode()) == null) {
             name = getName(unformattable.getClass());
         }
-        invalidElement = name;
-        errorCause     = cause;
-        highlightError = true;
+        setInvalidWKT(name, cause);
     }
 
     /**
@@ -1488,18 +1504,22 @@ public class Formatter implements Locali
      * This method can be used as an alternative to {@link #setInvalidWKT(IdentifiedObject, Exception)} when the
      * problematic object is not an instance of {@code IdentifiedObject}.
      *
-     * <p>If any {@code setInvalidWKT(…)} method is invoked more than once during formatting,
-     * then only information about the first failure will be retained.</p>
-     *
      * @param unformattable The class of the object that can not be formatted,
      * @param cause The cause for the failure to format, or {@code null} if the cause is not an exception.
      */
     public void setInvalidWKT(final Class<?> unformattable, final Exception cause) {
         ArgumentChecks.ensureNonNull("unformattable", unformattable);
-        if (invalidElement == null) {
-            invalidElement = getName(unformattable);
-            errorCause     = cause;
-        }
+        setInvalidWKT(getName(unformattable), cause);
+    }
+
+    /**
+     * Implementation of public {@code setInvalidWKT(…)} methods.
+     *
+     * <div class="note"><b>Note:</b> the message is stored as an {@link InternationalString}
+     * in order to defer the actual message formatting until needed.</div>
+     */
+    private void setInvalidWKT(final String invalidElement, final Exception cause) {
+        warnings().add(Errors.formatInternational(Errors.Keys.CanNotRepresentInFormat_2, "WKT", invalidElement), cause, null);
         highlightError = true;
     }
 
@@ -1520,24 +1540,10 @@ public class Formatter implements Locali
     }
 
     /**
-     * Returns the error message {@link #isInvalidWKT()} is set, or {@code null} otherwise.
-     * If non-null, a cause may be available in the {@link #getErrorCause()} method.
-     *
-     * <div class="note"><b>Note:</b> the message is returned as an {@link InternationalString}
-     * in order to defer the actual message formatting until needed.</div>
-     */
-    final InternationalString getErrorMessage() {
-        if (!isInvalidWKT()) {
-            return null;
-        }
-        return Errors.formatInternational(Errors.Keys.CanNotRepresentInFormat_2, "WKT", invalidElement);
-    }
-
-    /**
-     * Returns the cause of the error, or {@code null} if the cause is not an exception.
+     * Returns the warnings, or {@code null} if none.
      */
-    final Exception getErrorCause() {
-        return errorCause;
+    final Warnings getWarnings() {
+        return warnings;
     }
 
     /**
@@ -1588,7 +1594,6 @@ public class Formatter implements Locali
         requestNewLine    = false;
         isComplement      = false;
         highlightError    = false;
-        invalidElement    = null;
-        errorCause        = null;
+        warnings          = null;
     }
 }

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -356,6 +356,27 @@ final class GeodeticObjectParser extends
     }
 
     /**
+     * Parses a coordinate reference system wrapped in an element of the given name.
+     *
+     * @param  parent   The parent element containing the CRS to parse.
+     * @param  mode     {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
+     * @param  keyword  "SourceCRS", "TargetCRS" or "InterpolationCRS".
+     * @return The coordinate reference system, or {@code null} if none.
+     * @throws ParseException if the CRS can not be parsed.
+     */
+    private CoordinateReferenceSystem parseCoordinateReferenceSystem(final Element parent, final int mode,
+            final String keyword) throws ParseException
+    {
+        final Element element = parent.pullElement(mode, keyword);
+        if (element == null) {
+            return null;
+        }
+        final CoordinateReferenceSystem crs = parseCoordinateReferenceSystem(element, true);
+        element.close(ignoredElements);
+        return crs;
+    }
+
+    /**
      * Returns the value associated to {@link IdentifiedObject#IDENTIFIERS_KEY} as an {@code Identifier} object.
      * This method shall accept all value types that {@link #parseMetadataAndClose(Element, Object)} may store.
      *
@@ -551,20 +572,6 @@ final class GeodeticObjectParser extends
     }
 
     /**
-     * Returns the given unit as a linear unit, or {@code null} if it is not linear.
-     */
-    private static Unit<Length> toLinear(final Unit<?> unit) {
-        return Units.isLinear(unit) ? unit.asType(Length.class) : null;
-    }
-
-    /**
-     * Returns the given unit as an angular unit, or {@code null} if it is not angular.
-     */
-    private static Unit<Angle> toAngular(final Unit<?> unit) {
-        return Units.isAngular(unit) ? unit.asType(Angle.class) : null;
-    }
-
-    /**
      * Parses an optional {@code "UNIT"} element of a known dimension.
      * This element has the following pattern:
      *
@@ -641,7 +648,7 @@ final class GeodeticObjectParser extends
      * @param  dimension   The minimal number of dimensions. Can be 1 if unknown.
      * @param  isWKT1      {@code true} if the parent element is an element from the WKT 1 standard.
      * @param  defaultUnit The contextual unit (usually {@code SI.METRE} or {@code SI.RADIAN}), or {@code null} if unknown.
-     * @param  datum       The datum of the enclosing CRS.
+     * @param  datum       The datum of the enclosing CRS, or {@code null} if unknown.
      * @return The {@code "CS"}, {@code "UNIT"} and/or {@code "AXIS"} elements as a Coordinate System, or {@code null}.
      * @throws ParseException if an element can not be parsed.
      * @throws FactoryException if the factory can not create the coordinate system.
@@ -718,11 +725,13 @@ final class GeodeticObjectParser extends
             if (type == null) {
                 throw parent.missingComponent(WKTKeywords.Axis);
             }
-            String nx = null, x = null;     // Easting or Longitude axis name and abbreviation.
-            String ny = null, y = null;     // Northing or latitude axis name and abbreviation.
-            String nz = null, z = null;     // Depth, height or time axis name and abbreviation.
-            AxisDirection direction = null; // Depth, height or time axis direction.
-            Unit<?> unit = defaultUnit;     // Depth, height or time axis unit.
+            String nx = null, x = null;             // Easting or Longitude axis name and abbreviation.
+            String ny = null, y = null;             // Northing or latitude axis name and abbreviation.
+            String nz = null, z = null;             // Depth, height or time axis name and abbreviation.
+            AxisDirection dx = AxisDirection.EAST;
+            AxisDirection dy = AxisDirection.NORTH;
+            AxisDirection direction = null;         // Depth, height or time axis direction.
+            Unit<?> unit = defaultUnit;             // Depth, height or time axis unit.
             /*switch (type)*/ {
                 /*
                  * Cartesian — we can create axes only for geodetic datum, in which case the axes are for
@@ -748,14 +757,22 @@ final class GeodeticObjectParser extends
                 }
                 /*
                  * Ellipsoidal — can be two- or three- dimensional, in which case the height can
-                 * only be ellipsoidal height.
+                 * only be ellipsoidal height. The default axis order depends on the WKT version:
+                 *
+                 *   - WKT 1 said explicitely that the default order is (longitude, latitude).
+                 *   - WKT 2 has no default, and allows only (latitude, longitude) order.
                  */
                 else if (type.equals(WKTKeywords.ellipsoidal)) {
                     if (defaultUnit == null) {
                         throw parent.missingComponent(WKTKeywords.AngleUnit);
                     }
-                    nx = AxisNames.GEODETIC_LONGITUDE; x = "λ";
-                    ny = AxisNames.GEODETIC_LATITUDE;  y = "φ";
+                    if (isWKT1) {
+                        nx = AxisNames.GEODETIC_LONGITUDE; x = "λ";
+                        ny = AxisNames.GEODETIC_LATITUDE;  y = "φ";
+                    } else {
+                        nx = AxisNames.GEODETIC_LATITUDE;  x = "φ"; dx = AxisDirection.NORTH;
+                        ny = AxisNames.GEODETIC_LONGITUDE; y = "λ"; dy = AxisDirection.EAST;
+                    }
                     if (dimension >= 3) {
                         direction = AxisDirection.UP;
                         z    = "h";
@@ -771,21 +788,23 @@ final class GeodeticObjectParser extends
                     if (defaultUnit == null) {
                         throw parent.missingComponent(WKTKeywords.Unit);
                     }
+                    z         = "h";
+                    nz        = "Height";
                     direction = AxisDirection.UP;
-                    final VerticalDatumType vt = ((VerticalDatum) datum).getVerticalDatumType();
-                    if (VerticalDatumType.GEOIDAL.equals(vt)) {
-                        nz = AxisNames.GRAVITY_RELATED_HEIGHT;
-                        z  = "H";
-                    } else if (VerticalDatumType.DEPTH.equals(vt)) {
-                        direction = AxisDirection.DOWN;
-                        nz = AxisNames.DEPTH;
-                        z  = "D";
-                    } else if (VerticalDatumTypes.ELLIPSOIDAL.equals(vt)) {
-                        nz = AxisNames.ELLIPSOIDAL_HEIGHT;  // Not allowed by ISO 19111 as a standalone axis, but SIS is
-                        z  = "h";                           // tolerant to this case since it is sometime hard to avoid.
-                    } else {
-                        nz = "Height";
-                        z  = "h";
+                    if (datum instanceof VerticalDatum) {
+                        final VerticalDatumType vt = ((VerticalDatum) datum).getVerticalDatumType();
+                        if (VerticalDatumType.GEOIDAL.equals(vt)) {
+                            nz = AxisNames.GRAVITY_RELATED_HEIGHT;
+                            z  = "H";
+                        } else if (VerticalDatumType.DEPTH.equals(vt)) {
+                            direction = AxisDirection.DOWN;
+                            nz = AxisNames.DEPTH;
+                            z  = "D";
+                        } else if (VerticalDatumTypes.ELLIPSOIDAL.equals(vt)) {
+                            // Not allowed by ISO 19111 as a standalone axis, but SIS is
+                            // tolerant to this case since it is sometime hard to avoid.
+                            nz = AxisNames.ELLIPSOIDAL_HEIGHT;
+                        }
                     }
                 }
                 /*
@@ -808,8 +827,8 @@ final class GeodeticObjectParser extends
             }
             int i = 0;
             axes = new CoordinateSystemAxis[dimension];
-            if (x != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nx), x, AxisDirection.EAST,  defaultUnit);
-            if (y != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, ny), y, AxisDirection.NORTH, defaultUnit);
+            if (x != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nx), x, dx,  defaultUnit);
+            if (y != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, ny), y, dy, defaultUnit);
             if (z != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nz), z, direction, unit);
             // Not a problem if the array does not have the expected length for the CS type. This will be verified below in this method.
         }
@@ -1157,6 +1176,43 @@ final class GeodeticObjectParser extends
     }
 
     /**
+     * Parses a {@code "Method"} (WKT 2) element, without the parameters.
+     *
+     * @param  parent   The parent element.
+     * @param  keywords The element keywords.
+     * @return The operation method.
+     * @throws ParseException if the {@code "Method"} element can not be parsed.
+     */
+    private OperationMethod parseMethod(final Element parent, final String... keywords) throws ParseException {
+        final Element element    = parent.pullElement(MANDATORY, keywords);
+        final String  name       = element.pullString("method");
+        Map<String,?> properties = parseMetadataAndClose(element, name, null);
+        final Identifier id      = toIdentifier(properties.remove(IdentifiedObject.IDENTIFIERS_KEY));  // See NOTE 2 in parseDerivingConversion.
+        /*
+         * The map projection method may be specified by an EPSG identifier (or any other authority),
+         * which is preferred to the method name since the later is potentially ambiguous. However not
+         * all CoordinateOperationFactory may accept identifier as an argument to 'getOperationMethod'.
+         * So if an identifier is present, we will try to use it but fallback on the name if we can
+         * not use the identifier.
+         */
+        FactoryException suppressed = null;
+        if (id != null) try {
+            // CodeSpace is a mandatory attribute in ID[…] elements, so we do not test for null values.
+            return opFactory.getOperationMethod(id.getCodeSpace() + DefaultNameSpace.DEFAULT_SEPARATOR + id.getCode());
+        } catch (FactoryException e) {
+            suppressed = e;
+        }
+        try {
+            return opFactory.getOperationMethod(name);
+        } catch (FactoryException e) {
+            if (suppressed != null) {
+//              e.addSuppressed(suppressed);    // Not available on JDK6.
+            }
+            throw element.parseFailed(e);
+        }
+    }
+
+    /**
      * Parses a {@code "Method"} (WKT 2) element, followed by parameter values. The syntax is given by
      * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#62">WKT 2 specification §9.3</a>.
      *
@@ -1170,16 +1226,16 @@ final class GeodeticObjectParser extends
      * element which is itself inside the {@code ProjectedCRS} element. This is different than WKT 1, which
      * puts this element right into the the {@code ProjectedCRS} element without {@code Conversion} wrapper.
      *
-     * @param  mode        {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
-     * @param  parent      The parent element.
-     * @param  wrapper     "Conversion" or "DerivingConversion" wrapper name, or null if parsing a WKT 1.
-     * @param  linearUnit  The linear unit of the parent {@code ProjectedCRS} element, or {@code null}.
-     * @param  angularUnit The angular unit of the sibling {@code GeographicCRS} element, or {@code null}.
+     * @param  mode               {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
+     * @param  parent             The parent element.
+     * @param  wrapper            "Conversion" or "DerivingConversion" wrapper name, or null if parsing a WKT 1.
+     * @param  defaultUnit        The unit (usually linear) of the parent element, or {@code null}.
+     * @param  defaultAngularUnit The angular unit of the sibling {@code GeographicCRS} element, or {@code null}.
      * @return The {@code "Method"} element and its parameters as a defining conversion.
      * @throws ParseException if the {@code "Method"} element can not be parsed.
      */
     private Conversion parseDerivingConversion(final int mode, Element parent, final String wrapper,
-            final Unit<Length> linearUnit, final Unit<Angle> angularUnit) throws ParseException
+            final Unit<?> defaultUnit, final Unit<Angle> defaultAngularUnit) throws ParseException
     {
         final String name;
         if (wrapper == null) {
@@ -1195,25 +1251,8 @@ final class GeodeticObjectParser extends
             }
             name = parent.pullString("name");
         }
-        final Element element    = parent.pullElement(MANDATORY, WKTKeywords.Method, WKTKeywords.Projection);
-        final String  methodName = element.pullString("method");
-        Map<String,?> properties = parseMetadataAndClose(element, methodName, null);
-        final Identifier id      = toIdentifier(properties.remove(IdentifiedObject.IDENTIFIERS_KEY)); // See NOTE 2.
-        /*
-         * The map projection method may be specified by an EPSG identifier (or any other authority),
-         * which is preferred to the method name since the later is potentially ambiguous. However not
-         * all CoordinateOperationFactory may accept identifier as an argument to 'getOperationMethod'.
-         * So if an identifier is present, we will try to use it but fallback on the name if we can
-         * not use the identifier.
-         */
-        OperationMethod  method     = null;
-        FactoryException suppressed = null;
-        if (id != null) try {
-            // CodeSpace is a mandatory attribute in ID[…] elements, so we do not test for null values.
-            method = opFactory.getOperationMethod(id.getCodeSpace() + DefaultNameSpace.DEFAULT_SEPARATOR + id.getCode());
-        } catch (FactoryException e) {
-            suppressed = e;
-        }
+        OperationMethod method = parseMethod(parent, WKTKeywords.Method, WKTKeywords.Projection);
+        Map<String,?> properties = this.properties;  // Same properties then OperationMethod, with ID removed.
         /*
          * Set the list of parameters.
          *
@@ -1224,32 +1263,26 @@ final class GeodeticObjectParser extends
          *         but we shall not inherit the OperationMethod identifier. This is the reason why we invoked
          *         properties.remove(IdentifiedObject.IDENTIFIERS_KEY)) above.
          */
+        final ParameterValueGroup parameters = method.getParameters().createValue();
+        parseParameters(parent, parameters, defaultUnit, defaultAngularUnit);
+        if (wrapper != null) {
+            properties = parseMetadataAndClose(parent, name, method);
+            /*
+             * DEPARTURE FROM ISO 19162: the specification in §9.3.2 said:
+             *
+             *     "If an identifier is provided as an attribute within the <map projection conversion> object,
+             *     because it is expected to describe a complete collection of zone name, method, parameters and
+             *     parameter values, it shall override any identifiers given within the map projection method and
+             *     map projection parameter objects."
+             *
+             * However this would require this GeodeticObjectParser to hold a CoordinateOperationAuthorityFactory,
+             * which we do not yet implement. See https://issues.apache.org/jira/browse/SIS-210
+             */
+        }
         try {
-            if (method == null) {
-                method = opFactory.getOperationMethod(methodName);
-            }
-            final ParameterValueGroup parameters = method.getParameters().createValue();
-            parseParameters(parent, parameters, linearUnit, angularUnit);
-            if (wrapper != null) {
-                properties = parseMetadataAndClose(parent, name, method);
-                /*
-                 * DEPARTURE FROM ISO 19162: the specification in §9.3.2 said:
-                 *
-                 *     "If an identifier is provided as an attribute within the <map projection conversion> object,
-                 *     because it is expected to describe a complete collection of zone name, method, parameters and
-                 *     parameter values, it shall override any identifiers given within the map projection method and
-                 *     map projection parameter objects."
-                 *
-                 * However this would require this GeodeticObjectParser to hold a CoordinateOperationAuthorityFactory,
-                 * which we do not yet implement. See https://issues.apache.org/jira/browse/SIS-163
-                 */
-            }
             return opFactory.createDefiningConversion(properties, method, parameters);
         } catch (FactoryException exception) {
-            if (suppressed != null) {
-//              exception.addSuppressed(suppressed);    // Not available on JDK6.
-            }
-            throw element.parseFailed(exception);
+            throw parent.parseFailed(exception);
         }
     }
 
@@ -1461,7 +1494,17 @@ final class GeodeticObjectParser extends
         SingleCRS        baseCRS  = null;
         Conversion       fromBase = null;
         if (!isWKT1 && !isBaseCRS) {
-            fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, null, null);
+            /*
+             * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said
+             * what to do if they are missing.  In this code, we default to the contextual units in the same way than
+             * what we do for ProjectedCRS parameters, in the hope to be consistent.
+             *
+             * An alternative would be to specify null units, in which case MathTransformParser.parseParameters(…)
+             * defaults to the units specified in the parameter descriptor. But this would make the CRS parser more
+             * implementation-dependent, because the parameter descriptors are provided by the MathTransformFactory
+             * instead than inferred from the WKT.
+             */
+            fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, unit, null);
             if (fromBase != null) {
                 /*
                  * The order of base types below is arbitrary. But no matter their type,
@@ -1506,9 +1549,9 @@ final class GeodeticObjectParser extends
         if (element == null) {
             return null;
         }
-        final String     name   = element.pullString("name");
-        final ImageDatum datum  = parseImageDatum(MANDATORY, element);
-        final Unit<?>    unit   = parseUnit(element);
+        final String     name  = element.pullString("name");
+        final ImageDatum datum = parseImageDatum(MANDATORY, element);
+        final Unit<?>    unit  = parseUnit(element);
         final CoordinateSystem cs;
         try {
             cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, false, unit, datum);
@@ -1562,40 +1605,66 @@ final class GeodeticObjectParser extends
         if (element == null) {
             return null;
         }
-        final boolean     isWKT1;
-        final Unit<Angle> angularUnit;
-              Unit<?>     defaultUnit;
+        final boolean isWKT1;
+        Unit<?>     csUnit;
+        Unit<Angle> angularUnit;
         switch (element.getKeywordIndex()) {
-            default: {      // WKT2 element.
-                isWKT1      = false;
-                defaultUnit = parseUnit(element);
-                angularUnit = Units.isAngular(defaultUnit) ? defaultUnit.asType(Angle.class) : NonSI.DEGREE_ANGLE;
-                if (defaultUnit == null) {
-                    /*
-                     * A UNIT[…] is mandatory either in the CoordinateSystem as a whole (defaultUnit != null),
-                     * or inside each AXIS[…] component (defaultUnit == null). An exception to this rule is when
-                     * parsing a BaseGeodCRS inside a ProjectedCRS or DerivedCRS, in which case axes are omitted.
-                     * We recognize those cases by a non-null 'csType' given in argument to this method.
-                     */
-                    if (WKTKeywords.ellipsoidal.equals(csType)) {
-                        defaultUnit = NonSI.DEGREE_ANGLE;   // For BaseGeodCRS in ProjectedCRS.
+            default: {
+                /*
+                 * WKT 2 "GeodeticCRS" element.
+                 * The specification in §8.2.2 (ii) said:
+                 *
+                 *     "If the subtype of the geodetic CRS to which the prime meridian is an attribute
+                 *     is geographic, the prime meridian’s <irm longitude> value shall be given in the
+                 *     same angular units as those for the horizontal axes of the geographic CRS;
+                 *     if the geodetic CRS subtype is geocentric the prime meridian’s <irm longitude>
+                 *     value shall be given in degrees."
+                 *
+                 * An apparent ambiguity exists for Geocentric CRS using a Spherical CS instead than the more
+                 * usual Cartesian CS: despite using angular units, we should not use the result of parseUnit
+                 * for those CRS. However this ambiguity should not happen in practice because such Spherical
+                 * CS have a third axis in metre.  Since the unit is not the same for all axes, csUnit should
+                 * be null if the WKT is well-formed.
+                 */
+                isWKT1 = false;
+                csUnit = parseUnit(element);
+                if (Units.isAngular(csUnit)) {
+                    angularUnit = csUnit.asType(Angle.class);
+                } else {
+                    angularUnit = NonSI.DEGREE_ANGLE;
+                    if (csUnit == null) {
+                        /*
+                         * A UNIT[…] is mandatory either in the CoordinateSystem as a whole (csUnit != null),
+                         * or inside each AXIS[…] component (csUnit == null). An exception to this rule is when
+                         * parsing a BaseGeodCRS inside a ProjectedCRS or DerivedCRS, in which case axes are omitted.
+                         * We recognize those cases by a non-null 'csType' given in argument to this method.
+                         */
+                        if (WKTKeywords.ellipsoidal.equals(csType)) {
+                            csUnit = NonSI.DEGREE_ANGLE;   // For BaseGeodCRS in ProjectedCRS.
+                        }
                     }
                 }
                 break;
             }
-            case 1: {       // WKT1 "GeogCS" element.
+            case 1: {
+                /*
+                 * WKT 1 "GeogCS" (Geographic) element.
+                 */
                 isWKT1      = true;
                 csType      = WKTKeywords.ellipsoidal;
                 angularUnit = parseScaledUnit(element, WKTKeywords.AngleUnit, SI.RADIAN);
-                defaultUnit = angularUnit;
+                csUnit      = angularUnit;
                 dimension   = 2;
                 break;
             }
-            case 3: {       // WKT1 "GeocCS" element.
+            case 3: {
+                /*
+                 * WKT 1 "GeocCS" (Geocentric) element.
+                 */
                 isWKT1      = true;
                 csType      = WKTKeywords.Cartesian;
                 angularUnit = NonSI.DEGREE_ANGLE;
-                defaultUnit = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+                csUnit      = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE);
                 dimension   = 3;
                 break;
             }
@@ -1605,31 +1674,50 @@ final class GeodeticObjectParser extends
          * A GeodeticCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind GeodeticCRS.
          * In the later case, the datum is null and we have instead DerivingConversion element from a BaseGeodCRS.
          */
-        GeodeticDatum datum    = null;
-        SingleCRS     baseCRS  = null;
-        Conversion    fromBase = null;
+        SingleCRS  baseCRS  = null;
+        Conversion fromBase = null;
         if (!isWKT1 && csType == null) {
-            fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, toLinear(defaultUnit), angularUnit);
+            /*
+             * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said
+             * what to do if they are missing.  In this code, we default to the contextual units in the same way than
+             * what we do for ProjectedCRS parameters, in the hope to be consistent.
+             *
+             * An alternative would be to specify null units, in which case MathTransformParser.parseParameters(…)
+             * defaults to the units specified in the parameter descriptor. But this would make the CRS parser more
+             * implementation-dependent, because the parameter descriptors are provided by the MathTransformFactory
+             * instead than inferred from the WKT.
+             */
+            fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, csUnit, angularUnit);
             if (fromBase != null) {
                 baseCRS = parseGeodeticCRS(MANDATORY, element, getSourceDimensions(fromBase.getMethod()), WKTKeywords.ellipsoidal);
             }
         }
-        if (baseCRS == null) {
-            // The most usual case.
-            final PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, angularUnit);
-            datum = parseDatum(MANDATORY, element, meridian);
-        }
         /*
          * At this point, we have either a non-null 'datum' or non-null 'baseCRS' + 'fromBase'.
          * The coordinate system is parsed in the same way for both cases, but the CRS is created differently.
          */
         final CoordinateSystem cs;
         try {
-            cs = parseCoordinateSystem(element, csType, dimension, isWKT1, defaultUnit, datum);
-            final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
+            cs = parseCoordinateSystem(element, csType, dimension, isWKT1, csUnit, null);
             if (baseCRS != null) {
+                final Map<String,?> properties = parseMetadataAndClose(element, name, null);
                 return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs);
             }
+            /*
+             * The specifiation in §8.2.2 (ii) said:
+             *
+             *     "(snip) the prime meridian’s <irm longitude> value shall be given in the
+             *     same angular units as those for the horizontal axes of the geographic CRS."
+             *
+             * This is a little bit different than using the 'angularUnit' variable directly,
+             * since the WKT could have overwritten the unit directly in the AXIS[…] element.
+             * So we re-fetch the angular unit. Normally, we will get the same value (unless
+             * the previous value was null).
+             */
+            angularUnit = AxisDirections.getAngularUnit(cs, angularUnit);
+            final PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, angularUnit);
+            final GeodeticDatum datum = parseDatum(MANDATORY, element, meridian);
+            final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
             if (cs instanceof EllipsoidalCS) {  // By far the most frequent case.
                 return crsFactory.createGeographicCRS(properties, datum, (EllipsoidalCS) cs);
             }
@@ -1684,7 +1772,17 @@ final class GeodeticObjectParser extends
         SingleCRS     baseCRS  = null;
         Conversion    fromBase = null;
         if (!isWKT1 && !isBaseCRS) {
-            fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, toLinear(unit), null);
+            /*
+             * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said
+             * what to do if they are missing.  In this code, we default to the contextual units in the same way than
+             * what we do for ProjectedCRS parameters, in the hope to be consistent.
+             *
+             * An alternative would be to specify null units, in which case MathTransformParser.parseParameters(…)
+             * defaults to the units specified in the parameter descriptor. But this would make the CRS parser more
+             * implementation-dependent, because the parameter descriptors are provided by the MathTransformFactory
+             * instead than inferred from the WKT.
+             */
+            fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, unit, null);
             if (fromBase != null) {
                 baseCRS = parseVerticalCRS(MANDATORY, element, true);
             }
@@ -1753,7 +1851,17 @@ final class GeodeticObjectParser extends
         SingleCRS     baseCRS  = null;
         Conversion    fromBase = null;
         if (!isBaseCRS) {
-            fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, null, null);
+            /*
+             * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said
+             * what to do if they are missing.  In this code, we default to the contextual units in the same way than
+             * what we do for ProjectedCRS parameters, in the hope to be consistent.
+             *
+             * An alternative would be to specify null units, in which case MathTransformParser.parseParameters(…)
+             * defaults to the units specified in the parameter descriptor. But this would make the CRS parser more
+             * implementation-dependent, because the parameter descriptors are provided by the MathTransformFactory
+             * instead than inferred from the WKT.
+             */
+            fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, unit, null);
             if (fromBase != null) {
                 baseCRS = parseTimeCRS(MANDATORY, element, true);
             }
@@ -1816,27 +1924,39 @@ final class GeodeticObjectParser extends
         /*
          * Parse the projection parameters. If a default linear unit is specified, it will apply to
          * all parameters that do not specify explicitely a LengthUnit. If no such crs-wide unit was
-         * specified, then the default will be the unit specified in the parameter descriptor.
+         * specified, then the default will be degrees.
+         *
+         * More specifically §9.3.4 in the specification said about the default units:
+         *
+         *    - lengths shall be given in the unit for the projected CRS axes.
+         *    - angles shall be given in the unit for the base geographic CRS of the projected CRS.
          */
-        Unit<Length> linearUnit = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE);
-        final boolean ignoreUnits = isWKT1 && usesCommonUnits;
+        Unit<Length> csUnit = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+        final Unit<Length> linearUnit;
+        final Unit<Angle>  angularUnit;
+        if (isWKT1 && usesCommonUnits) {
+            linearUnit  = SI.METRE;
+            angularUnit = NonSI.DEGREE_ANGLE;
+        } else {
+            linearUnit  = csUnit;
+            angularUnit = AxisDirections.getAngularUnit(geoCRS.getCoordinateSystem(), NonSI.DEGREE_ANGLE);
+        }
         final Conversion conversion = parseDerivingConversion(MANDATORY, element,
-                isWKT1 ? null : WKTKeywords.Conversion, ignoreUnits ? SI.METRE : linearUnit,
-                ignoreUnits ? NonSI.DEGREE_ANGLE : geoCRS.getCoordinateSystem().getAxis(0).getUnit().asType(Angle.class));
+                isWKT1 ? null : WKTKeywords.Conversion, linearUnit, angularUnit);
         /*
          * Parse the coordinate system. The linear unit must be specified somewhere, either explicitely in each axis
-         * or for the whole CRS with the above 'linearUnit' value. If 'linearUnit' is null, then an exception will be
-         * thrown with a message like "A LengthUnit component is missing in ProjectedCRS".
+         * or for the whole CRS with the above 'csUnit' value. If 'csUnit' is null, then an exception will be thrown
+         * with a message like "A LengthUnit component is missing in ProjectedCRS".
          *
          * However we make an exception if we are parsing a BaseProjCRS, since the coordinate system is unspecified
          * in the WKT of base CRS. In this case only, we will default to metre.
          */
-        if (linearUnit == null && isBaseCRS) {
-            linearUnit = SI.METRE;
+        if (csUnit == null && isBaseCRS) {
+            csUnit = SI.METRE;
         }
         final CoordinateSystem cs;
         try {
-            cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, isWKT1, linearUnit, geoCRS.getDatum());
+            cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, isWKT1, csUnit, geoCRS.getDatum());
             final Map<String,?> properties = parseMetadataAndClose(element, name, conversion);
             if (cs instanceof CartesianCS) {
                 return crsFactory.createProjectedCRS(properties, (GeographicCRS) geoCRS, conversion, (CartesianCS) cs);
@@ -1848,8 +1968,10 @@ final class GeodeticObjectParser extends
     }
 
     /**
-     * Parses a {@code "COMPD_CS"} element.
-     * This element has the following pattern:
+     * Parses a {@code "CompoundCRS"} element. The syntax is given by
+     * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#110">WKT 2 specification §16</a>.
+     *
+     * The legacy WKT 1 specification was:
      *
      * {@preformat text
      *     COMPD_CS["<name>", <head cs>, <tail cs> {,<authority>}]
@@ -1857,8 +1979,8 @@ final class GeodeticObjectParser extends
      *
      * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
-     * @return The {@code "COMPD_CS"} element as a {@link CompoundCRS} object.
-     * @throws ParseException if the {@code "COMPD_CS"} element can not be parsed.
+     * @return The {@code "CompoundCRS"} element as a {@link CompoundCRS} object.
+     * @throws ParseException if the {@code "CompoundCRS"} element can not be parsed.
      */
     private CompoundCRS parseCompoundCRS(final int mode, final Element parent) throws ParseException {
         final Element element = parent.pullElement(mode, WKTKeywords.CompoundCRS, WKTKeywords.Compd_CS);
@@ -1938,7 +2060,33 @@ final class GeodeticObjectParser extends
         }
     }
 
+    /**
+     * Parses a {@code "CoordinateOperation"} element. The syntax is given by
+     * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#113">WKT 2 specification §17</a>.
+     *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
+     * @param  parent The parent element.
+     * @return The {@code "CoordinateOperation"} element as a {@link CoordinateOperation} object.
+     * @throws ParseException if the {@code "CoordinateOperation"} element can not be parsed.
+     */
     private CoordinateOperation parseOperation(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.CoordinateOperation);
+        if (element == null) {
+            return null;
+        }
+        final String name = element.pullString("name");
+        final CoordinateReferenceSystem sourceCRS        = parseCoordinateReferenceSystem(element, MANDATORY, WKTKeywords.SourceCRS);
+        final CoordinateReferenceSystem targetCRS        = parseCoordinateReferenceSystem(element, MANDATORY, WKTKeywords.TargetCRS);
+        final CoordinateReferenceSystem interpolationCRS = parseCoordinateReferenceSystem(element, OPTIONAL,  WKTKeywords.InterpolationCRS);
+        final OperationMethod           method           = parseMethod(parent, WKTKeywords.Method);
+        final Element                   accuracy         = parent.pullElement(OPTIONAL, WKTKeywords.OperationAccuracy);
+        final Map<String,Object>        properties       = parseMetadataAndClose(parent, name, method);
+        final ParameterValueGroup       parameters       = method.getParameters().createValue();
+        parseParameters(parent, parameters, null, null);
+        if (accuracy != null) {
+            accuracy.pullDouble("accuracy");    // TODO: share the code from EPSG factory.
+            accuracy.close(ignoredElements);
+        }
         return null;    // Not yet implemented.
     }
 }

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -24,7 +24,6 @@ import javax.measure.unit.SI;
 import javax.measure.unit.Unit;
 import javax.measure.unit.UnitFormat;
 import javax.measure.quantity.Angle;
-import javax.measure.quantity.Length;
 import org.opengis.util.FactoryException;
 import org.opengis.util.NoSuchIdentifierException;
 import org.opengis.parameter.ParameterValue;
@@ -247,15 +246,17 @@ class MathTransformParser extends Abstra
     /**
      * Parses a sequence of {@code "PARAMETER"} elements.
      *
-     * @param  element     The parent element containing the parameters to parse.
-     * @param  parameters  The group where to store the parameter values.
-     * @param  linearUnit  The default linear unit, or {@code null}.
-     * @param  angularUnit The default angular unit, or {@code null}.
+     * @param  element            The parent element containing the parameters to parse.
+     * @param  parameters         The group where to store the parameter values.
+     * @param  defaultUnit        The default unit (for arbitrary quantity, including angular), or {@code null}.
+     * @param  defaultAngularUnit The default angular unit, or {@code null} if none. This is determined by the
+     *         context, especially when {@link GeodeticObjectParser} parses a {@code ProjectedCRS} element.
      * @throws ParseException if the {@code "PARAMETER"} element can not be parsed.
      */
     final void parseParameters(final Element element, final ParameterValueGroup parameters,
-            final Unit<Length> linearUnit, final Unit<Angle> angularUnit) throws ParseException
+            final Unit<?> defaultUnit, final Unit<Angle> defaultAngularUnit) throws ParseException
     {
+        final Unit<?> defaultSI = (defaultUnit != null) ? defaultUnit.toSI() : null;
         Element param = element;
         try {
             while ((param = element.pullElement(OPTIONAL, WKTKeywords.Parameter)) != null) {
@@ -267,7 +268,7 @@ class MathTransformParser extends Abstra
                  * than the parameter name. However we do not yet have a "get parameter by ID" in Apache SIS
                  * or in GeoAPI interfaces. This was not considered necessary since SIS is lenient (hopefully
                  * without introducing ambiguity) regarding parameter names, but we may revisit in a future
-                 * version if it become no longer the case. See https://issues.apache.org/jira/browse/SIS-163
+                 * version if it become no longer the case. See https://issues.apache.org/jira/browse/SIS-210
                  */
                 final ParameterValue<?>      parameter  = parameters.parameter(name);
                 final ParameterDescriptor<?> descriptor = parameter.getDescriptor();
@@ -275,10 +276,13 @@ class MathTransformParser extends Abstra
                 final boolean                isNumeric  = Number.class.isAssignableFrom(valueClass);
                 if (isNumeric && unit == null) {
                     unit = descriptor.getUnit();
-                    if (Units.isLinear(unit)) {
-                        unit = linearUnit;
-                    } else if (Units.isAngular(unit)) {
-                        unit = angularUnit;
+                    if (unit != null) {
+                        final Unit<?> si = unit.toSI();
+                        if (si.equals(defaultSI)) {
+                            unit = defaultUnit;
+                        } else if (si.equals(SI.RADIAN)) {
+                            unit = defaultAngularUnit;
+                        }
                     }
                 }
                 if (unit != null) {

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -92,6 +92,7 @@ import org.apache.sis.util.Characters;
  * @version 0.6
  * @module
  *
+ * @see org.apache.sis.util.Characters#isValidWKT(int)
  * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#39">WKT 2 specification §7.5.3</a>
  */
 public abstract class Transliterator implements Serializable {
@@ -101,6 +102,11 @@ public abstract class Transliterator imp
     private static final long serialVersionUID = 7115456393795045932L;
 
     /**
+     * A bitmask of control characters that are considered as spaces according {@link Character#isWhitespace(char)}.
+     */
+    static final int SPACES = 0xF0003E00;
+
+    /**
      * Default names to associate to axis directions in a Cartesian coordinate system.
      * Those names do not apply to other kind of coordinate systems.
      *
@@ -146,13 +152,38 @@ public abstract class Transliterator imp
      * {@linkplain Symbols#getClosingQuote(int) closing quotes}. The quotes will be doubled by the
      * caller if needed after this method has been invoked.</p>
      *
-     * <p>The default implementation invokes {@link CharSequences#toASCII(CharSequence)}.</p>
+     * <p>The default implementation invokes {@link CharSequences#toASCII(CharSequence)},
+     * replaces line feed and tabulations by single spaces, then remove control characters.</p>
      *
      * @param  text The text to format without non-ASCII characters.
      * @return The text to write in <cite>Well Known Text</cite>.
+     *
+     * @see org.apache.sis.util.Characters#isValidWKT(int)
      */
     public String filter(final String text) {
-        return CharSequences.toASCII(text).toString();
+        CharSequence s = CharSequences.toASCII(text);
+        StringBuilder buffer = null;
+        for (int i=s.length(); --i >= 0;) {
+            final char c = s.charAt(i);
+            if (c < 32) {
+                if (buffer == null) {
+                    if (s == text) {
+                        s = buffer = new StringBuilder(text);
+                    } else {
+                        buffer = (StringBuilder) s;
+                    }
+                }
+                if ((SPACES & (1 << c)) != 0) {
+                    buffer.setCharAt(i, ' ');
+                    if (i != 0 && c == '\n' && s.charAt(i-1) == '\r') {
+                        buffer.deleteCharAt(--i);
+                    }
+                } else {
+                    buffer.deleteCharAt(i);
+                }
+            }
+        }
+        return s.toString();
     }
 
     /**

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -21,8 +21,6 @@ import java.util.Locale;
 import java.util.TimeZone;
 import java.util.Map;
 import java.util.HashMap;
-import java.util.Collections;
-import java.util.List;
 import java.io.IOException;
 import java.text.Format;
 import java.text.NumberFormat;
@@ -81,6 +79,11 @@ import org.apache.sis.internal.util.Stan
  *
  * <div class="section">Limitations</div>
  * <ul>
+ *   <li><strong>The WKT format is not lossless!</strong>
+ *       Objects formatted by {@code WKTFormat} are not guaranteed to be identical after parsing.
+ *       Some metadata may be lost or altered, but the coordinate operations between two CRS should produce
+ *       the same numerical results provided that the two CRS were formatted independently (do not rely on
+ *       {@link org.opengis.referencing.crs.GeneralDerivedCRS#getConversionFromBase()} for instance).</li>
  *   <li>Instances of this class are not synchronized for multi-threading.
  *       It is recommended to create separated format instances for each thread.
  *       If multiple threads access a {@code WKTFormat} concurrently, it must be synchronized externally.</li>
@@ -554,18 +557,15 @@ public class WKTFormat extends CompoundF
             this.formatter = formatter;
         }
         final boolean valid;
-        final InternationalString warning;
         try {
             formatter.setBuffer(buffer);
             valid = formatter.appendElement(object) || formatter.appendValue(object);
         } finally {
-            warning = formatter.getErrorMessage();  // Must be saved before formatter.clear() is invoked.
+            warnings = formatter.getWarnings();  // Must be saved before formatter.clear() is invoked.
             formatter.setBuffer(null);
             formatter.clear();
         }
-        if (warning != null) {
-            warnings = new Warnings(getLocale(), (byte) 0, Collections.<String, List<String>>emptyMap());
-            warnings.add(warning, formatter.getErrorCause(), null);
+        if (warnings != null) {
             warnings.setRoot(object);
         }
         if (!valid) {

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -32,6 +32,7 @@ import org.opengis.util.InternationalStr
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Localized;
+import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.util.resources.Vocabulary;
@@ -237,6 +238,49 @@ public final class Warnings implements L
     }
 
     /**
+     * Returns the number of warning messages.
+     *
+     * @return The number of warning messages.
+     */
+    public final int getNumMessages() {
+        return (messages != null) ? messages.size() / 2 : 0;
+    }
+
+    /**
+     * Returns a warning message.
+     *
+     * @param  index 0 for the first warning, 1 for the second warning, <i>etc.</i> until {@link #getNumMessages()} - 1.
+     * @return The <var>i</var>-th warning message.
+     */
+    public String getMessage(int index) {
+        ArgumentChecks.ensureValidIndex(getNumMessages(), index);
+        index *= 2;
+        final InternationalString i18n = (InternationalString) messages.get(index);
+        if (i18n != null) {
+            return i18n.toString(errorLocale);
+        } else {
+            final Exception cause = (Exception) messages.get(index + 1);
+            final String[] sources = exceptionSources.get(cause);   // See comment in 'toString(Locale)'.
+            if (sources != null) {
+                return Errors.getResources(errorLocale).getString(Errors.Keys.UnparsableStringInElement_2, sources);
+            } else {
+                return cause.toString();
+            }
+        }
+    }
+
+    /**
+     * Returns the exception which was the cause of the message at the given index, or {@code null} if none.
+     *
+     * @param  index The value given to {@link #getMessage(int)}.
+     * @return The exception which was the cause of the warning message, or {@code null} if none.
+     */
+    public Exception getException(final int index) {
+        ArgumentChecks.ensureValidIndex(getNumMessages(), index);
+        return (Exception) messages.get(index*2 + 1);
+    }
+
+    /**
      * Returns the non-fatal exceptions that occurred during the parsing or formatting.
      * If no exception occurred, returns an empty set.
      *
@@ -313,7 +357,7 @@ public final class Warnings implements L
               .append(lineSeparator);
         if (messages != null) {
             for (final Iterator<?> it = messages.iterator(); it.hasNext();) {
-                InternationalString i18n = (InternationalString) it.next();
+                final InternationalString i18n = (InternationalString) it.next();
                 Exception cause = (Exception) it.next();
                 final String message;
                 if (i18n != null) {

Modified: sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -22,6 +22,7 @@ import org.opengis.referencing.cs.Ellips
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.apache.sis.internal.metadata.AxisNames;
 import org.apache.sis.test.mock.CoordinateSystemAxisMock;
+import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -38,6 +39,31 @@ import static org.junit.Assert.*;
  */
 public final strictfp class TransliteratorTest extends TestCase {
     /**
+     * Verify the value of the {@link Transliterator#SPACES} constant.
+     */
+    @Test
+    public void testSpacesConstant() {
+        int code = 0;
+        for (char c=0; c<32; c++) {
+            if (Character.isWhitespace(c)) {
+                code |= (1 << c);
+            }
+        }
+        assertEquals(Transliterator.SPACES, code);
+    }
+
+    /**
+     * Tests {@link Transliterator#filter(String)}.
+     */
+    @Test
+    @DependsOnMethod("testSpacesConstant")
+    public void testFilter() {
+        final Transliterator t = Transliterator.DEFAULT;
+        assertEquals("Nouvelle triangulation francaise", t.filter("Nouvelle\r\ntriangulation\nfrançaise"));
+        assertEquals("ABC D E", t.filter("AB\bC\rD\tE"));
+    }
+
+    /**
      * Tests {@link Transliterator#toLongAxisName(String, AxisDirection, String)}.
      */
     @Test

Modified: sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -32,12 +32,10 @@ import org.apache.sis.util.logging.Loggi
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.internal.jaxb.Context;
-import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
 import org.apache.sis.referencing.crs.DefaultGeographicCRS;
 import org.apache.sis.referencing.cs.AxesConvention;
-import org.apache.sis.measure.Units;
 
 import static java.util.Collections.singletonMap;
 import static org.apache.sis.internal.util.Numerics.epsilonEqual;
@@ -108,34 +106,6 @@ public final class ReferencingUtilities
     }
 
     /**
-     * Returns the angular unit of the specified coordinate system.
-     * The preference will be given to the longitude axis, if found.
-     *
-     * @param  cs The coordinate system from which to get the angular unit, or {@code null}.
-     * @return The angular unit, of {@code null} if no angular unit was found.
-     *
-     * @since 0.6
-     */
-    public static Unit<Angle> getAngularUnit(final CoordinateSystem cs) {
-        Unit<Angle> unit = null;
-        if (cs != null) {
-            for (int i = cs.getDimension(); --i>=0;) {
-                final CoordinateSystemAxis axis = cs.getAxis(i);
-                if (axis != null) {  // Paranoiac check.
-                    final Unit<?> candidate = axis.getUnit();
-                    if (Units.isAngular(candidate)) {
-                        unit = candidate.asType(Angle.class);
-                        if (AxisDirection.EAST.equals(AxisDirections.absolute(axis.getDirection()))) {
-                            break; // Found the longitude axis.
-                        }
-                    }
-                }
-            }
-        }
-        return unit;
-    }
-
-    /**
      * Returns the unit used for all axes in the given coordinate system.
      * If not all axes use the same unit, then this method returns {@code null}.
      *
@@ -145,6 +115,8 @@ public final class ReferencingUtilities
      *
      * @param cs The coordinate system for which to get the unit, or {@code null}.
      * @return The unit for all axis in the given coordinate system, or {@code null}.
+     *
+     * @see org.apache.sis.internal.metadata.AxisDirections#getAngularUnit(CoordinateSystem)
      */
     public static Unit<?> getUnit(final CoordinateSystem cs) {
         Unit<?> unit = null;

Modified: sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -31,6 +31,7 @@ import org.opengis.referencing.crs.Geode
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.apache.sis.internal.referencing.Legacy;
+import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.referencing.WKTUtilities;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
@@ -39,7 +40,6 @@ import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.io.wkt.Formatter;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
-import static org.apache.sis.internal.referencing.WKTUtilities.toFormattable;
 
 
 /**
@@ -188,16 +188,16 @@ class DefaultGeodeticCRS extends Abstrac
          */
         final GeodeticDatum datum = getDatum();     // Gives subclasses a chance to override.
         formatter.newLine();
-        formatter.append(toFormattable(datum));
+        formatter.append(WKTUtilities.toFormattable(datum));
         formatter.newLine();
         final PrimeMeridian pm = datum.getPrimeMeridian();
-        final Unit<Angle> angularUnit = ReferencingUtilities.getAngularUnit(cs);
+        final Unit<Angle> angularUnit = AxisDirections.getAngularUnit(cs, null);
         if (convention != Convention.WKT2_SIMPLIFIED ||
                 ReferencingUtilities.getGreenwichLongitude(pm, NonSI.DEGREE_ANGLE) != 0)
         {
             final Unit<Angle> oldUnit = formatter.addContextualUnit(angularUnit);
             formatter.indent(1);
-            formatter.append(toFormattable(pm));
+            formatter.append(WKTUtilities.toFormattable(pm));
             formatter.indent(-1);
             formatter.newLine();
             formatter.restoreContextualUnit(angularUnit, oldUnit);

Modified: sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -39,6 +39,7 @@ import org.apache.sis.referencing.Identi
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.operation.DefaultOperationMethod;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
+import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.referencing.WKTUtilities;
 import org.apache.sis.internal.util.Constants;
@@ -386,7 +387,7 @@ public class DefaultProjectedCRS extends
         final CartesianCS   cs          = getCoordinateSystem();
         final GeographicCRS baseCRS     = getBaseCRS();
         final Unit<?>       lengthUnit  = ReferencingUtilities.getUnit(cs);
-        final Unit<Angle>   angularUnit = ReferencingUtilities.getAngularUnit(baseCRS.getCoordinateSystem());
+        final Unit<Angle>   angularUnit = AxisDirections.getAngularUnit(baseCRS.getCoordinateSystem(), null);
         final Unit<Angle>   oldAngle    = formatter.addContextualUnit(angularUnit);
         final Unit<?>       oldLength   = formatter.addContextualUnit(lengthUnit);
         /*

Modified: sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -16,8 +16,11 @@
  */
 package org.apache.sis.io.wkt;
 
+import java.util.Collections;
 import java.text.ParseException;
+import javax.measure.unit.NonSI;
 import org.opengis.referencing.crs.VerticalCRS;
+import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
@@ -164,7 +167,9 @@ public final strictfp class WKTFormatTes
                 + "GEOGCS[“NAD27”,"
                 +   "DATUM[“North American Datum 1927”,"
                 +     "SPHEROID[“Clarke 1866”, 6378206.4, 294.97869821]],"
-                +   "UNIT[“degree”,0.0174532925199433]],"
+                +   "UNIT[“degree”,0.0174532925199433],"
+                +   "AXIS[“Lat”,NORTH],"
+                +   "AXIS[“Long”,EAST]],"
                 + "PROJECTION[“Lambert_Conformal_Conic_2SP”],"
                 + "PARAMETER[“latitude_of_origin”,27.83333333333333],"
                 + "PARAMETER[“central_meridian”,-99.0],"
@@ -227,4 +232,38 @@ public final strictfp class WKTFormatTes
         final Object reparsed = format.parseObject(reformat);
         assertEqualsIgnoreMetadata(expected, reparsed);
     }
+
+    /**
+     * Tests the production of a warning messages when the WKT contains unformattable elements.
+     *
+     * @throws ParseException if the parsing (tested after formatting) failed.
+     */
+    @Test
+    public void testWarnings() throws ParseException {
+        DefaultPrimeMeridian pm = new DefaultPrimeMeridian(Collections.singletonMap(
+                DefaultPrimeMeridian.NAME_KEY, "Invalid “$name” here"), -10, NonSI.DEGREE_ANGLE);
+        format = new WKTFormat(null, null);
+        final String   wkt      = format.format(pm);
+        final Warnings warnings = format.getWarnings();
+        assertNotNull("warnings", warnings);
+        assertEquals ("warnings.numMessages", 1, warnings.getNumMessages());
+        assertEquals ("PrimeMeridian[\"Invalid \"\"$name\"\" here\", -10.0, AngleUnit[\"degree\", 0.017453292519943295]]", wkt);
+        assertEquals ("The “$” character in “\"$name\"” is not permitted by the “Well-Known Text” format.", warnings.getMessage(0));
+        assertNull   (warnings.getException(0));
+        /*
+         * Verify that FormattableObject.toWKT() reports that the WKT is invalid.
+         */
+        try {
+            pm.toWKT();
+            fail("Expected UnformattableObjectException.");
+        } catch (UnformattableObjectException e) {
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("$name"));
+        }
+        /*
+         * Verify that the WKT is still parseable despite the warning.
+         */
+        pm = (DefaultPrimeMeridian) format.parseObject(wkt);
+        assertEquals("Invalid \"$name\" here", pm.getName().getCode());
+    }
 }

Modified: sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -28,7 +28,7 @@ import org.apache.sis.util.resources.Err
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.5
+ * @version 0.6
  * @module
  */
 public final class Characters extends Static {
@@ -78,6 +78,34 @@ public final class Characters extends St
     }
 
     /**
+     * Returns {@code true} if the given code point is a valid character for <cite>Well Known Text</cite> (WKT).
+     * This method returns {@code true} for the following characters:
+     *
+     * <blockquote><pre>{@literal A-Z a-z 0-9 _ [ ] ( ) { } < = > . , : ; + - (space) % & ' " * ^ / \ ? | °}</pre></blockquote>
+     *
+     * They are ASCII codes 32 to 125 inclusive except ! (33), # (35), $ (36), @ (64) and ` (96),
+     * plus the addition of ° (176) despite being formally outside the ASCII character set.
+     *
+     * @param  c The code point to test.
+     * @return {@code true} if the given code point is a valid WKT character.
+     *
+     * @see org.apache.sis.io.wkt.Transliterator
+     *
+     * @since 0.6
+     */
+    public static boolean isValidWKT(final int c) {
+        switch (c) {
+            case '!':
+            case '#':
+            case '$':
+            case '@':
+            case '`': return false;
+            case '°': return true;
+            default : return (c >= ' ') && (c <= '}');
+        }
+    }
+
+    /**
      * Returns {@code true} if the given code point is a {@linkplain Character#LINE_SEPARATOR
      * line separator}, a {@linkplain Character#PARAGRAPH_SEPARATOR paragraph separator} or one
      * of the {@code '\r'} or {@code '\n'} control characters.

Modified: sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -227,6 +227,8 @@ public final class StringBuilders extend
                         cr = ASCII.charAt(r);
                     } else {
                         switch (getType(c)) {
+                            case FORMAT:
+                            case CONTROL: buffer.delete(i, i + n); continue;  // Character.isIdentifierIgnorable
                             case PARAGRAPH_SEPARATOR:       // Fall through
                             case LINE_SEPARATOR:            cr = '\n'; break;
                             case SPACE_SEPARATOR:           cr = ' '; break;

Modified: sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -328,6 +328,11 @@ public final class Errors extends Indexe
         public static final short IllegalCRSType_1 = 194;
 
         /**
+         * The “{2}” character in “{1}” is not permitted by the “{0}” format.
+         */
+        public static final short IllegalCharacterForFormat_3 = 195;
+
+        /**
          * Class ‘{1}’ is illegal. It must be ‘{0}’ or a derived class.
          */
         public static final short IllegalClass_2 = 34;

Modified: sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] (original)
+++ sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] Fri Jul  3 17:50:05 2015
@@ -74,6 +74,7 @@ IllegalArgumentField_4            = Argu
 IllegalArgumentValue_2            = Argument \u2018{0}\u2019 can not take the \u201c{1}\u201d value.
 IllegalAxisDirection_2            = Coordinate system of class \u2018{0}\u2019 can not have axis in the {1} direction.
 IllegalBitsPattern_1              = Illegal bits pattern: {0}.
+IllegalCharacterForFormat_3       = The \u201c{2}\u201d character in \u201c{1}\u201d is not permitted by the \u201c{0}\u201d format.
 IllegalClass_2                    = Class \u2018{1}\u2019 is illegal. It must be \u2018{0}\u2019 or a derived class.
 IllegalCoordinateSystem_1         = Coordinate system can not be \u201c{0}\u201d.
 IllegalCRSType_1                  = Coordinate reference system can not be of type \u2018{0}\u2019.

Modified: sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] (original)
+++ sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] Fri Jul  3 17:50:05 2015
@@ -72,6 +72,7 @@ IllegalArgumentValue_2            = L\u2
 IllegalAxisDirection_2            = Les syst\u00e8mes de coordonn\u00e9es de classe \u2018{0}\u2019 ne peuvent pas avoir d\u2019axe dans la direction \u00ab\u202f{1}\u202f\u00bb.
 IllegalBitsPattern_1              = Pattern de bits invalide: {0}.
 IllegalClass_2                    = La classe \u2018{1}\u2019 est ill\u00e9gale. Il doit s\u2019agir d\u2019une classe \u2018{0}\u2019 ou d\u00e9riv\u00e9e.
+IllegalCharacterForFormat_3       = Le caract\u00e8re \u00ab\u202f{2}\u202f\u00bb dans \u00ab\u202f{1}\u202f\u00bb n\u2019est pas permis par le format \u00ab\u202f{0}\u202f\u00bb.
 IllegalCoordinateSystem_1         = Le syst\u00e8me de coordonn\u00e9es ne peut pas \u00eatre \u00ab\u202f{0}\u202f\u00bb.
 IllegalCRSType_1                  = Le syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es ne peut pas \u00eatre de type \u2018{0}\u2019.
 IllegalFormatPatternForClass_2    = Le mod\u00e8le \u00ab\u202f{1}\u202f\u00bb ne peut pas \u00eatre appliqu\u00e9 au formatage d\u2019objets de type \u2018{0}\u2019.

Modified: sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java?rev=1689051&r1=1689050&r2=1689051&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java [UTF-8] Fri Jul  3 17:50:05 2015
@@ -29,7 +29,7 @@ import static org.apache.sis.util.Charac
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.5
+ * @version 0.6
  * @module
  */
 public final strictfp class CharactersTest extends TestCase {
@@ -44,6 +44,21 @@ public final strictfp class CharactersTe
     }
 
     /**
+     * Tests the {@link Characters#isValidWKT(int)} method.
+     */
+    @Test
+    public void testIsValidWKT() {
+        final String valids = "_[](){}<=>.,:;+- %&'\"*^/\\?|°";
+        for (char c=0; c<256; c++) {
+            final boolean valid = (c >= 'A' && c <= 'Z')
+                               || (c >= 'a' && c <= 'z')
+                               || (c >= '0' && c <= '9')
+                               || valids.indexOf(c) >= 0;
+            assertEquals(valid, isValidWKT(c));
+        }
+    }
+
+    /**
      * Tests the {@link Characters#isLineOrParagraphSeparator(int)} method.
      */
     @Test



Mime
View raw message