Как отменить эскейп строкового литерала Java в Java?


я обрабатываю некоторый исходный код Java с помощью Java. Я извлекаю строковые литералы и передаю их в функцию, принимающую строку. Проблема в том, что мне нужно пройти неэкранированные версии строки в функцию (т. е. это означает преобразование n к новой строке, и в одном и т. д.).

есть ли функция внутри Java API, которая делает это? Если нет, Могу ли я получить такую функциональность из какой-либо библиотеки? Очевидно, что компилятор Java должен сделать это преобразование.


PS- в случае, если кто-то хочет знать: я пытаюсь unobfuscate строковые литералы в декомпилированных запутанных Java-файлов

11 62

11 ответов:

EDIT: вы можете скачать полный источник для функции, которую я обсуждаю ниже. Я также обсуждаю это более подробно в ответ.

Проблема

The org.apache.commons.lang.StringEscapeUtils.unescapeJava() данный здесь как "ответ" на самом деле очень мало помогает вообще.

  • вы должны обеспечить загрузку еще одного гигантского файла jar с прикладами cruft, которые вам не нужны или не нужны.
  • он лицензия. Некоторые люди не хотят беспокоиться о лицензии, независимо от того, насколько хорошо или насколько плохо на самом деле.
  • он забывает о на null.
  • он не обрабатывает восьмеричной на всех.
  • он не может справиться с видами побегов, допущенных java.util.regex.Pattern.compile() и все, что использует его, в том числе \a,\e и особенно \cX.
  • он не поддерживает логические кодовые точки Юникода по номеру, только для идиотских UTF-16 повреждение головного мозга.
  • это написано каким-то чертовым идиотом, который даже не знает разницы между полоснуть и обратная косая черта.
  • исходный код полон раздражающих возвратов каретки.
  • это написано, чтобы взять writer аргумент, так что если вы не передадите его один он все равно должен создать манекен StringWriter для вывода, а затем преобразовать, что передать обратно к вам.
  • это выглядит как код UCS-2, а не код UTF-16: они используют амортизированные charAt интерфейс вместо codePoint интерфейс, тем самым распространяя заблуждение, что Java char гарантированно содержит символ Юникода. Это не так. Им только сходит с рук эта слепота к астральным планам, потому что никакой суррогат UTF-16 не будет искать то, что они ищут.

как и многие другие пункты, их смущает незнание названий кодовых пунктов U+2F и U+5C не внушает доверия в них вообще ничего нет. Для протокола:

  /  47    002F  SOLIDUS
        = slash, virgule
        x (latin letter dental click - 01C0)
        x (combining long solidus overlay - 0338)
        x (fraction slash - 2044)
        x (division slash - 2215)
 \  92    005C  REVERSE SOLIDUS
        = backslash
        x (combining reverse solidus overlay - 20E5)
        x (set minus - 2216)

Решение

так что сегодня утром я наконец-то надоело не уметь читать в строках со встроенными в них побегами. Мне это было нужно для написания набора тестов для более крупного и интересного проекта: прозрачное преобразование Java неопровержимо Unicode-невежественные регулярные выражения в версии, где вы можете использовать все \w,\W,\s,\S,\v, \V,\h,\H,\d,\D,\b,\B,\X и \R в ваших шаблонах и иметь их на самом деле правильно работать с Unicode. Все, что я делаю, это переписать строку шаблона; он по-прежнему компилируется со стандартным N NN \N \NN \NNN * Can range up to !7 not 7 * * TODO: add !\o{NNNNN} * last Unicode is 4177777 * maxint is 37777777777 * * Control chars: ?\cX * Means: ord(X) ^ ord('@') * * Old hex escapes: \xXX * unbraced must be 2 xdigits * * Perl hex escapes: !\x{XXX} braced may be 1-8 xdigits * NB: proper Unicode never needs more than 6, as highest * valid codepoint is 0x10FFFF, not maxint 0xFFFFFFFF * * Lame Java escape: \[IDIOT JAVA PREPROCESSOR]uXXXX must be * exactly 4 xdigits; * * I can't write XXXX in this comment where it belongs * because the damned Java Preprocessor can't mind its * own business. Idiots! * * Lame Python escape: !\UXXXXXXXX must be exactly 8 xdigits * * TODO: Perl translation escapes: \Q \U \L \E \[IDIOT JAVA PREPROCESSOR]u \l * These are not so important to cover if you're passing the * result to Pattern.compile(), since it handles them for you * further downstream. Hm, what about \[IDIOT JAVA PREPROCESSOR]u? * */ public final static String unescape_perl_string(String oldstr) { /* * In contrast to fixing Java's broken regex charclasses, * this one need be no bigger, as unescaping shrinks the string * here, where in the other one, it grows it. */ StringBuffer newstr = new StringBuffer(oldstr.length()); boolean saw_backslash = false; for (int i = 0; i < oldstr.length(); i++) { int cp = oldstr.codePointAt(i); if (oldstr.codePointAt(i) > Character.MAX_VALUE) { i++; /****WE HATES UTF-16! WE HATES IT FOREVERSES!!!****/ } if (!saw_backslash) { if (cp == '\') { saw_backslash = true; } else { newstr.append(Character.toChars(cp)); } continue; /* switch */ } if (cp == '\') { saw_backslash = false; newstr.append('\'); newstr.append('\'); continue; /* switch */ } switch (cp) { case 'r': newstr.append('\r'); break; /* switch */ case 'n': newstr.append('\n'); break; /* switch */ case 'f': newstr.append('\f'); break; /* switch */ /* PASS a \b THROUGH!! */ case 'b': newstr.append("\b"); break; /* switch */ case 't': newstr.append('\t'); break; /* switch */ case 'a': newstr.append('7'); break; /* switch */ case 'e': newstr.append('3'); break; /* switch */ /* * A "control" character is what you get when you xor its * codepoint with '@'==64. This only makes sense for ASCII, * and may not yield a "control" character after all. * * Strange but true: "\c{" is ";", "\c}" is "=", etc. */ case 'c': { if (++i == oldstr.length()) { die("trailing \c"); } cp = oldstr.codePointAt(i); /* * don't need to grok surrogates, as next line blows them up */ if (cp > 0x7f) { die("expected ASCII after \c"); } newstr.append(Character.toChars(cp ^ 64)); break; /* switch */ } case '8': case '9': die("illegal octal digit"); /* NOTREACHED */ /* * may be 0 to 2 octal digits following this one * so back up one for fallthrough to next case; * unread this digit and fall through to next case. */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': --i; /* FALLTHROUGH */ /* * Can have 0, 1, or 2 octal digits following a 0 * this permits larger values than octal 377, up to * octal 777. */ case '0': { if (i+1 == oldstr.length()) { /* found at end of string */ newstr.append(Character.toChars(0)); break; /* switch */ } i++; int digits = 0; int j; for (j = 0; j <= 2; j++) { if (i+j == oldstr.length()) { break; /* for */ } /* safe because will unread surrogate */ int ch = oldstr.charAt(i+j); if (ch < '0' || ch > '7') { break; /* for */ } digits++; } if (digits == 0) { --i; newstr.append(''); break; /* switch */ } int value = 0; try { value = Integer.parseInt( oldstr.substring(i, i+digits), 8); } catch (NumberFormatException nfe) { die("invalid octal value for \0 escape"); } newstr.append(Character.toChars(value)); i += digits-1; break; /* switch */ } /* end case '0' */ case 'x': { if (i+2 > oldstr.length()) { die("string too short for \x escape"); } i++; boolean saw_brace = false; if (oldstr.charAt(i) == '{') { /* ^^^^^^ ok to ignore surrogates here */ i++; saw_brace = true; } int j; for (j = 0; j < 8; j++) { if (!saw_brace && j == 2) { break; /* for */ } /* * ASCII test also catches surrogates */ int ch = oldstr.charAt(i+j); if (ch > 127) { die("illegal non-ASCII hex digit in \x escape"); } if (saw_brace && ch == '}') { break; /* for */ } if (! ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) ) { die(String.format( "illegal hex digit #%d '%c' in \x", ch, ch)); } } if (j == 0) { die("empty braces in \x{} escape"); } int value = 0; try { value = Integer.parseInt(oldstr.substring(i, i+j), 16); } catch (NumberFormatException nfe) { die("invalid hex value for \x escape"); } newstr.append(Character.toChars(value)); if (saw_brace) { j++; } i += j-1; break; /* switch */ } case 'u': { if (i+4 > oldstr.length()) { die("string too short for \u escape"); } i++; int j; for (j = 0; j < 4; j++) { /* this also handles the surrogate issue */ if (oldstr.charAt(i+j) > 127) { die("illegal non-ASCII hex digit in \u escape"); } } int value = 0; try { value = Integer.parseInt( oldstr.substring(i, i+j), 16); } catch (NumberFormatException nfe) { die("invalid hex value for \u escape"); } newstr.append(Character.toChars(value)); i += j-1; break; /* switch */ } case 'U': { if (i+8 > oldstr.length()) { die("string too short for \U escape"); } i++; int j; for (j = 0; j < 8; j++) { /* this also handles the surrogate issue */ if (oldstr.charAt(i+j) > 127) { die("illegal non-ASCII hex digit in \U escape"); } } int value = 0; try { value = Integer.parseInt(oldstr.substring(i, i+j), 16); } catch (NumberFormatException nfe) { die("invalid hex value for \U escape"); } newstr.append(Character.toChars(value)); i += j-1; break; /* switch */ } default: newstr.append('\'); newstr.append(Character.toChars(cp)); /* * say(String.format( * "DEFAULT unrecognized escape %c passed through", * cp)); */ break; /* switch */ } saw_backslash = false; } /* weird to leave one at the end */ if (saw_backslash) { newstr.append('\'); } return newstr.toString(); } /* * Return a string "U+XX.XXX.XXXX" etc, where each XX set is the * xdigits of the logical Unicode code point. No bloody brain-damaged * UTF-16 surrogate crap, just true logical characters. */ public final static String uniplus(String s) { if (s.length() == 0) { return ""; } /* This is just the minimum; sb will grow as needed. */ StringBuffer sb = new StringBuffer(2 + 3 * s.length()); sb.append("U+"); for (int i = 0; i < s.length(); i++) { sb.append(String.format("%X", s.codePointAt(i))); if (s.codePointAt(i) > Character.MAX_VALUE) { i++; /****WE HATES UTF-16! WE HATES IT FOREVERSES!!!****/ } if (i+1 < s.length()) { sb.append("."); } } return sb.toString(); } private static final void die(String foa) { throw new IllegalArgumentException(foa); } private static final void say(String what) { System.out.println(what); }

как любой может ясно видеть из кода Java выше, я действительно программист C-Java is что-нибудь но мой любимый язык. Боюсь, что мне действительно придется встать на сторону Роб Пайк в своем знаменитом public static void говорить на этот один.

’Nuff сказал.

в любом случае, это всего лишь быстрый утренний хакер, но если это помогает другим, вы можете это сделать - без каких-либо условий. Если вы улучшите его, Я бы хотел, чтобы вы прислали мне свои улучшения, но вам, конечно, не нужно.

можно использовать String unescapeJava(String) метод StringEscapeUtils С Apache Commons Lang.

вот пример фрагмента:

    String in = "a\tb\n\\"c\\"";

    System.out.println(in);
    // a\tb\n\"c\"

    String out = StringEscapeUtils.unescapeJava(in);

    System.out.println(out);
    // a    b
    // "c"

класс утилиты имеет методы для экранирования и unescape строк для Java, Java Script, HTML, XML и SQL. Он также имеет перегрузки, которые записывает непосредственно в java.io.Writer.


предостережения

похоже StringEscapeUtils обрабатывает Unicode экранирует с одним u, но не восьмеричные побеги, или Unicode убегает с посторонним u s.

    /* Unicode escape test #1: PASS */

    System.out.println(
        "\u0030"
    ); // 0
    System.out.println(
        StringEscapeUtils.unescapeJava("\u0030")
    ); // 0
    System.out.println(
        "\u0030".equals(StringEscapeUtils.unescapeJava("\u0030"))
    ); // true

    /* Octal escape test: FAIL */

    System.out.println(
        ""
    ); // %
    System.out.println(
        StringEscapeUtils.unescapeJava("\45")
    ); // 45
    System.out.println(
        "".equals(StringEscapeUtils.unescapeJava("\45"))
    ); // false

    /* Unicode escape test #2: FAIL */

    System.out.println(
        "\uu0030"
    ); // 0
    System.out.println(
        StringEscapeUtils.unescapeJava("\uu0030")
    ); // throws NestableRuntimeException:
       //   Unable to parse unicode value: u003

цитата из JLS:

Восьмеричные экранирования предусмотрены для совместимости с C, но могут выражать только значения Unicode \u0000 через \u00FF, поэтому обычно предпочтительны эскейпы Unicode.

если ваша строка может содержать восьмеричные escapes, вы можете сначала преобразовать их в Unicode escapes или использовать другой подход.

в посторонний u также описана следующим образом:

язык программирования Java определяет стандартный способ преобразования программы, написанной в Юникоде, в ASCII, который изменяет программу в форму, которая может быть обработана средствами на основе ASCII. Преобразование включает в себя преобразование любых экранирований Unicode в исходном тексте программы в ASCII путем добавления дополнительного u-например, \uxxxx становится \uuxxxx - при одновременном преобразовании символов, отличных от ASCII исходный текст в Unicode экранирует, содержащий по одному u каждый.

эта преобразованная версия одинаково приемлема для компилятора для языка программирования Java и представляет собой точно такую же программу. Точный источник Юникода может быть позже восстановлен из этой формы ASCII путем преобразования каждой escape-последовательности, где несколько u ' s присутствуют в последовательности символов Юникода с одним меньше u, одновременно Преобразуя каждую escape-последовательность с помощью одного u к соответствующему одиночному символу Юникода.

если ваша строка может содержать Unicode escapes с extraneous u, то вам также может потребоваться предварительно обработать это перед использованием StringEscapeUtils.

в качестве альтернативы вы можете попробовать написать свой собственный строковый литерал Java unescaper с нуля, убедившись, что следуете точным спецификациям JLS.

ссылки

столкнулся с подобной проблемой, также не был удовлетворен представленными решениями и реализовал это сам.

также доступна в качестве сути на Github:

/**
 * Unescapes a string that contains standard Java escape sequences.
 * <ul>
 * <li><strong>&#92;b &#92;f &#92;n &#92;r &#92;t &#92;" &#92;'</strong> :
 * BS, FF, NL, CR, TAB, double and single quote.</li>
 * <li><strong>&#92;X &#92;XX &#92;XXX</strong> : Octal character
 * specification (0 - 377, 0x00 - 0xFF).</li>
 * <li><strong>&#92;uXXXX</strong> : Hexadecimal based Unicode character.</li>
 * </ul>
 * 
 * @param st
 *            A string optionally containing standard java escape sequences.
 * @return The translated string.
 */
public String unescapeJavaString(String st) {

    StringBuilder sb = new StringBuilder(st.length());

    for (int i = 0; i < st.length(); i++) {
        char ch = st.charAt(i);
        if (ch == '\') {
            char nextChar = (i == st.length() - 1) ? '\' : st
                    .charAt(i + 1);
            // Octal escape?
            if (nextChar >= '0' && nextChar <= '7') {
                String code = "" + nextChar;
                i++;
                if ((i < st.length() - 1) && st.charAt(i + 1) >= '0'
                        && st.charAt(i + 1) <= '7') {
                    code += st.charAt(i + 1);
                    i++;
                    if ((i < st.length() - 1) && st.charAt(i + 1) >= '0'
                            && st.charAt(i + 1) <= '7') {
                        code += st.charAt(i + 1);
                        i++;
                    }
                }
                sb.append((char) Integer.parseInt(code, 8));
                continue;
            }
            switch (nextChar) {
            case '\':
                ch = '\';
                break;
            case 'b':
                ch = '\b';
                break;
            case 'f':
                ch = '\f';
                break;
            case 'n':
                ch = '\n';
                break;
            case 'r':
                ch = '\r';
                break;
            case 't':
                ch = '\t';
                break;
            case '\"':
                ch = '\"';
                break;
            case '\'':
                ch = '\'';
                break;
            // Hex Unicode: u????
            case 'u':
                if (i >= st.length() - 5) {
                    ch = 'u';
                    break;
                }
                int code = Integer.parseInt(
                        "" + st.charAt(i + 2) + st.charAt(i + 3)
                                + st.charAt(i + 4) + st.charAt(i + 5), 16);
                sb.append(Character.toChars(code));
                i += 5;
                continue;
            }
            i++;
        }
        sb.append(ch);
    }
    return sb.toString();
}

С http://commons.apache.org/lang/:

StringEscapeUtils

StringEscapeUtils.unescapeJava(String str)

Я знаю, что этот вопрос был старым, но я хотел решение, которое не включает библиотеки за пределами тех, которые включены JRE6 (т. е. Apache Commons не приемлем), и я придумал простое решение, используя встроенный java.io.StreamTokenizer:

import java.io.*;

// ...

String literal = "\"Has \\"\\\\t\\" & isn\\'t \\r\\n on 1 line.\"";
StreamTokenizer parser = new StreamTokenizer(new StringReader(literal));
String result;
try {
  parser.nextToken();
  if (parser.ttype == '"') {
    result = parser.sval;
  }
  else {
    result = "ERROR!";
  }
}
catch (IOException e) {
  result = e.toString();
}
System.out.println(result);

выход:

Has "\  " & isn't
 on 1 line.

Я немного опоздал на это, но я думал, что предоставлю свое решение, так как мне нужна та же функциональность. Я решил использовать API компилятора Java, который делает его медленнее, но делает результаты точными. В основном я живу создать класс, а затем вернуть результаты. Вот метод:

public static String[] unescapeJavaStrings(String... escaped) {
    //class name
    final String className = "Temp" + System.currentTimeMillis();
    //build the source
    final StringBuilder source = new StringBuilder(100 + escaped.length * 20).
            append("public class ").append(className).append("{\n").
            append("\tpublic static String[] getStrings() {\n").
            append("\t\treturn new String[] {\n");
    for (String string : escaped) {
        source.append("\t\t\t\"");
        //we escape non-escaped quotes here to be safe 
        //  (but something like \" will fail, oh well for now)
        for (int i = 0; i < string.length(); i++) {
            char chr = string.charAt(i);
            if (chr == '"' && i > 0 && string.charAt(i - 1) != '\') {
                source.append('\');
            }
            source.append(chr);
        }
        source.append("\",\n");
    }
    source.append("\t\t};\n\t}\n}\n");
    //obtain compiler
    final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    //local stream for output
    final ByteArrayOutputStream out = new ByteArrayOutputStream();
    //local stream for error
    ByteArrayOutputStream err = new ByteArrayOutputStream();
    //source file
    JavaFileObject sourceFile = new SimpleJavaFileObject(
            URI.create("string:///" + className + Kind.SOURCE.extension), Kind.SOURCE) {
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return source;
        }
    };
    //target file
    final JavaFileObject targetFile = new SimpleJavaFileObject(
            URI.create("string:///" + className + Kind.CLASS.extension), Kind.CLASS) {
        @Override
        public OutputStream openOutputStream() throws IOException {
            return out;
        }
    };
    //file manager proxy, with most parts delegated to the standard one 
    JavaFileManager fileManagerProxy = (JavaFileManager) Proxy.newProxyInstance(
            StringUtils.class.getClassLoader(), new Class[] { JavaFileManager.class },
            new InvocationHandler() {
                //standard file manager to delegate to
                private final JavaFileManager standard = 
                    compiler.getStandardFileManager(null, null, null); 
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if ("getJavaFileForOutput".equals(method.getName())) {
                        //return the target file when it's asking for output
                        return targetFile;
                    } else {
                        return method.invoke(standard, args);
                    }
                }
            });
    //create the task
    CompilationTask task = compiler.getTask(new OutputStreamWriter(err), 
            fileManagerProxy, null, null, null, Collections.singleton(sourceFile));
    //call it
    if (!task.call()) {
        throw new RuntimeException("Compilation failed, output:\n" + 
                new String(err.toByteArray()));
    }
    //get the result
    final byte[] bytes = out.toByteArray();
    //load class
    Class<?> clazz;
    try {
        //custom class loader for garbage collection
        clazz = new ClassLoader() { 
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                if (name.equals(className)) {
                    return defineClass(className, bytes, 0, bytes.length);
                } else {
                    return super.findClass(name);
                }
            }
        }.loadClass(className);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
    //reflectively call method
    try {
        return (String[]) clazz.getDeclaredMethod("getStrings").invoke(null);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Он принимает массив, так что вы можете unescape в пакетах. Таким образом, следующий простой тест завершается успешно:

public static void main(String[] meh) {
    if ("1\n".equals(unescapeJavaStrings("1\02\03\n")[0])) {
        System.out.println("Success");
    } else {
        System.out.println("Failure");
    }
}

я столкнулся с той же проблемой, но я не был очарован ни одним из решений, которые я нашел здесь. Итак, я написал тот, который повторяет символы строки, используя сопоставитель, чтобы найти и заменить escape-последовательности. Это решение предполагает правильно отформатированный ввод. То есть он счастливо пропускает бессмысленные экранирования и декодирует экранирование Unicode для подачи строки и возврата каретки (которые в противном случае не могут отображаться в символьном литерале или строковом литерале из-за определения таких литералы и порядок фаз перевода для исходного кода Java). Извините, код немного упакован для краткости.

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Decoder {

    // The encoded character of each character escape.
    // This array functions as the keys of a sorted map, from encoded characters to decoded characters.
    static final char[] ENCODED_ESCAPES = { '\"', '\'', '\',  'b',  'f',  'n',  'r',  't' };

    // The decoded character of each character escape.
    // This array functions as the values of a sorted map, from encoded characters to decoded characters.
    static final char[] DECODED_ESCAPES = { '\"', '\'', '\', '\b', '\f', '\n', '\r', '\t' };

    // A pattern that matches an escape.
    // What follows the escape indicator is captured by group 1=character 2=octal 3=Unicode.
    static final Pattern PATTERN = Pattern.compile("\\(?:(b|t|n|f|r|\\"|\\'|\\)|((?:[0-3]?[0-7])?[0-7])|u+(\p{XDigit}{4}))");

    public static CharSequence decodeString(CharSequence encodedString) {
        Matcher matcher = PATTERN.matcher(encodedString);
        StringBuffer decodedString = new StringBuffer();
        // Find each escape of the encoded string in succession.
        while (matcher.find()) {
            char ch;
            if (matcher.start(1) >= 0) {
                // Decode a character escape.
                ch = DECODED_ESCAPES[Arrays.binarySearch(ENCODED_ESCAPES, matcher.group(1).charAt(0))];
            } else if (matcher.start(2) >= 0) {
                // Decode an octal escape.
                ch = (char)(Integer.parseInt(matcher.group(2), 8));
            } else /* if (matcher.start(3) >= 0) */ {
                // Decode a Unicode escape.
                ch = (char)(Integer.parseInt(matcher.group(3), 16));
            }
            // Replace the escape with the decoded character.
            matcher.appendReplacement(decodedString, Matcher.quoteReplacement(String.valueOf(ch)));
        }
        // Append the remainder of the encoded string to the decoded string.
        // The remainder is the longest suffix of the encoded string such that the suffix contains no escapes.
        matcher.appendTail(decodedString);
        return decodedString;
    }

    public static void main(String... args) {
        System.out.println(decodeString(args[0]));
    }
}

Я должен отметить, что Apache Commons Lang3, похоже, не страдает слабостями, указанными в принятом решении. То есть, StringEscapeUtils Кажется, обрабатывать восьмеричные побеги и несколько u символы Unicode экранируются. Это означает, что если у вас нет какой-то горящей причины избегать Apache Commons, вы, вероятно, должны использовать его, а не мое решение (или любое другое решение здесь).

для записи, если вы используете Scala, вы можете сделать:

StringContext.treatEscapes(escaped)

org.apache.commons.lang3.StringEscapeUtils из commons-lang3 теперь помечен как устаревший. Вы можете использовать org.apache.commons.text.StringEscapeUtils#unescapeJava(String) вместо. Это требует дополнительного зависимостей Maven:

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-text</artifactId>
            <version>1.4</version>
        </dependency>

и, кажется, обрабатывать еще несколько особых случаев, например, отменяет:

  • экранированные обратные косые черты, одинарные и двойные кавычки
  • экранированные восьмеричные и юникодные значения
  • \b,\n,\t,\f,\r

если Вы читаете Unicode экранированные символы из файла, то вам будет трудно сделать это, потому что строка будет прочитана буквально вместе с escape для задней косой черты:

my_file.txt

Blah blah...
Column delimiter=;
Word delimiter=\u0020 #This is just unicode for whitespace

.. more stuff

здесь, когда вы читаете строку 3 из файла строка / строка будет иметь:

"Word delimiter=\u0020 #This is just unicode for whitespace"

и char[] в строке появится сообщение:

{...., '=', '\', 'u', '0', '0', '2', '0', ' ', '#', 't', 'h', ...}

Commons StringUnescape не будет unescape это для вас (я пробовал unescapeXml ()). Вы будете иметь сделать это вручную как описано здесь.

Итак, подстрока "\u0020 "должна стать 1 одиночным символом'\u0020'

но если вы используете это "\u0020", чтобы сделать String.split("... ..... ..", columnDelimiterReadFromFile) который действительно использует регулярное выражение внутри, он будет работать напрямую, потому что строка, считанная из файла, была экранирована и идеально подходит для использования в шаблоне регулярного выражения!! (В замешательстве?)

возможно, вы захотите взглянуть на реализацию Eclipse Stringliteral.