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 bdccdc1014901eda858cd198f958137a6a80a735 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Tue Jan 30 16:11:24 2024 +0100 Fix a formatting error (exception or broken strings) when the WKT contains numbers formatted as a matrix and X364 coloring is enabled. --- .../main/org/apache/sis/io/wkt/Formatter.java | 96 +++++++++++++--------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java index 92fb6d754f..a32da24a3e 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java @@ -280,7 +280,7 @@ public class Formatter implements Localized { private int colorApplied; /** - * The amount of spaces to use in indentation, or {@value org.apache.sis.io.wkt.WKTFormat#SINGLE_LINE} + * The number of spaces to use in indentation, or {@value org.apache.sis.io.wkt.WKTFormat#SINGLE_LINE} * if indentation is disabled. * * @see #configure(Convention, Citation, Colors, byte, byte, byte, int) @@ -288,7 +288,7 @@ public class Formatter implements Localized { private byte indentation; /** - * The amount of space to write on the left side of each line. This amount is increased + * The number of space to write on the left side of each line. This amount is increased * by {@code indentation} every time a {@link FormattableObject} is appended in a new * indentation level. */ @@ -353,7 +353,7 @@ public class Formatter implements Localized { * * @param convention the convention to use. * @param symbols the symbols. - * @param indentation the amount of spaces to use in indentation for WKT formatting, + * @param indentation the number of spaces to use in indentation for WKT formatting, * or {@link WKTFormat#SINGLE_LINE} for formatting the whole WKT on a single line. */ public Formatter(final Convention convention, final Symbols symbols, final int indentation) { @@ -422,7 +422,7 @@ public class Formatter implements Localized { * @param colors the syntax coloring, or {@code null} if none. * @param toUpperCase whether keywords shall be converted to upper cases. * @param longKeywords {@code -1} for short keywords, {@code +1} for long keywords or 0 for the default. - * @param indentation the amount of spaces to use in indentation for WKT formatting, or {@link WKTFormat#SINGLE_LINE}. + * @param indentation the number of spaces to use in indentation for WKT formatting, or {@link WKTFormat#SINGLE_LINE}. * @param listSizeLimit maximum number of elements to show in lists, or {@link Integer#MAX_VALUE} if unlimited. */ final void configure(Convention convention, final Citation authority, final Colors colors, @@ -559,7 +559,7 @@ public class Formatter implements Localized { /** * Increases or decreases the indentation. A value of {@code +1} increases - * the indentation by the amount of spaces specified at construction time, + * the indentation by the number of spaces specified at construction time, * and a value of {@code -1} reduces it by the same amount. * * @param amount +1 for increasing the indentation, or -1 for decreasing it, or 0 for no-op. @@ -712,11 +712,19 @@ public class Formatter implements Localized { final Locale syntax = symbols.getLocale(); // Not the same purpose as `this.locale`. keyword = (toUpperCase >= 0) ? keyword.toUpperCase(syntax) : keyword.toLowerCase(syntax); } + /* + * If the WKT contains errors or non-standard elements, highlight the keyword (if allowed). + * Some buffer indices will need to be shifted for spaces occupied by the X364 color codes: + * `base`, `keywordSpaceAt`. + */ + int highlightOffset = 0; if (highlightError && colors != null) { final String color = colors.getAnsiSequence(ElementKind.ERROR); if (color != null) { - buffer.insert(base, color + X364.BACKGROUND_DEFAULT.sequence()); - base += color.length(); + final String c = color + X364.BACKGROUND_DEFAULT.sequence(); + highlightOffset = c.length(); + buffer.insert(base, c); + base += color.length(); // Insert keyword before `BACKGROUND_DEFAULT`. } } highlightError = false; @@ -734,7 +742,7 @@ public class Formatter implements Localized { final int n = keywordSpaceAt.size(); for (int i=0; i<n;) { int p = keywordSpaceAt.getInt(i); - p += (++i * length); // Take in account spaces added previously. + p += highlightOffset + (++i * length); // Take in account spaces added previously. buffer.insert(p, additionalMargin); } keywordSpaceAt.clear(); @@ -1276,7 +1284,7 @@ public class Formatter implements Localized { * If the rows are going to be formatted on many lines, then we will need to put some margin before each row. * If the first row starts on its own line, then the margin will be the usual indentation. But if the first * row starts on the same line as previous elements (or the keyword of this element, e.g. "BOX["), then we - * will need a different amount of spaces if we want to have the numbers properly aligned. + * will need a different number of spaces if we want to have the numbers properly aligned. */ final int numRows = rows.length; final boolean isMultiLines = (indentation > WKTFormat.SINGLE_LINE) && (numRows > 1); @@ -1292,20 +1300,23 @@ public class Formatter implements Localized { if (Characters.isLineOrParagraphSeparator(c)) break; i -= Character.charCount(c); } - currentLineLength = buffer.codePointCount(i, length); + currentLineLength = X364.lengthOfPlain(buffer, i, length); } marginBeforeRow = CharSequences.spaces(currentLineLength); } else { marginBeforeRow = ""; } /* - * `formattedNumberMarks` contains, for each number in each row, positions in the `buffer` where - * the number starts and position where it ends. Those positions are stored as (start,end) pairs. - * We compute those marks unconditionally for simplicity, but will ignore them if formatting on - * a single line. + * `formattedNumberMarks` contains, for each number in each row, the index after `base` where + * the number starts and the index where the number ends (including X364 colors), together with + * the number lengths (ignoring X364 colors). Those information are stored as (start,end,length) + * tuples. We compute those values unconditionally for simplicity, but will ignore them if the + * WKT is formatted on a single line. */ + final int TUPLE_LENGTH = 3; // Number of elements in each `formattedNumberMarks` tuple. + final int base = elementStart; // Needs to be saved here because `elementStart` may be modified. final int[][] formattedNumberMarks = new int[numRows][]; - int numColumns = 0; + int maxNumCols = 0; for (int j=0; j<numRows; j++) { if (j == 0) { appendSeparator(); // It is up to the caller to decide if we begin with a new line. @@ -1314,63 +1325,72 @@ public class Formatter implements Localized { } final Vector numbers = rows[j]; final int numCols = numbers.size(); - numColumns = Math.max(numColumns, numCols); // Store the length of longest row. - final int[] marks = new int[numCols << 1]; // Positions where numbers are formatted. + maxNumCols = Math.max(maxNumCols, numCols); // Store the length of longest row. + final int[] marks = new int[numCols*TUPLE_LENGTH]; // Positions where numbers are formatted. formattedNumberMarks[j] = marks; - for (int i=0; i<numCols; i++) { + for (int i=0,k=0; i<numCols; i++) { if (i != 0) buffer.append(Symbols.NUMBER_SEPARATOR); if (i < fractionDigits.length) { // Otherwise, same as previous number. final int f = fractionDigits[i]; numberFormat.setMaximumFractionDigits(f); numberFormat.setMinimumFractionDigits(f); } - marks[i << 1] = buffer.length(); // Store the start position where number is formatted. + marks[k++] = buffer.length() - base; // Store the start position where number is formatted. setColor(ElementKind.NUMBER); + final int s = buffer.length(); final Number n = numbers.get(i); if (n != null) { numberFormat.format(n, buffer, dummy); } else { buffer.append('…'); } + final int e = buffer.length(); resetColor(); - marks[(i << 1) | 1] = buffer.length(); // Store the end position where number is formatted. + marks[k++] = buffer.length() - base; // Store the end position where number is formatted. + marks[k++] = buffer.codePointCount(s, e); // Note: there is no X364 colors in this range. } } /* - * If formatting on more than one line, insert the amount of spaces required for aligning numbers. + * If formatting on more than one line, insert the number of spaces required for aligning numbers. * This is possible because we wrote the coordinate values with fixed number of fraction digits. */ if (isMultiLines) { - final int base = elementStart; - final String toWrite = buffer.substring(base); // Save what we formatted in above loop. - buffer.setLength(base); // Discard what we formatted - we will rewrite. - final int[] columnWidths = new int[numColumns]; - for (final int[] marks : formattedNumberMarks) { // Compute the maximal width of each column. - for (int i=0; i<marks.length; i += 2) { - final int k = i >> 1; - final int w = toWrite.codePointCount(marks[i ] -= base, - marks[i+1] -= base); + // Compute the maximal width of each column. + final int[] columnWidths = new int[maxNumCols]; + for (final int[] marks : formattedNumberMarks) { + for (int i = TUPLE_LENGTH-1; i < marks.length; i += TUPLE_LENGTH) { + final int w = marks[i]; + final int k = i / TUPLE_LENGTH; if (w > columnWidths[k]) columnWidths[k] = w; } } - if (needsAlignment && keywordSpaceAt == null) { - keywordSpaceAt = new IntegerList(formattedNumberMarks.length, Integer.MAX_VALUE); - } + final String toWrite = buffer.substring(base); // Save what we formatted in above loop. + buffer.setLength(base); // Discard what we formatted - we will rewrite. + int endOfPrevious = 0; boolean requestAlignment = false; - int lastPosition = 0; for (int[] marks : formattedNumberMarks) { // Recopy the formatted text, with more spaces. for (int i = 0; i<marks.length;) { - final int w = columnWidths[i >> 1]; + final int w = columnWidths[i / TUPLE_LENGTH]; final int s = marks[i++]; final int e = marks[i++]; - buffer.append(toWrite, lastPosition, s) - .append(CharSequences.spaces(w - toWrite.codePointCount(s, e))); + final int n = marks[i++]; + buffer.append(toWrite, endOfPrevious, s).append(CharSequences.spaces(w - n)); + /* + * If we are formatting the first number of a new line except the first line, + * we will need to add more spaces than what we added with `marginBeforeRow`. + * The number of spaces depends on the number of characters in the keyword to + * be written before the values. We do not know that keyword yet, so we need + * to remember that more spaces will need to be inserted here. + */ if (requestAlignment) { requestAlignment = false; + if (keywordSpaceAt == null) { + keywordSpaceAt = new IntegerList(formattedNumberMarks.length, Integer.MAX_VALUE); + } keywordSpaceAt.add(buffer.length()); } buffer.append(toWrite, s, e); - lastPosition = e; + endOfPrevious = e; } requestAlignment = needsAlignment; }