blob: 17c3bc4040c18246eb66bd1872f530521ed95e0c [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2016, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.r.core.data;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import java.util.Locale;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.rj.data.RFactorStore;
import org.eclipse.statet.rj.data.RStore;
@NonNullByDefault
public class RValueFormatter {
protected static final String NA_STRING= "NA"; //$NON-NLS-1$
protected static final String NUM_INF_STRING= "Inf";
protected static final String NUM_NAN_STRING= "NaN";
protected static final String NAME_NA_STRING= "<NA>"; //$NON-NLS-1$
private static final char[] DIGITS= new char[] {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
private static final DecimalFormatSymbols NUM_SYMBOLS;
static {
final DecimalFormatSymbols symbols= new DecimalFormatSymbols(Locale.ENGLISH);
symbols.setExponentSeparator("e");
symbols.setNaN(NUM_NAN_STRING);
symbols.setInfinity(NUM_INF_STRING);
NUM_SYMBOLS= symbols;
}
private final StringBuilder sb= new StringBuilder(80);
private @Nullable DecimalFormat numFormat;
public RValueFormatter() {
}
public void clear() {
this.sb.setLength(0);
}
public void append(final String s) {
this.sb.append(s);
}
public void append(final char c) {
this.sb.append(c);
}
private void append(final String s, final int start, final int end) {
switch (end - start) {
case 0:
return;
case 1:
this.sb.append(s.charAt(start));
return;
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
this.sb.append(s, start, end);
return;
default:
this.sb.append(s.substring(start, end));
return;
}
}
public final String getString() {
return this.sb.toString();
}
private void appendUIntHex(final int value) {
int pos= Math.max(((Integer.SIZE - Integer.numberOfLeadingZeros(value) + 3) / 4) - 1, 0);
do {
this.sb.append(DIGITS[(value >>> 4 * pos) & 0xF]);
} while (--pos >= 0);
}
private void appendUIntHex2(final byte value) {
this.sb.append(DIGITS[(value >>> 4) & 0x0F]);
this.sb.append(DIGITS[value & 0x0F]);
}
public void appendNA() {
this.sb.append(NA_STRING);
}
public void appendInt(final int integer) {
this.sb.append(integer);
// this.sb.append('L');
}
public void appendNum(final double value) {
this.sb.append(formatNum(value));
// if (Double.isInfinite(value)) {
// if (value < 0.0) {
// this.sb.append('-');
// }
// this.sb.append(NUM_INF_STRING);
// return;
// }
// if (Double.isNaN(value)) {
// this.sb.append(NUM_NAN_STRING);
// return;
// }
// this.sb.append(value);
// return;
}
private String formatNum(final double value) {
if (this.numFormat == null) {
this.numFormat= createNumFormat();
}
final double abs;
this.numFormat.setScientificNotation(value != 0
&& ((abs= Math.abs(value)) < 1e-4 || abs >= 1e5) );
return this.numFormat.format(value);
}
protected DecimalFormat createNumFormat() {
final DecimalFormat format= new DecimalFormat("0.0###################", NUM_SYMBOLS); //$NON-NLS-1$
format.setDecimalSeparatorAlwaysShown(true);
format.setGroupingUsed(false);
format.setExponentSignAlwaysShown(true);
format.setSignificantDigitsUsed(false);
return format;
}
private boolean isPrintableChar(final int cp) {
return (UCharacter.isPrintable(cp));
}
private void appendEscapedCodepoint(final int cp) {
this.sb.append((cp <= 0xFFFF) ? "\\u{" : "\\U{"); //$NON-NLS-1$ //$NON-NLS-2$
appendUIntHex(cp);
this.sb.append('}');
}
private int getEscapedCodepointLength(final int cp) {
return 4 + Math.max(((Integer.SIZE - Integer.numberOfLeadingZeros(cp) + 3) / 4), 1);
}
private void appendEscapedQuoteD(final String value) {
int idx= 0;
int start= idx;
while (idx < value.length()) {
final char c= value.charAt(idx);
switch (c) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x0E:
case 0x0F:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x19:
case 0x1A:
case 0x1B:
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
case 0x7F:
append(value, start, idx);
appendEscapedCodepoint(c);
start= ++idx;
continue;
case 0x07:
append(value, start, idx);
this.sb.append("\\a"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x08:
append(value, start, idx);
this.sb.append("\\b"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x09:
append(value, start, idx);
this.sb.append("\\t"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0A:
append(value, start, idx);
this.sb.append("\\n"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0B:
append(value, start, idx);
this.sb.append("\\v"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0C:
append(value, start, idx);
this.sb.append("\\f"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0D:
append(value, start, idx);
this.sb.append("\\r"); //$NON-NLS-1$
start= ++idx;
continue;
case '\\':
append(value, start, idx);
this.sb.append('\\');
start= idx++;
continue;
case '\"':
append(value, start, idx);
this.sb.append('\\');
start= idx++;
continue;
case 0x20: case 0x21: /* " */ case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
case 0x58: case 0x59: case 0x5A: case 0x5B: /* \\ */ case 0x5D: case 0x5E: case 0x5F:
case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: /* DEL */
idx++;
continue;
default:
{ final int cp= value.codePointAt(idx);
if (isPrintableChar(cp)) {
if (cp != c) {
idx+= 2;
}
else {
idx++;
}
continue;
}
else {
append(value, start, idx);
appendEscapedCodepoint(cp);
if (cp != c) {
start= (idx+= 2);
}
else {
start= ++idx;
}
continue;
}
}
}
}
append(value, start, idx);
}
public void appendStringD(final String value) {
this.sb.ensureCapacity(value.length() + 2);
this.sb.append('\"');
appendEscapedQuoteD(value);
this.sb.append('\"');
}
private void appendEscaped(final String value) {
int idx= 0;
int start= idx;
while (idx < value.length()) {
final char c= value.charAt(idx);
switch (c) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x0E:
case 0x0F:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x19:
case 0x1A:
case 0x1B:
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
case 0x7F:
append(value, start, idx);
appendEscapedCodepoint(c);
start= ++idx;
continue;
case 0x07:
append(value, start, idx);
this.sb.append("\\a"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x08:
append(value, start, idx);
this.sb.append("\\b"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x09:
append(value, start, idx);
this.sb.append("\\t"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0A:
append(value, start, idx);
this.sb.append("\\n"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0B:
append(value, start, idx);
this.sb.append("\\v"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0C:
append(value, start, idx);
this.sb.append("\\f"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0D:
append(value, start, idx);
this.sb.append("\\r"); //$NON-NLS-1$
start= ++idx;
continue;
case '\\':
append(value, start, idx);
this.sb.append('\\');
start= idx++;
continue;
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
case 0x58: case 0x59: case 0x5A: case 0x5B: /* \\ */ case 0x5D: case 0x5E: case 0x5F:
case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: /* DEL */
idx++;
continue;
default:
{ final int cp= value.codePointAt(idx);
if (isPrintableChar(cp)) {
if (cp != c) {
idx+= 2;
}
else {
idx++;
}
continue;
}
else {
append(value, start, idx);
appendEscapedCodepoint(cp);
if (cp != c) {
start= (idx+= 2);
}
else {
start= ++idx;
}
continue;
}
}
}
}
append(value, start, idx);
}
private void appendEscapedG(final String value) {
int idx= 0;
int start= idx;
while (idx < value.length()) {
final char c= value.charAt(idx);
switch (c) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x0E:
case 0x0F:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x19:
case 0x1A:
case 0x1B:
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
case 0x7F:
append(value, start, idx);
appendEscapedCodepoint(c);
start= ++idx;
continue;
case 0x07:
append(value, start, idx);
this.sb.append("\\a"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x08:
append(value, start, idx);
this.sb.append("\\b"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x09:
append(value, start, idx);
this.sb.append("\\t"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0A:
append(value, start, idx);
this.sb.append("\\n"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0B:
append(value, start, idx);
this.sb.append("\\v"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0C:
append(value, start, idx);
this.sb.append("\\f"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0D:
append(value, start, idx);
this.sb.append("\\r"); //$NON-NLS-1$
start= ++idx;
continue;
case '`':
append(value, start, idx);
this.sb.append("\\`"); //$NON-NLS-1$
start= ++idx;
continue;
case '\\':
append(value, start, idx);
this.sb.append('\\');
start= idx++;
continue;
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
case 0x58: case 0x59: case 0x5A: case 0x5B: /* \\ */ case 0x5D: case 0x5E: case 0x5F:
/* ` */ case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: /* DEL */
idx++;
continue;
default:
{ final int cp= value.codePointAt(idx);
if (isPrintableChar(cp)) {
if (cp != c) {
idx+= 2;
}
else {
idx++;
}
continue;
}
else {
append(value, start, idx);
appendEscapedCodepoint(cp);
if (cp != c) {
start= (idx+= 2);
}
else {
start= ++idx;
}
continue;
}
}
}
}
append(value, start, idx);
}
private boolean appendEscaped(final String value, final boolean isEscaped, int max) {
int idx= 0;
int start= idx;
int end= 0;
while (idx < value.length() && this.sb.length() + (idx - start) < max) {
final char c= value.charAt(idx);
switch (c) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x0E:
case 0x0F:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x19:
case 0x1A:
case 0x1B:
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
case 0x7F:
append(value, start, idx);
end= this.sb.length();
appendEscapedCodepoint(c);
start= ++idx;
continue;
case 0x07:
append(value, start, idx);
end= this.sb.length();
this.sb.append("\\a"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x08:
append(value, start, idx);
end= this.sb.length();
this.sb.append("\\b"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x09:
append(value, start, idx);
end= this.sb.length();
this.sb.append("\\t"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0A:
append(value, start, idx);
this.sb.append("\\n"); //$NON-NLS-1$
end= this.sb.length();
start= ++idx;
continue;
case 0x0B:
append(value, start, idx);
end= this.sb.length();
this.sb.append("\\v"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0C:
append(value, start, idx);
end= this.sb.length();
this.sb.append("\\f"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0D:
append(value, start, idx);
end= this.sb.length();
this.sb.append("\\r"); //$NON-NLS-1$
start= ++idx;
continue;
case '\\':
append(value, start, idx);
end= this.sb.length();
this.sb.append('\\');
start= (isEscaped) ? ++idx : idx++;
continue;
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
case 0x58: case 0x59: case 0x5A: case 0x5B: /* \\ */ case 0x5D: case 0x5E: case 0x5F:
case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: /* DEL */
idx++;
continue;
default:
{ final int cp= value.codePointAt(idx);
if (isPrintableChar(cp)) {
if (cp != c) {
idx+= 2;
max++;
}
else {
idx++;
}
continue;
}
else {
append(value, start, idx);
end= this.sb.length();
appendEscapedCodepoint(cp);
if (cp != c) {
start= (idx+= 2);
}
else {
start= ++idx;
}
continue;
}
}
}
}
if (this.sb.length() >= max) {
this.sb.setLength(end);
return false;
}
else {
append(value, start, idx);
return (idx == value.length());
}
}
private String escapeString(final String s) {
this.sb.setLength(0);
int idx= 0;
int start= idx;
while (idx < s.length()) {
final char c= s.charAt(idx);
switch (c) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x0E:
case 0x0F:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x19:
case 0x1A:
case 0x1B:
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
case 0x7F:
append(s, start, idx);
appendEscapedCodepoint(c);
start= ++idx;
continue;
case 0x07:
append(s, start, idx);
this.sb.append("\\a"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x08:
append(s, start, idx);
this.sb.append("\\b"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x09:
append(s, start, idx);
this.sb.append("\\t"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0A:
append(s, start, idx);
this.sb.append("\\n"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0B:
append(s, start, idx);
this.sb.append("\\v"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0C:
append(s, start, idx);
this.sb.append("\\f"); //$NON-NLS-1$
start= ++idx;
continue;
case 0x0D:
append(s, start, idx);
this.sb.append("\\r"); //$NON-NLS-1$
start= ++idx;
continue;
case '\\':
append(s, start, idx);
this.sb.append("\\"); //$NON-NLS-1$
start= idx++;
continue;
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
case 0x58: case 0x59: case 0x5A: case 0x5B: /* \\ */ case 0x5D: case 0x5E: case 0x5F:
case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: /* DEL */
idx++;
continue;
default:
{ final int cp= s.codePointAt(idx);
if (isPrintableChar(cp)) {
if (cp != c) {
idx+= 2;
}
else {
idx++;
}
continue;
}
else {
append(s, start, idx);
appendEscapedCodepoint(cp);
if (cp != c) {
start= (idx+= 2);
}
else {
start= ++idx;
}
continue;
}
}
}
}
if (start != 0) {
append(s, start, idx);
return this.sb.toString();
}
return s;
}
public void appendRaw(final byte raw) {
appendUIntHex2(raw);
}
public void append(final RStore<?> data, final int idx) {
if (data.isNA(idx)) {
this.sb.append(NA_STRING);
return;
}
switch (data.getStoreType()) {
// case RStore.LOGICAL:
case RStore.INTEGER:
appendInt(data.getInt(idx));
return;
case RStore.NUMERIC:
appendNum(data.getNum(idx));
return;
case RStore.CHARACTER:
appendStringD(data.getChar(idx));
return;
// case RStore.COMPLEX:
case RStore.RAW:
appendRaw(data.getRaw(idx));
return;
case RStore.FACTOR:
appendName(((RFactorStore) data).getLevels(), data.getInt(idx) - 1);
return;
default:
this.sb.append(data.getChar(idx));
return;
}
}
public void appendName(final RStore<?> data, final int idx) {
if (data.isNA(idx)) {
this.sb.append(NAME_NA_STRING);
}
switch (data.getStoreType()) {
case RStore.CHARACTER:
appendEscaped(data.getChar(idx));
return;
// case RStore.INTEGER:
// case RStore.NUMERIC:
default:
this.sb.append(data.getChar(idx));
return;
}
}
public void appendName(final String name, final boolean quote) {
if (quote) {
this.sb.ensureCapacity(name.length() + 2);
this.sb.append('`');
appendEscapedG(name);
this.sb.append('`');
}
else {
appendEscaped(name);
}
}
public void appendSourceLine(final String source, final int limit) {
if (!appendEscaped(source, true, limit)) {
this.sb.append('\u2026');
}
}
public String format(final RStore<?> data, final int idx) {
if (data.isNA(idx)) {
return NA_STRING;
}
switch (data.getStoreType()) {
// case RStore.LOGICAL:
// case RStore.INTEGER:
case RStore.NUMERIC:
return formatNum(data.getNum(idx));
case RStore.CHARACTER:
clear();
appendStringD(data.getChar(idx));
return getString();
// case RStore.COMPLEX:
case RStore.RAW:
clear();
appendUIntHex2(data.getRaw(idx));
return getString();
case RStore.FACTOR:
return formatName(((RFactorStore) data).getLevels(), data.getInt(idx) - 1);
default:
return nonNullAssert(data.getChar(idx));
}
}
public String formatName(final RStore<?> data, final int idx) {
if (data.isNA(idx)) {
return NAME_NA_STRING;
}
switch (data.getStoreType()) {
// case RStore.INTEGER:
// case RStore.NUMERIC:
case RStore.CHARACTER:
return escapeString(data.getChar(idx));
default:
return nonNullAssert(data.getChar(idx));
}
}
public String quoteName(final String name) {
clear();
this.sb.append('"');
int idx= 0;
int start= idx;
while (idx < name.length()) {
idx= name.indexOf('"', idx);
if (idx >= 0) {
append(name, start, idx);
this.sb.append('\\');
start= idx++;
}
else {
break;
}
}
append(name, start, name.length());
this.sb.append('"');
return getString();
}
/**
* Correct text regions in accordance with {@link #appendName(String, boolean)}
*/
public void correctNameRegions(final int[] regions,
final String name, final boolean quote, final int offset) {
if (quote) {
correctRegionsEscapeG(name, regions, offset, 1, 1);
}
else {
throw new UnsupportedOperationException();
}
}
private int shiftRegions(final int[] regions, int i, final int endOffset, final int shift) {
while (i < regions.length
&& regions[i] <= endOffset ) {
regions[i++]+= shift;
}
return i;
}
private int shiftRegionsClosed(final int[] regions, int i, final int endOffset, final int shift) {
while (i < regions.length
&& (regions[i] < endOffset || (i % 2 == 1 && regions[i] == endOffset)) ) {
regions[i++]+= shift;
}
return i;
}
private void shiftRegionsRemaining(final int[] regions, int i, final int shift) {
while (i < regions.length) {
regions[i++]+= shift;
}
}
/**
* Correct text regions in accordance with {@link #appendEscapedG(String)}
**/
private void correctRegionsEscapeG(final String value,
final int[] regions, final int valueOffset, int shift, final int shiftAtEnd) {
int i= 0;
for (;; i++) {
if (i >= regions.length) {
return;
}
if (regions[i] < valueOffset) {
continue;
}
else {
break;
}
}
int idx= 0;
while (idx < value.length()) {
final char c= value.charAt(idx);
switch (c) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x0E:
case 0x0F:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x19:
case 0x1A:
case 0x1B:
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
case 0x7F:
i= shiftRegions(regions, i, valueOffset + idx, shift);
if (i >= regions.length) {
return;
}
// appendEscapedCodepoint(c) =>
idx++;
continue;
case 0x07:
case 0x08:
case 0x09:
case 0x0A:
case 0x0B:
case 0x0C:
case 0x0D:
case '`':
case '\\':
i= shiftRegions(regions, i, valueOffset + idx, shift);
if (i >= regions.length) {
return;
}
// \\s
shift+= 1;
idx++;
continue;
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F:
case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
case 0x58: case 0x59: case 0x5A: case 0x5B: /* \\ */ case 0x5D: case 0x5E: case 0x5F:
/* ` */ case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: /* DEL */
idx++;
continue;
default:
{ i= shiftRegions(regions, i, valueOffset + idx, shift);
if (i >= regions.length) {
return;
}
final int cp= value.codePointAt(idx);
if (cp != c) {
if (!isPrintableChar(cp)) {
shift+= getEscapedCodepointLength(cp) - 2;
}
idx+= 2;
}
else {
if (!isPrintableChar(cp)) {
shift+= getEscapedCodepointLength(cp) - 1;
}
idx++;
}
continue;
}
}
}
i= shiftRegionsClosed(regions, i, valueOffset + idx, shift); // idx == value.length()
shiftRegionsRemaining(regions, i, shift + shiftAtEnd);
}
}