Javaを使用して、Javaソースコードを処理しています。文字列リテラルを抽出し、文字列を受け取る関数にそれらを供給しています。問題は、関数への文字列(つまり、これは\n
を改行に、\\
単一の\
など)。
Java API内にこれを行う関数がありますか?そうでない場合は、いくつかのライブラリからそのような機能を取得できますか?明らかにJava変換。
誰かが知りたい場合は、逆コンパイルされた難読化されたJavaファイルで文字列リテラルを難読化解除しようとしています。
ここで別の答えとして与えられたorg.Apache.commons.lang.StringEscapeUtils.unescapeJava()
は、実際にはほとんど役に立ちません。
\0
_を忘れます。Java.util.regex.Pattern.compile()
で許可されている種類のエスケープ、および_\a
_、_\e
_、特に_\cX
_を含むそれを使用するすべてのものを処理することはできません。charAt
インターフェイスの代わりに、減価償却されたcodePoint
インターフェイスを使用するため、Java char
はUnicode文字を保持することが保証されています。そうではありません。 UTF-16のサロゲートが探しているものを探してしまうことはないので、彼らはこれで逃げます。私は、Apacheコードのすべての苛立ちなしでOPの質問を解決する文字列アンエスケーパーを書きました。
_/*
*
* 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);
}
_
それが他の人を助けるなら、あなたはそれを歓迎します-紐は付いていません。あなたがそれを改善するなら、私はあなたにあなたの改善を私にメールで送って欲しいが、あなたは確かにそうする必要はない。
String unescapeJava(String)
StringEscapeUtils
from Apache Commons Lang のメソッドを使用できます。
以下にスニペットの例を示します。
String in = "a\\tb\\n\\\"c\\\"";
System.out.println(in);
// a\tb\n\"c\"
String out = StringEscapeUtils.unescapeJava(in);
System.out.println(out);
// a b
// "c"
ユーティリティクラスには、Javaの文字列をエスケープおよびエスケープ解除するメソッド、Javaスクリプト、HTML、XML、およびSQL。また、 Java.io.Writer
に直接書き込むオーバーロードもあります。 。
StringEscapeUtils
は、1つのu
でUnicodeエスケープを処理しますが、8進エスケープ、または余分なu
sを使用した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からの引用:
8進エスケープはCとの互換性のために提供されていますが、Unicode値
\u0000
から\u00FF
のみを表現できるため、通常はUnicodeエスケープが優先されます。
文字列に8進エスケープを含めることができる場合は、最初にUnicodeエスケープに変換するか、別のアプローチを使用することができます。
無関係なu
も次のように文書化されています。
Javaプログラミング言語は、Unicodeで書かれたプログラムを、ASCIIベースで処理できる形式にプログラムを変更するASCIIに変換する標準的な方法を指定しますツール:変換には、プログラムのソーステキスト内のUnicodeエスケープをASCIIに追加することにより、余分な
u
--たとえば、\uxxxx
が\uuxxxx
-同時に、ソーステキスト内の非ASCII文字を、それぞれ1つのuを含むUnicodeエスケープに変換します。この変換されたバージョンは、Javaプログラミング言語のコンパイラに等しく受け入れられ、まったく同じプログラムを表します。正確なUnicodeソースは、このASCII複数の
u
が存在する各エスケープシーケンスを、u
が1つ少ないUnicode文字のシーケンスに変換すると同時に、単一のu
を持つ各エスケープシーケンスを対応する単一のUnicode文字。
余分なu
を含むUnicodeエスケープを文字列に含めることができる場合は、StringEscapeUtils
を使用する前にこれを前処理する必要があります。
別の方法として、独自のJava文字列リテラルunescaperをゼロから記述して、正確なJLS仕様に従うようにしてください。
同様の問題に出くわし、提示されたソリューションにも満足せず、自分でこれを実装しました。
Github の要点としても利用可能:
/**
* 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();
}
これを参照してください http://commons.Apache.org/lang/ :
StringEscapeUtils.unescapeJava(String str)
私はこの質問が古いことを知っていますが、含まれている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.
私はこれに少し遅れていますが、同じ機能が必要だったので、自分のソリューションを提供すると思いました。 JavaコンパイラAPIを使用することにしました。これにより、速度は遅くなりますが、結果は正確になります。基本的に、クラスを作成してから結果を返します。メソッドは次のとおりです。
public static String[] unescapeJavaStrings(String... escaped) {
//class name
final String className = "Temp" + System.currentTimeMillis();
//build the source
final StringBuilder source = new StringBuilder(100 + escaped.length * 20).
append("public class ").append(className).append("{\n").
append("\tpublic static String[] getStrings() {\n").
append("\t\treturn new String[] {\n");
for (String string : escaped) {
source.append("\t\t\t\"");
//we escape non-escaped quotes here to be safe
// (but something like \\" will fail, oh well for now)
for (int i = 0; i < string.length(); i++) {
char chr = string.charAt(i);
if (chr == '"' && i > 0 && string.charAt(i - 1) != '\\') {
source.append('\\');
}
source.append(chr);
}
source.append("\",\n");
}
source.append("\t\t};\n\t}\n}\n");
//obtain compiler
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//local stream for output
final ByteArrayOutputStream out = new ByteArrayOutputStream();
//local stream for error
ByteArrayOutputStream err = new ByteArrayOutputStream();
//source file
JavaFileObject sourceFile = new SimpleJavaFileObject(
URI.create("string:///" + className + Kind.SOURCE.extension), Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return source;
}
};
//target file
final JavaFileObject targetFile = new SimpleJavaFileObject(
URI.create("string:///" + className + Kind.CLASS.extension), Kind.CLASS) {
@Override
public OutputStream openOutputStream() throws IOException {
return out;
}
};
//file manager proxy, with most parts delegated to the standard one
JavaFileManager fileManagerProxy = (JavaFileManager) Proxy.newProxyInstance(
StringUtils.class.getClassLoader(), new Class[] { JavaFileManager.class },
new InvocationHandler() {
//standard file manager to delegate to
private final JavaFileManager standard =
compiler.getStandardFileManager(null, null, null);
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("getJavaFileForOutput".equals(method.getName())) {
//return the target file when it'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");
}
}
私は同じ問題に遭遇しましたが、ここで見つけた解決策に夢中になりませんでした。そこで、マッチャーを使用して文字列の文字を反復処理し、エスケープシーケンスを見つけて置換するものを作成しました。このソリューションは、適切にフォーマットされた入力を前提としています。つまり、無意味なエスケープを喜んでスキップし、改行とキャリッジリターンのUnicodeエスケープをデコードします(そうでなければ、リテラルの定義と=の翻訳フェーズの順序のために、文字リテラルまたは文字列リテラルに表示できません) Java source)。申し訳ありませんが、コードは簡潔にするために少し詰められています。
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
は8進数のエスケープとUnicodeエスケープの複数のu
文字を処理するようです。つまり、Apache Commonsを回避するための何らかの理由がない限り、おそらく私のソリューション(または他のソリューション)ではなく、それを使用する必要があります。
記録のために、Scalaを使用する場合、次のことができます。
StringContext.treatEscapes(escaped)
commons-lang3の_org.Apache.commons.lang3.StringEscapeUtils
_は、非推奨とマークされました。代わりに 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
_Stringliteral のEclipse実装をご覧ください。
ファイルから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)
を使用している場合、ファイルから読み取られた文字列がエスケープされ、正規表現パターンでの使用に最適であるため、直接動作します!! (混乱した?)