如何在Java中使用Javastring?
我正在使用Java处理一些Java源代码。 我正在提取string文字并将它们提供给一个带有String的函数。 问题是我需要将非转义版本的string传递给函数(即,这意味着将\n
转换为换行符, \\
转换为单个\
等)。
Java API中是否有这样的函数? 如果没有,我可以从某个图书馆获得这样的function吗? Java编译器显然需要做这个转换。
PS – 如果有人想知道:我试图在反编译的混淆Java文件中unobfuscatestring文字
编辑:你可以下载我在下面讨论的function的完整源代码 。 我也在这个答案中更详细地讨论它。
问题
这里给出的org.apache.commons.lang.StringEscapeUtils.unescapeJava()
作为“答案”真的没有什么帮助。
- 你必须提供装载另一个巨大的jar文件,用你不需要或想要的cruft的buttloads。
- 它有一个许可证。 有些人不想担心执照,不pipe它有多好或多坏。
- 它忘记关于
\0
为null。 - 它根本不处理八进制。
- 它不能处理
java.util.regex.Pattern.compile()
允许使用的各种转义,包括\a
,\e
,特别是\cX
。 - 它不支持按数字顺序的逻辑Unicode代码点,只能用于白痴UTF-16脑损伤。
- 它是由一些甚至不知道斜杠和反斜杠之间区别的血腥白痴写的。
- 源代码充满了恼人的回车。
- 它被写为需要
writer
参数,所以如果你不通过它,它仍然需要为输出创build一个虚拟的StringWriter
,然后将其转换回给你。 - 这看起来像UCS-2代码,而不是UTF-16代码:它们使用折旧的
charAt
接口而不是codePoint
接口,因此公布了一个Javachar
保证Unicode字符的错觉。 不是。 他们只能逃避这种对星界飞机的盲目性,因为没有UTF-16替代者会结束寻找他们正在寻找的任何东西。
像许多其他问题一样,他们对U+2F
和U+5C
编码名称的尴尬无知并没有给他们带来任何信心。 作为logging:
/ 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)
解决scheme
所以今天早上我终于厌倦了不能读取embedded式转义string。 我需要它来编写一个更大,更有意思的项目的testing套件:透明地将Java的不可思议的Unicode-无知正则expression式转换为可以使用\w
, \W
, \s
, \S
, \v
, \V
, \h
, \H
, \d
, \D
, \b
, \B
, \X
和\R
,并让它们在Unicode中正常工作。 我所做的只是重写模式string; 它仍然使用标准的java.util.regex.Pattern.compile()
函数进行编译,所以一切正常。 stringunescaper故意通过任何\b
,直到在调用转换器函数使Java正则expression式感知Unicode之前调用它,因为它必须处理边界意义上的\b
。
无论如何,这里是stringunescaper,尽pipe这两个string不那么有趣,但却没有解决OP的问题,而没有对Apache代码的所有烦恼。 它可以在一些地方处理一些紧缩,但是在午餐前几个小时我就快速地把它砍掉了,只是为了启动它,帮助驱动testing套件。 另一个function是更多的工作:昨天一整天都带着我,穿上它。
/* * * 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); }
正如任何人可以从上面的Java代码清楚地看到,我真的是一个C程序员 – Java是什么,但我最喜欢的语言。 恐怕我真的不得不在罗伯·派克(Rob Pike)的这个着名的公共静态空谈中站出来 。
“Nuff说。
无论如何,这只是一个早上的嘲弄,但如果它帮助别人,欢迎您 – 没有任何附加条件。 如果你改进它,我很乐意给你发邮件给我,但你当然不需要。
您可以使用Apache Commons Lang的StringEscapeUtils
String unescapeJava(String)
方法。
以下是一个示例代码片段:
String in = "a\\tb\\n\\\"c\\\""; System.out.println(in); // a\tb\n\"c\" String out = StringEscapeUtils.unescapeJava(in); System.out.println(out); // ab // "c"
实用程序类具有用于Java,Java Script,HTML,XML和SQL的转义string和非stringstring的方法。 它也有直接写入java.io.Writer
重载。
注意事项
它看起来像StringEscapeUtils
处理与一个u
Unicode转义,但不是八进制转义,或Unicode转义与无关的。
/* 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兼容,提供了八进制转义
\u00FF
,但是只能表示Unicode值\u0000
到\u00FF
,所以Unicode转义\u00FF
通常是首选的。
如果您的string可以包含八进制转义符,则可能需要先将其转换为Unicode转义符,或使用其他方法。
无关的u
也logging如下:
Java编程语言规定了一种将以Unicode编写的程序转换为ASCII的标准方式,该程序将程序转换为可由基于ASCII的工具处理的格式。 转换包括通过添加一个额外的
u
将程序源文本中的所有Unicode转义转换为ASCII,例如,\uxxxx
成为\uuxxxx
,同时将源文本中的非ASCII字符转换为每个包含一个u的Unicode转义。这个转换后的版本对于Java编程语言的编译器同样是可以接受的,并且表示完全相同的程序。 通过将存在多个
u
的每个转义序列转换为一个具有一个u
的Unicode字符序列,同时将每个转义序列转换为相应的单个Unicode,可以从此ASCIIforms恢复确切的Unicode源。字符。
如果你的string可以包含与你无关的Unicode转义符,那么在使用StringEscapeUtils
之前,你可能还需要StringEscapeUtils
进行预处理。
或者,您可以尝试从头开始编写自己的Javastringunescaper,确保遵循确切的JLS规范。
参考
- JLS 3.3 Unicode转义
- JLS 3.10.6字符和string文字的转义序列
遇到类似的问题,不满意提出的解决scheme,并自己实施这一个。
在Github上也可以作为Gist使用:
/** * Unescapes a string that contains standard Java escape sequences. * <ul> * <li><strong>\b \f \n \r \t \" \'</strong> : * BS, FF, NL, CR, TAB, double and single quote.</li> * <li><strong>\X \XX \XXX</strong> : Octal character * specification (0 - 377, 0x00 - 0xFF).</li> * <li><strong>\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(); }
我知道这个问题是旧的,但我想要一个解决scheme,不涉及那些包括JRE6以外的库(即Apache Commons是不可接受的),我想出了一个简单的解决scheme,使用内置的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.
我有点晚了,但我想我会提供我的解决scheme,因为我需要相同的function。 我决定使用Java编译器API,使其更慢,但使结果准确。 基本上我活着创build一个类,然后返回结果。 这里是方法:
public static String[] unescapeJavaStrings(String... escaped) { //class name final String className = "Temp" + System.currentTimeMillis(); //build the source final StringBuilder source = new StringBuilder(100 + escaped.length * 20). append("public class ").append(className).append("{\n"). append("\tpublic static String[] getStrings() {\n"). append("\t\treturn new String[] {\n"); for (String string : escaped) { source.append("\t\t\t\""); //we escape non-escaped quotes here to be safe // (but something like \\" will fail, oh well for now) for (int i = 0; i < string.length(); i++) { char chr = string.charAt(i); if (chr == '"' && i > 0 && string.charAt(i - 1) != '\\') { source.append('\\'); } source.append(chr); } source.append("\",\n"); } source.append("\t\t};\n\t}\n}\n"); //obtain compiler final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //local stream for output final ByteArrayOutputStream out = new ByteArrayOutputStream(); //local stream for error ByteArrayOutputStream err = new ByteArrayOutputStream(); //source file JavaFileObject sourceFile = new SimpleJavaFileObject( URI.create("string:///" + className + Kind.SOURCE.extension), Kind.SOURCE) { @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return source; } }; //target file final JavaFileObject targetFile = new SimpleJavaFileObject( URI.create("string:///" + className + Kind.CLASS.extension), Kind.CLASS) { @Override public OutputStream openOutputStream() throws IOException { return out; } }; //file manager proxy, with most parts delegated to the standard one JavaFileManager fileManagerProxy = (JavaFileManager) Proxy.newProxyInstance( StringUtils.class.getClassLoader(), new Class[] { JavaFileManager.class }, new InvocationHandler() { //standard file manager to delegate to private final JavaFileManager standard = compiler.getStandardFileManager(null, null, null); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("getJavaFileForOutput".equals(method.getName())) { //return the target file when it's asking for output return targetFile; } else { return method.invoke(standard, args); } } }); //create the task CompilationTask task = compiler.getTask(new OutputStreamWriter(err), fileManagerProxy, null, null, null, Collections.singleton(sourceFile)); //call it if (!task.call()) { throw new RuntimeException("Compilation failed, output:\n" + new String(err.toByteArray())); } //get the result final byte[] bytes = out.toByteArray(); //load class Class<?> clazz; try { //custom class loader for garbage collection clazz = new ClassLoader() { protected Class<?> findClass(String name) throws ClassNotFoundException { if (name.equals(className)) { return defineClass(className, bytes, 0, bytes.length); } else { return super.findClass(name); } } }.loadClass(className); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } //reflectively call method try { return (String[]) clazz.getDeclaredMethod("getStrings").invoke(null); } catch (Exception e) { throw new RuntimeException(e); } }
它需要一个数组,所以你可以unescape分批。 所以下面的简单testing成功了:
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"); } }
我遇到了同样的问题,但是我并不喜欢我在这里find的任何解决scheme。 所以,我写了一个使用匹配器遍历string的字符来查找和replace转义序列。 该解决scheme假设格式正确的input。 也就是说,它高兴地跳过无意义的转义,并且它解码换行和回车的Unicode转义(否则不能出现在字符字面量或string面量中,这是由于这些文字的定义和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似乎没有受到所接受的解决scheme中指出的弱点。 也就是说, StringEscapeUtils
似乎处理Unicode转义的八进制转义和多个u
字符。 这意味着除非你有一些燃烧的理由来避免Apache Commons,否则你应该使用它而不是我的解决scheme(或者其他任何解决scheme)。
如果你正在从一个文件中读取unicode转义的字符,那么你将会有一段艰难的时间,因为这个string会被逐字读出来,
my_file.txt
Blah blah... Column delimiter=; Word delimiter=\u0020 #This is just unicode for whitespace .. more stuff
在这里,当你从文件中读取第3行时,string/行将具有:
"Word delimiter=\u0020 #This is just unicode for whitespace"
并且string中的char []将显示:
{...., '=', '\\', 'u', '0', '0', '2', '0', ' ', '#', 't', 'h', ...}
Commons的StringUnescape不会为你忽略这个(我试过unescapeXml())。 您必须按照此处所述手动执行此操作。
所以,子string“\ u0020”应该变成1个单个字符'\ u0020'
但是如果你使用这个“\ u0020”去做String.split("... ..... ..", columnDelimiterReadFromFile)
,它真的在内部使用regex,它会直接工作,因为从文件读取的string被转义并在正则expression式中使用是完美的! (困惑?)
您可能想看看Stringliteral的Eclipse实现。