Вопрос-ответ

How to unescape a Java string literal in Java?

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

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

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

Переведено автоматически
Ответ 1

Проблема

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


  • Он забывает о \0 для null.

  • Восьмеричный литерал вообще не обрабатывается.

  • Он не может обрабатывать виды экранирования, допускаемые java.util.regex.Pattern.compile() и всем, что его использует, включая \a, \e и особенно \cX.

  • Он не поддерживает логические кодовые точки Unicode по номерам, только для UTF-16.

  • Это похоже на код UCS-2, а не на код UTF-16: они используют устаревший charAt интерфейс вместо codePoint interface , таким образом распространяя заблуждение, что 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);
}

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

Ответ 2

Вы можете использовать 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 обрабатывает экранирования в Юникоде с помощью единицы u, но не восьмеричные экранирования или экранирования в Юникоде с помощью посторонних u символов.

    /* 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, но могут выражать только значения в Юникоде \u0000 через \u00FF, поэтому экранирование в Юникоде обычно предпочтительнее.


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

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


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


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


Если ваша строка может содержать экранирование в Юникоде с помощью extraneous u, то вам также может потребоваться предварительная обработка этого перед использованием StringEscapeUtils.

В качестве альтернативы вы можете попробовать написать свой собственный unescaper строкового литерала 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();
}
Ответ 4

String#translateEscapes

Современная Java предлагает этот метод String#translateEscapes.

Эта функция появилась в Java 15 после предварительного просмотра в Java 13 и 14.

Эта функция отменяет экранирование:


  • Однобуквенные комбинации, такие как \t для табуляции и \r для возврата каретки.

  • Восьмеричные числа \0 через \377 как эквивалент кодовой точки

Этот метод не переводит экранирующие символы Unicode, такие как "\u2022" для МАРКИРОВАННОГО символа ().

java string