将扩展 ASCII 或 Unicode 转换为等效的 7 位 ASCII (<128),包括特殊字符
Convert Extended ASCII or Unicode to 7-bit ASCII (<128) equivalent including special characters
如何将 Java 中的字符从扩展 ASCII 或 Unicode 转换为它们的 7 位 ASCII 等效字符,包括特殊字符,如打开 (“
0x93) 和关闭 (”
0x94) 引用到一个简单的双引号 ("
0x22) 例如。或者类似地破折号 (–
0x96) 到连字符减号 (-
0x2D)。我发现 Stack Overflow questions 与此类似,但答案似乎只处理重音并忽略特殊字符。
例如我希望“Caffè – Peña”
转换为"Caffe - Pena"
。
但是当我使用 java.text.Normalizer:
String sample = "“Caffè – Peña”";
System.out.println(Normalizer.normalize(sample, Normalizer.Form.NFD)
.replaceAll("\p{InCombiningDiacriticalMarks}", ""));
输出是
“Caffe – Pena”
为了阐明我的需求,我正在与使用 EBCDIC 编码的 IBM i Db2 数据库进行交互。例如,如果用户粘贴从 Word 或 Outlook 复制的字符串,我指定的字符等字符将转换为 SUB(EBCDIC 中的 0x3F,ASCII 中的 0x1A)。这会导致很多不必要的头痛。我正在寻找一种方法来清理字符串,以便尽可能少地丢失信息。
评论者说您的问题是“主观的”(不是 opinion-based 的意思,而是每个人的具体要求与其他人的略有不同)或定义不明确或本质上不可能。 .. 在技术上是正确的。
但是你正在寻找可以改善这种情况的实际做法,这也是完全有效的。
平衡实施难度与结果准确性的最佳点是将您已经找到的内容与 less-negative 评论者的建议结合在一起:
- 使用标准规范化程序处理变音符号和其他“标准规范化”字符。
- 使用您自己的映射处理其他所有内容(可能包括 Unicode General_Category property,但最终可能需要包括您自己的 hand-picked 将特定字符替换为其他特定字符)。
以上可能涵盖“所有”未来案例,具体取决于数据的来源。或者足够接近您可以实现并完成它的所有内容。如果你想增加一些健壮性,并且会在一段时间内维护这个过程,那么你也可以列出你想要在清理结果中允许的所有字符,然后设置某种异常或日志记录机制,让您(或您的继任者)在出现新的未处理案例时发现它们,然后可用于改进映射的自定义部分。
您可以按照另一位评论者的建议,只使用 String.replace() 来替换引号字符,随着时间的推移,您可能会增加有问题的字符列表。
您还可以使用更通用的函数来替换或忽略任何无法编码的字符。例如:
private String removeUnrepresentableChars(final String _str, final String _encoding) throws CharacterCodingException, UnsupportedEncodingException {
final CharsetEncoder enccoder = Charset.forName(_encoding).newEncoder();
enccoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
ByteBuffer encoded = enccoder.encode(CharBuffer.wrap(_str));
return new String(encoded.array(), _encoding);
}
private String replaceUnrepresentableChars(final String _str, final String _encoding, final String _replacement) throws CharacterCodingException, UnsupportedEncodingException {
final CharsetEncoder encoder = Charset.forName(_encoding).newEncoder();
encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
encoder.replaceWith(_replacement.getBytes(_encoding));
ByteBuffer encoded = encoder.encode(CharBuffer.wrap(_str));
return new String(encoded.array(), _encoding);
}
例如,您可以用 _encoding
的“IBM-037”来调用它们。
但是,如果您的objective是希望丢失尽可能少的信息,您应该评估数据是否可以以 UTF-8 (CCSID 1208) 格式存储。这可以很好地处理智能引号和其他“特殊字符”。根据您的数据库和应用程序结构,这样的更改实施起来可能非常小,也可能非常大且有风险!但是要进行无损翻译,唯一的办法就是使用 unicode 风格,而 UTF-8 是最明智的。
经过一番挖掘,我找到了基于 this answer using org.apache.lucene.analysis.ASCIIFoldingFilter
的解决方案
我能找到的所有示例都使用方法的静态版本 foldToASCII as in this project:
private static String getFoldedString(String text) {
char[] textChar = text.toCharArray();
char[] output = new char[textChar.length * 4];
int outputPos = ASCIIFoldingFilter.foldToASCII(textChar, 0, output, 0, textChar.length);
text = new String(output, 0, outputPos);
return text;
}
但是那个静态方法有一个注释说
This API is for internal purposes only and might change in incompatible ways in the next release.
所以经过反复试验,我想出了这个避免使用静态方法的版本:
public static String getFoldedString(String text) throws IOException {
String output = "";
try (Analyzer analyzer = CustomAnalyzer.builder()
.withTokenizer(KeywordTokenizerFactory.class)
.addTokenFilter(ASCIIFoldingFilterFactory.class)
.build()) {
try (TokenStream ts = analyzer.tokenStream(null, new StringReader(text))) {
CharTermAttribute charTermAtt = ts.addAttribute(CharTermAttribute.class);
ts.reset();
if (ts.incrementToken()) output = charTermAtt.toString();
ts.end();
}
}
return output;
}
类似于我提供的答案 here。
这正是我所寻找的,并将字符转换为其 ASCII 7 位等效版本。
然而,通过进一步的研究我发现因为我主要处理 Windows-1252 编码并且由于 jt400 处理 ASCII <-> EBCDIC (CCSID 37) 转换的方式,如果 ASCII 字符串转换为 EBCDIC 并返回 ACSII,唯一丢失的字符是 0x80
到 0x9f
。受到 lucene's foldToASCII 处理方式的启发,我整理了以下仅处理这些情况的方法:
public static String replaceInvalidChars(String text) {
char input[] = text.toCharArray();
int length = input.length;
char output[] = new char[length * 6];
int outputPos = 0;
for (int pos = 0; pos < length; pos++) {
final char c = input[pos];
if (c < '\u0080') {
output[outputPos++] = c;
} else {
switch (c) {
case '\u20ac': //€ 0x80
output[outputPos++] = 'E';
output[outputPos++] = 'U';
output[outputPos++] = 'R';
break;
case '\u201a': //‚ 0x82
output[outputPos++] = '\'';
break;
case '\u0192': //ƒ 0x83
output[outputPos++] = 'f';
break;
case '\u201e': //„ 0x84
output[outputPos++] = '"';
break;
case '\u2026': //… 0x85
output[outputPos++] = '.';
output[outputPos++] = '.';
output[outputPos++] = '.';
break;
case '\u2020': //† 0x86
output[outputPos++] = '?';
break;
case '\u2021': //‡ 0x87
output[outputPos++] = '?';
break;
case '\u02c6': //ˆ 0x88
output[outputPos++] = '^';
break;
case '\u2030': //‰ 0x89
output[outputPos++] = 'p';
output[outputPos++] = 'e';
output[outputPos++] = 'r';
output[outputPos++] = 'm';
output[outputPos++] = 'i';
output[outputPos++] = 'l';
break;
case '\u0160': //Š 0x8a
output[outputPos++] = 'S';
break;
case '\u2039': //‹ 0x8b
output[outputPos++] = '\'';
break;
case '\u0152': //Œ 0x8c
output[outputPos++] = 'O';
output[outputPos++] = 'E';
break;
case '\u017d': //Ž 0x8e
output[outputPos++] = 'Z';
break;
case '\u2018': //‘ 0x91
output[outputPos++] = '\'';
break;
case '\u2019': //’ 0x92
output[outputPos++] = '\'';
break;
case '\u201c': //“ 0x93
output[outputPos++] = '"';
break;
case '\u201d': //” 0x94
output[outputPos++] = '"';
break;
case '\u2022': //• 0x95
output[outputPos++] = '-';
break;
case '\u2013': //– 0x96
output[outputPos++] = '-';
break;
case '\u2014': //— 0x97
output[outputPos++] = '-';
break;
case '\u02dc': //˜ 0x98
output[outputPos++] = '~';
break;
case '\u2122': //™ 0x99
output[outputPos++] = '(';
output[outputPos++] = 'T';
output[outputPos++] = 'M';
output[outputPos++] = ')';
break;
case '\u0161': //š 0x9a
output[outputPos++] = 's';
break;
case '\u203a': //› 0x9b
output[outputPos++] = '\'';
break;
case '\u0153': //œ 0x9c
output[outputPos++] = 'o';
output[outputPos++] = 'e';
break;
case '\u017e': //ž 0x9e
output[outputPos++] = 'z';
break;
case '\u0178': //Ÿ 0x9f
output[outputPos++] = 'Y';
break;
default:
output[outputPos++] = c;
break;
}
}
}
return new String(Arrays.copyOf(output, outputPos));
}
因为事实证明我真正的问题是 Windows-1252 到 Latin-1 (ISO-8859-1) 的翻译,这里是 supporting material 显示 Windows -1252 到上述方法中使用的 Unicode 翻译,最终得到 Latin-1 编码。
如何将 Java 中的字符从扩展 ASCII 或 Unicode 转换为它们的 7 位 ASCII 等效字符,包括特殊字符,如打开 (“
0x93) 和关闭 (”
0x94) 引用到一个简单的双引号 ("
0x22) 例如。或者类似地破折号 (–
0x96) 到连字符减号 (-
0x2D)。我发现 Stack Overflow questions 与此类似,但答案似乎只处理重音并忽略特殊字符。
例如我希望“Caffè – Peña”
转换为"Caffe - Pena"
。
但是当我使用 java.text.Normalizer:
String sample = "“Caffè – Peña”";
System.out.println(Normalizer.normalize(sample, Normalizer.Form.NFD)
.replaceAll("\p{InCombiningDiacriticalMarks}", ""));
输出是
“Caffe – Pena”
为了阐明我的需求,我正在与使用 EBCDIC 编码的 IBM i Db2 数据库进行交互。例如,如果用户粘贴从 Word 或 Outlook 复制的字符串,我指定的字符等字符将转换为 SUB(EBCDIC 中的 0x3F,ASCII 中的 0x1A)。这会导致很多不必要的头痛。我正在寻找一种方法来清理字符串,以便尽可能少地丢失信息。
评论者说您的问题是“主观的”(不是 opinion-based 的意思,而是每个人的具体要求与其他人的略有不同)或定义不明确或本质上不可能。 .. 在技术上是正确的。
但是你正在寻找可以改善这种情况的实际做法,这也是完全有效的。
平衡实施难度与结果准确性的最佳点是将您已经找到的内容与 less-negative 评论者的建议结合在一起:
- 使用标准规范化程序处理变音符号和其他“标准规范化”字符。
- 使用您自己的映射处理其他所有内容(可能包括 Unicode General_Category property,但最终可能需要包括您自己的 hand-picked 将特定字符替换为其他特定字符)。
以上可能涵盖“所有”未来案例,具体取决于数据的来源。或者足够接近您可以实现并完成它的所有内容。如果你想增加一些健壮性,并且会在一段时间内维护这个过程,那么你也可以列出你想要在清理结果中允许的所有字符,然后设置某种异常或日志记录机制,让您(或您的继任者)在出现新的未处理案例时发现它们,然后可用于改进映射的自定义部分。
您可以按照另一位评论者的建议,只使用 String.replace() 来替换引号字符,随着时间的推移,您可能会增加有问题的字符列表。
您还可以使用更通用的函数来替换或忽略任何无法编码的字符。例如:
private String removeUnrepresentableChars(final String _str, final String _encoding) throws CharacterCodingException, UnsupportedEncodingException {
final CharsetEncoder enccoder = Charset.forName(_encoding).newEncoder();
enccoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
ByteBuffer encoded = enccoder.encode(CharBuffer.wrap(_str));
return new String(encoded.array(), _encoding);
}
private String replaceUnrepresentableChars(final String _str, final String _encoding, final String _replacement) throws CharacterCodingException, UnsupportedEncodingException {
final CharsetEncoder encoder = Charset.forName(_encoding).newEncoder();
encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
encoder.replaceWith(_replacement.getBytes(_encoding));
ByteBuffer encoded = encoder.encode(CharBuffer.wrap(_str));
return new String(encoded.array(), _encoding);
}
例如,您可以用 _encoding
的“IBM-037”来调用它们。
但是,如果您的objective是希望丢失尽可能少的信息,您应该评估数据是否可以以 UTF-8 (CCSID 1208) 格式存储。这可以很好地处理智能引号和其他“特殊字符”。根据您的数据库和应用程序结构,这样的更改实施起来可能非常小,也可能非常大且有风险!但是要进行无损翻译,唯一的办法就是使用 unicode 风格,而 UTF-8 是最明智的。
经过一番挖掘,我找到了基于 this answer using org.apache.lucene.analysis.ASCIIFoldingFilter
的解决方案我能找到的所有示例都使用方法的静态版本 foldToASCII as in this project:
private static String getFoldedString(String text) {
char[] textChar = text.toCharArray();
char[] output = new char[textChar.length * 4];
int outputPos = ASCIIFoldingFilter.foldToASCII(textChar, 0, output, 0, textChar.length);
text = new String(output, 0, outputPos);
return text;
}
但是那个静态方法有一个注释说
This API is for internal purposes only and might change in incompatible ways in the next release.
所以经过反复试验,我想出了这个避免使用静态方法的版本:
public static String getFoldedString(String text) throws IOException {
String output = "";
try (Analyzer analyzer = CustomAnalyzer.builder()
.withTokenizer(KeywordTokenizerFactory.class)
.addTokenFilter(ASCIIFoldingFilterFactory.class)
.build()) {
try (TokenStream ts = analyzer.tokenStream(null, new StringReader(text))) {
CharTermAttribute charTermAtt = ts.addAttribute(CharTermAttribute.class);
ts.reset();
if (ts.incrementToken()) output = charTermAtt.toString();
ts.end();
}
}
return output;
}
类似于我提供的答案 here。
这正是我所寻找的,并将字符转换为其 ASCII 7 位等效版本。
然而,通过进一步的研究我发现因为我主要处理 Windows-1252 编码并且由于 jt400 处理 ASCII <-> EBCDIC (CCSID 37) 转换的方式,如果 ASCII 字符串转换为 EBCDIC 并返回 ACSII,唯一丢失的字符是 0x80
到 0x9f
。受到 lucene's foldToASCII 处理方式的启发,我整理了以下仅处理这些情况的方法:
public static String replaceInvalidChars(String text) {
char input[] = text.toCharArray();
int length = input.length;
char output[] = new char[length * 6];
int outputPos = 0;
for (int pos = 0; pos < length; pos++) {
final char c = input[pos];
if (c < '\u0080') {
output[outputPos++] = c;
} else {
switch (c) {
case '\u20ac': //€ 0x80
output[outputPos++] = 'E';
output[outputPos++] = 'U';
output[outputPos++] = 'R';
break;
case '\u201a': //‚ 0x82
output[outputPos++] = '\'';
break;
case '\u0192': //ƒ 0x83
output[outputPos++] = 'f';
break;
case '\u201e': //„ 0x84
output[outputPos++] = '"';
break;
case '\u2026': //… 0x85
output[outputPos++] = '.';
output[outputPos++] = '.';
output[outputPos++] = '.';
break;
case '\u2020': //† 0x86
output[outputPos++] = '?';
break;
case '\u2021': //‡ 0x87
output[outputPos++] = '?';
break;
case '\u02c6': //ˆ 0x88
output[outputPos++] = '^';
break;
case '\u2030': //‰ 0x89
output[outputPos++] = 'p';
output[outputPos++] = 'e';
output[outputPos++] = 'r';
output[outputPos++] = 'm';
output[outputPos++] = 'i';
output[outputPos++] = 'l';
break;
case '\u0160': //Š 0x8a
output[outputPos++] = 'S';
break;
case '\u2039': //‹ 0x8b
output[outputPos++] = '\'';
break;
case '\u0152': //Œ 0x8c
output[outputPos++] = 'O';
output[outputPos++] = 'E';
break;
case '\u017d': //Ž 0x8e
output[outputPos++] = 'Z';
break;
case '\u2018': //‘ 0x91
output[outputPos++] = '\'';
break;
case '\u2019': //’ 0x92
output[outputPos++] = '\'';
break;
case '\u201c': //“ 0x93
output[outputPos++] = '"';
break;
case '\u201d': //” 0x94
output[outputPos++] = '"';
break;
case '\u2022': //• 0x95
output[outputPos++] = '-';
break;
case '\u2013': //– 0x96
output[outputPos++] = '-';
break;
case '\u2014': //— 0x97
output[outputPos++] = '-';
break;
case '\u02dc': //˜ 0x98
output[outputPos++] = '~';
break;
case '\u2122': //™ 0x99
output[outputPos++] = '(';
output[outputPos++] = 'T';
output[outputPos++] = 'M';
output[outputPos++] = ')';
break;
case '\u0161': //š 0x9a
output[outputPos++] = 's';
break;
case '\u203a': //› 0x9b
output[outputPos++] = '\'';
break;
case '\u0153': //œ 0x9c
output[outputPos++] = 'o';
output[outputPos++] = 'e';
break;
case '\u017e': //ž 0x9e
output[outputPos++] = 'z';
break;
case '\u0178': //Ÿ 0x9f
output[outputPos++] = 'Y';
break;
default:
output[outputPos++] = c;
break;
}
}
}
return new String(Arrays.copyOf(output, outputPos));
}
因为事实证明我真正的问题是 Windows-1252 到 Latin-1 (ISO-8859-1) 的翻译,这里是 supporting material 显示 Windows -1252 到上述方法中使用的 Unicode 翻译,最终得到 Latin-1 编码。