| /*=============================================================================# |
| # 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); |
| } |
| |
| } |