Як уникнути Java-рядкового літералу в Java?


76

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

Чи існує функція всередині Java API, яка це робить? Якщо ні, чи можу я отримати таку функціональність з якоїсь бібліотеки? Очевидно, що компілятор Java повинен зробити це перетворення.

На випадок, якщо хтось захоче дізнатись, я намагаюся де-заплутати рядкові літерали в декомпільованих заплутаних файлах Java.

Відповіді:


103

Проблема

org.apache.commons.lang.StringEscapeUtils.unescapeJava()Дається тут ще одна відповідь насправді дуже мало допомагає взагалі.

  • Він забуває про \0null.
  • Він не виконує жодних вісімкове взагалі .
  • Він не може впоратися з різного роду пагонах , що допускаються java.util.regex.Pattern.compile()і все , що використовує, в тому числі \a, \eі в зокрема \cX.
  • Він не підтримує логічні кодові коди Unicode за номерами, лише для UTF-16.
  • Це виглядає як код UCS-2, а не код UTF-16: вони використовують амортизований charAtінтерфейс замість codePointінтерфейсу, тим самим проголошуючи оману, що Java charгарантовано містить символ Unicode. Це не. Їм це вдається лише тому, що жоден сурогат UTF-16 не закінчить шукати все, що вони шукають.

Рішення

Я написав рядок, який розв'язує питання OP без усіх роздратування коду Apache.

/*
 *
 * unescape_perl_string()
 *
 *      Tom Christiansen <tchrist@perl.com>
 *      Sun Nov 28 12:55:24 MST 2010
 *
 * It's completely ridiculous that there's 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'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('\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);
}

Якщо це допомагає іншим, ви можете його запросити - жодних рядків не додано. Якщо ви вдосконалите його, я хотів би, щоб ви надіслали мені свої вдосконалення поштою, але вам, звичайно, не потрібно.


1
чому ваша процедура називається unescape_ perl _string? Крім того, чи не робить все зайве уникнення для речей, не визначених специфікацією, помилкою, оскільки сама Java не може трактувати буквал так? Просто переконуюсь, що я тут нічого не пропускаю - код досить складний, і я трохи переживаю за всі зайві біти.
яскравий

@tchrist Чи маєте ви уявлення про те, чи проблеми, які ви описали підходом Apache, все ще діють чи вони виправили це?
sjngm

1
@tchrist Дякую. Я щойно спробував ваш акуратний метод із текстовим файлом, який містить, foo\\barі він повернувся foo\\bar. Я би очікував, що це буде foo\bar. Це помилка чи я неправильно розумію ідею методу?
sjngm

@sjngm Як писано, насправді передбачається проходити через невизнані втечі зворотних рисок, замість того, щоб позбавлятись одного рівня, тому що я вважаю, що нескінченно зростаюча тенденція подвійного, потрійного, чотириразового та ін. .
christ

3
Apache Commons Lang3 виправив низку цих речей (принаймні восьмеричний біт, який мене притягував
Дрю Стівенс,

49

Ви можете використовувати 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"

Клас утиліти має методи екранувань та невдалих рядків для Java, Java Script, HTML, XML та SQL. Він також має перевантаження, які записують безпосередньо в java.io.Writer.


Застереження

Схоже, StringEscapeUtilsобробляє втечу Unicode з одним u, але не восьмеричне, або втікання Unicode із сторонніми us.

    /* 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:

Вісімкові пагони передбачені для сумісності з С, але може висловити тільки значення Unicode \u0000через \u00FF, тому вислизає Unicode, як правило , кращі.

Якщо ваш рядок може містити вісімкові екранування, ви можете спершу перетворити їх на екрани Unicode або скористатися іншим підходом.

Стороннє uтакож документується наступним чином:

Мова програмування Java визначає стандартний спосіб перетворення програми, написаної в Unicode, у ASCII, що змінює програму у форму, яку можна обробляти засобами на базі ASCII. Трансформація передбачає перетворення будь-якого екрану Unicode у вихідному тексті програми в ASCII, додавши додатковийu приклад, \uxxxxстає\uuxxxx -одночасно перетворюючи символи, що не є ASCII у вихідному тексті, в екрануючі Unicode, що містять по одному u.

Ця трансформована версія є однаково прийнятною для компілятора мови програмування Java і представляє абсолютно ту саму програму. Точне джерело Unicode можна згодом відновити з цієї форми ASCII, перетворивши кожну послідовність виходів, де кількаu символів, у послідовність символів Unicode з одним меншим числом u, одночасно перетворюючи кожну послідовність переходів з одного uна відповідний одиночний символ Unicode.

Якщо ваш рядок може містити переходи Unicode із сторонніми u , можливо, вам також доведеться попередньо обробити це перед використанням StringEscapeUtils.

Крім того, ви можете спробувати написати власний буквальний рядок Java, що не перетворюється, з нуля, дотримуючись точних специфікацій JLS.

Список літератури


Я знайшов і цю бібліотеку (після публікації). У мене проблеми з вісімковими значеннями. Зараз я намагаюся перетворити їх вручну.
ziggystar

Е-е, перетворення восьмериць в унікод не є тривіальним. Тільки 0-127 можна легко відобразити. Чи це правильно?
ziggystar

@ziggystar: Схоже, 0-255натомість JLS каже (див. цитату). Найбільший восьмеричний втеча \377.
полігенні мастила

Ах, ти маєш рацію. Специфікація говорить, що восьмерики є значеннями Unicode. Я підозрював, що це значення ASCII.
ziggystar

У мене є \ xf3, який є акцентом іспанською мовою. Як мені зняти його?
розробник тигрів

17

Натрапив на подібну проблему, не був задоволений представленими рішеннями і сам реалізував цю.

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

3
Для додаткової обережності спочатку зробіть нульову перевірку та в такому випадку поверніть null. Але спасибі!
Андреас Рудольф,


8

Я знаю, що це питання було давнім, але я хотів рішення, яке не стосується бібліотек поза межами включеного 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.

1
@UdoKlimaschewski - Ви маєте рацію. Ви можете подивитися джерело, щоб побачити, які захисні екрани він насправді підтримує.
DaoWen

6

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

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);
    }
}

Потрібен масив, щоб ви могли уникнути партіями. Отже, наступний простий тест вдався:

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");
    }
}

4

Для запису, якщо ви використовуєте Scala, ви можете зробити:

StringContext.treatEscapes(escaped)

Він має справу лише з `[\ b, \ t, \ n, \ f, \ r, \\, \", \ '] `, він не може впоратися з втечами Unicode.
Андрій Тюкін,


3

Я зіткнувся з тією ж проблемою, але мене не захопило жодне з вирішень, яке я знайшов тут. Отже, я написав такий, який перебирає символи рядка за допомогою збігу, щоб знайти та замінити вхідні послідовності. Це рішення передбачає правильно відформатоване введення. Тобто він із задоволенням пропускає безглузді екранації та декодує екранування 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, ви, ймовірно, повинні використовувати його, а не моє рішення (або будь-яке інше рішення тут).


2

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

0

Якщо ви читаєте символи Unicode, що втекли з файлу, то вам буде важко це робити, оскільки рядок буде читатися буквально разом із екраном для зворотної риски:

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 єдиним символом "\ u0020"

Але якщо ви використовуєте цей "\ u0020", String.split("... ..... ..", columnDelimiterReadFromFile)який насправді використовує регулярний вираз внутрішньо, він буде працювати безпосередньо, оскільки рядок, прочитаний з файлу, був захищений і ідеально підходить для використання у шаблоні регулярних виразів !! (Збентежений?)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.