Подтвердить что ты не робот

Как unescape строковый литерал Java в Java?

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

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


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

4b9b3361

Ответ 1

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

Проблема

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

  • Вы должны обеспечить загрузку еще одного ginormous файла jar с buttloads of cruft, который вам не нужен или не нужен.
  • У него есть лицензия. Некоторые люди не хотят беспокоиться о лицензии, независимо от того, насколько хороши или насколько это плохо.
  • Он забывает о \0 для null.
  • Она не обрабатывать восьмеричной вообще.
  • Он не может обрабатывать виды побегов, допущенных java.util.regex.Pattern.compile() и все, что его использует, включая \a, \e, и особенно \cX.
  • Он не поддерживает логические коды кода Unicode по номеру, только для идиотического повреждения UTF-16.
  • Его написал какой-то кровавый идиот, который даже не знает разницы между косой чертой и обратной косой чертой.
  • Исходный код полна раздражающих возвратов каретки.
  • Его написано, чтобы взять аргумент writer, поэтому, если вы его не передадите, ему все равно придется создать фиктивный StringWriter для вывода, а затем конвертировать его, чтобы передать вам.
  • Это похоже на код UCS-2, а не код UTF-16: они используют обесцененный интерфейс charAt вместо интерфейса codePoint, тем самым codePoint заблуждение, что char Java гарантированно удерживает символ Unicode. Это не. Они просто уходят с этой слепотой на астральные планы, потому что ни один суррогат 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)

Решение

Таким образом, этим утром я, наконец, устал от того, что не мог читать строки со встроенными экранами в них. Мне понадобилось это для написания набора тестов для более крупного и более интересного проекта: прозрачное преобразование Javas беззаботно Unicode-неосведомленных регулярных выражений в версии, где вы можете использовать все \w, \W, \s, \S, \v, \V, \h, \H, \d, \D, \b, \B, \X и \R в ваших шаблонах и правильно ли они работают с Unicode. Все, что я делаю, это переписать строку шаблона; он все еще компилируется со стандартной функцией java.util.regex.Pattern.compile(), поэтому все работает так, как ожидалось. Строка unescaper преднамеренно передает любой \b через нетронутую, если вы вызываете ее, прежде чем вы вызываете функцию преобразователя, чтобы сделать Java-регулярные выражения Unicode-осведомленными, поскольку это имеет дело с \b в граничном смысле.

Во всяком случае, здесь строка unescaper, которая хотя и менее интересная из пары, решает вопрос OP без всяких раздражений кода Apache. Он мог бы немного подтянуться в нескольких местах, но я быстро взломал его за несколько часов до обеда, чтобы его запустить и запустить, чтобы помочь водить комплект тестов. Другая функция - намного больше работы: вчера я взял меня весь день, черт побери.

/*
 *
 * unescape_perl_string()
 *
 *      Tom Christiansen <[email protected]>
 *      Sun Nov 28 12:55:24 MST 2010
 *
 * It completely ridiculous that there no standard
 * unescape_java_string function.  Since I have to do the
 * damn thing myself, I might as well make it halfway useful
 * by supporting things Java was too stupid to consider in
 * strings:
 * 
 *   => "?" items  are additions to Java string escapes
 *                 but normal in Java regexes
 *
 *   => "!" items  are also additions to Java regex escapes
 *   
 * Standard singletons: ?\a ?\e \f \n \r \t
 * 
 *      NB: \b is unsupported as backspace so it can pass-through
 *          to the regex translator untouched; I refuse to make anyone
 *          doublebackslash it as doublebackslashing is a Java idiocy
 *          I desperately wish would die out.  There are plenty of
 *          other ways to write it:
 *
 *              \cH, \12, \012, \x08 \x{8}, \u0008, \U00000008
 *
 * Octal escapes: \0 \0N \0NN \N \NN \NNN
 *    Can range up to !\777 not \377
 *    
 *      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 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('\007');
                       break; /* switch */

            case 'e':  newstr.append('\033');
                       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 \0 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('\0');
                    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-кода, я действительно программист на С - Java - это не что иное, как мой любимый язык. Я боюсь, что мне действительно придется столкнуться с Роб Пайком в его знаменитом публичном статическом разговоре об этом.

Достаточно.

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

Ответ 2

Вы можете использовать String unescapeJava(String) метод StringEscapeUtils from 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"

Утилита класса имеет методы для escapes и 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(
        "\45"
    ); // %
    System.out.println(
        StringEscapeUtils.unescapeJava("\\45")
    ); // 45
    System.out.println(
        "\45".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, поэтому обычно предпочтительны escape-последовательности Unicode.

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

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

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

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

Если ваша строка может содержать escape-последовательности Unicode с посторонним u, вам также может потребоваться предварительная обработка этого файла перед использованием StringEscapeUtils.

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

Ссылки

Ответ 3

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

Также доступен как Gist на 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();
}

Ответ 5

Я знаю, что этот вопрос был старым, но мне нужно решение, которое не включает библиотеки за пределами включенных 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.

Ответ 6

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

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 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\02\03\n".equals(unescapeJavaStrings("1\\02\\03\\n")[0])) {
        System.out.println("Success");
    } else {
        System.out.println("Failure");
    }
}

Ответ 7

Я столкнулся с одной и той же проблемой, но я не был в восторге от тех решений, которые я нашел здесь. Итак, я написал один, который выполняет итерации над символами строки, используя матчи, чтобы найти и заменить escape-последовательности. Это решение предполагает корректный форматированный ввод. То есть, он счастливо пропускает бессмысленные 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, похоже, обрабатывает восьмеричные escape-последовательности и несколько символов u для Unicode-экранов. Это означает, что если у вас нет какой-либо жгучей причины, чтобы избежать Apache Commons, вы, вероятно, должны использовать ее, а не мое решение (или любое другое решение здесь).

Ответ 8

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

StringContext.treatEscapes(escaped)

Ответ 9

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>

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

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

Ответ 10

Если вы читаете unicode escaped chars из файла, вам будет сложно делать это, потому что строка будет прочитана буквально вместе с побегом для обратного слэш:

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 не отменит это для вас (я попробовал unescapeXml()). Вам нужно будет сделать это вручную как описанный здесь.

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

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

Ответ 11

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