it-swarm.com.de

Wie kann ein Java-String-Literal in Java aufgehoben werden?

Ich verarbeite Java-Quellcode mit Java. Ich extrahiere die String-Literale und füttere sie mit einer Funktion, die einen String übernimmt. Das Problem ist, dass ich die nicht maskierte Version des Strings an die Funktion übergeben muss (d. H. Das bedeutet, \n in eine neue Zeile und \\ in einen einzelnen \ usw.).

Gibt es eine Funktion innerhalb der Java-API, die dies tut? Wenn nicht, kann ich eine solche Funktionalität von einer Bibliothek erhalten? Natürlich muss der Java-Compiler diese Konvertierung durchführen.

Falls jemand wissen will, versuche ich, String-Literale in dekompilierten, verschleierten Java-Dateien zu verschleiern.

62
ziggystar

Das Problem

Die org.Apache.commons.lang.StringEscapeUtils.unescapeJava(), die hier als weitere Antwort gegeben wird, ist wirklich sehr wenig hilfreich.

  • Es vergisst \0 für null.
  • Oktal überhaupt wird nicht verarbeitet. 
  • Es kann nicht mit den Arten von Escape-Befehlen umgehen, die von der Java.util.regex.Pattern.compile() zugelassen werden, und mit allem, was sie verwendet, einschließlich \a, \e und insbesondere \cX
  • Logische Unicode-Codepunkte werden nur für UTF-16 unterstützt.
  • Dies sieht aus wie UCS-2-Code und nicht als UTF-16-Code: Sie verwenden anstelle der charAt-Schnittstelle die herabgesetzte codePoint-Schnittstelle. Damit wird die Täuschung laut, dass eine Java-char garantiert ein Unicode-Zeichen enthält. Es ist nicht. Sie kommen nur damit durch, weil kein UTF-16-Surrogat nach etwas suchen wird, nach dem sie suchen. 

Die Lösung

Ich habe eine Zeichenkette geschrieben, die die OP-Frage ohne alle Irritationen des Apache-Codes löst.

/*
 *
 * unescape_Perl_string()
 *
 *      Tom Christiansen <[email protected]>
 *      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);
}

Wenn es anderen hilft, sind Sie herzlich willkommen - keine Bedingungen. Wenn Sie es verbessern, würde ich es sehr begrüßen, wenn Sie mir Ihre Verbesserungen per E-Mail zusenden.

89
tchrist

Sie können String unescapeJava(String) Methode von StringEscapeUtils von Apache Commons Lang verwenden.

Hier ist ein Beispiel-Snippet:

_    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"
_

Die Utility-Klasse verfügt über Methoden zum Escape- und Unescape-Zeichenfolgen für Java,Java Script, HTML, XML und SQL. Es hat auch Überladungen, die direkt in ein Java.io.Writer schreiben.


Vorbehalte

Es sieht so aus, als würde StringEscapeUtils Unicode-Escapezeichen mit einem u behandeln, nicht jedoch Oktal-Escapezeichen oder Unicode-Escapezeichen mit fremden 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
_

Ein Zitat aus dem JLS:

Oktale Escape-Zeichen werden aus Gründen der Kompatibilität mit C bereitgestellt, können jedoch nur Unicode-Werte _\u0000_ bis _\u00FF_ ausdrücken. Daher werden Unicode-Escape-Zeichen normalerweise bevorzugt.

Wenn Ihre Zeichenfolge oktale Escapezeichen enthalten kann, möchten Sie diese möglicherweise zuerst in Unicode-Escapezeichen konvertieren oder einen anderen Ansatz verwenden.

Das fremde u wird auch wie folgt dokumentiert:

Die Programmiersprache Java gibt eine Standardmethode für die Umwandlung eines in Unicode geschriebenen Programms in ASCII an, mit der ein Programm in eine Form umgewandelt wird, die von ASCII-basierten Tools verarbeitet werden kann. Bei der Umwandlung werden alle Unicode-Escapezeichen im Quelltext des Programms in ASCII konvertiert, indem ein zusätzliches u hinzugefügt wird. Beispielsweise wird _\uxxxx_ zu _\uuxxxx_, während gleichzeitig Nicht-ASCII-Zeichen konvertiert werden im Quelltext entgeht Unicode mit jeweils einem u.

Diese transformierte Version ist für einen Compiler für die Programmiersprache Java gleichermaßen akzeptabel und repräsentiert genau dasselbe Programm. Die genaue Unicode-Quelle kann später aus diesem ASCII Formular wiederhergestellt werden, indem jede Escape-Sequenz, in der mehrere u vorhanden sind, in eine Sequenz von Unicode-Zeichen mit einem weniger u konvertiert wird. Gleichzeitig wird jede Escape-Sequenz mit einem einzelnen u in konvertiert das entsprechende einzelne Unicode-Zeichen.

Wenn Ihre Zeichenfolge Unicode-Escapezeichen mit dem Zusatz u enthalten kann, müssen Sie dies möglicherweise auch vor der Verwendung von StringEscapeUtils verarbeiten.

Alternativ können Sie versuchen, Ihren eigenen Java String-Literal-Unescaper von Grund auf neu zu schreiben und dabei die genauen JLS-Spezifikationen einzuhalten.

Verweise

48

Kam über ein ähnliches Problem, war auch mit den vorgestellten Lösungen nicht zufrieden und setzte diese selbst um.

Auch als Gist auf Github verfügbar:

/**
 * 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();
}
11

Sehen Sie dies von http://commons.Apache.org/lang/ :

StringEscapeUtils

StringEscapeUtils.unescapeJava(String str)

10
Lasse Espeholt

Ich weiß, dass diese Frage alt war, aber ich wollte eine Lösung, die keine Bibliotheken außerhalb der enthaltenen JRE6 umfasst (d. H. Apache Commons ist nicht akzeptabel), und ich habe eine einfache Lösung mit dem integrierten Java.io.StreamTokenizer gefunden:

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

Ausgabe:

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

Ich bin etwas spät dran, aber ich dachte, ich würde meine Lösung anbieten, da ich die gleiche Funktionalität brauchte. Ich entschied mich für die Java-Compiler-API, die zwar langsamer ist, aber die Ergebnisse genau macht. Grundsätzlich lebe ich eine Klasse und gebe dann die Ergebnisse zurück. Hier ist die Methode:

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

Sie benötigen ein Array, um die Stapelung zu deaktivieren. Der folgende einfache Test ist also erfolgreich:

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

Ich bin auf das gleiche Problem gestoßen, aber ich war nicht begeistert von den Lösungen, die ich hier gefunden habe. Also habe ich einen geschrieben, der die Zeichen der Zeichenkette mit einem Matcher durchläuft, um die Escape-Sequenzen zu finden und zu ersetzen. Diese Lösung setzt eine korrekt formatierte Eingabe voraus. Das heißt, es überspringt glücklich unsinnige Escapezeichen und dekodiert Unicode-Escape-Zeichen für Zeilenvorschub und Wagenrücklauf (die sonst nicht in einem Zeichen- oder String-Literal vorkommen können, aufgrund der Definition dieser Literale und der Reihenfolge der Übersetzungsphasen für Java Quelle). Entschuldigung, der Code ist aus Gründen der Kürze etwas gepackt.

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

Ich sollte beachten, dass Apache Commons Lang3 nicht die Schwächen zu erleiden scheint, die in der akzeptierten Lösung angegeben sind. Das heißt, StringEscapeUtils scheint mit oktalen Escapezeichen und mehreren u-Zeichen von Unicode-Escapezeichen umzugehen. Das heißt, es sei denn, Sie haben einen triftigen Grund, Apache Commons zu meiden, sollten Sie es eher als meine Lösung (oder eine andere Lösung hier) verwenden.

3
Nathan Ryan

Wenn Sie Scala verwenden, können Sie Folgendes tun:

StringContext.treatEscapes(escaped)
2
Tvaroh

org.Apache.commons.lang3.StringEscapeUtils von commons-lang3 ist jetzt als veraltet markiert. Sie können stattdessen org.Apache.commons.text.StringEscapeUtils#unescapeJava(String) verwenden. Es erfordert eine zusätzliche Maven-Abhängigkeit :

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

und scheint einige spezielle Fälle zu behandeln, z. entkleidet:

  • backslashes, einfache und doppelte Anführungszeichen
  • eskalierte Oktal- und Unicode-Werte
  • \\b, \\n, \\t, \\f, \\r
1
Jens Piegsa

Vielleicht möchten Sie einen Blick auf die Eclipse-Implementierung von Stringliteral werfen. 

0
stoilkov

Wenn Sie uncodierte Zeichen aus einer Datei lesen, wird es schwierig werden, da der String buchstäblich zusammen mit einem Escape für den hinteren Schrägstrich gelesen wird:

meine_datei.txt

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

.. more stuff

Wenn Sie Zeile 3 aus der Datei lesen, wird die Zeichenfolge/Zeile Folgendes enthalten:

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

und das Zeichen [] in der Zeichenfolge zeigt:

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

Commons StringUnescape entdeckt dies nicht für Sie (Ich habe es mit unescapeXml () versucht). Sie müssen es manuell wie hier beschrieben tun.

Die Unterzeichenfolge "\ u0020" sollte also zu einem einzelnen Zeichen '\ u0020' werden.

Wenn Sie dieses "\ u0020" jedoch für String.split("... ..... ..", columnDelimiterReadFromFile) verwenden, das intern regex verwendet, funktioniert es direkt, da der aus der Datei gelesene String mit Escapezeichen versehen wurde und perfekt für das Regex-Muster geeignet ist. (Verwirrt?)

0