将符号和重音字母转换为英文字母

142
问题在于,正如您所知,在Unicode表中有数千个字符in the Unicode chart,我想将所有相似的字符转换为英文字母。
例如,这里是一些转换:
ҥ->H
Ѷ->V
Ȳ->Y
Ǭ->O
Ƈ->C
tђє Ŧค๓เℓy --> the Family
...

我看到有20多个A/a字母的版本,但我不知道如何对它们进行分类。它们就像是草堆中的针一样难以找到。

完整的Unicode字符列表位于 http://www.ssec.wisc.edu/~tomw/java/unicode.htmlhttp://unicode.org/charts/charindex.html。只需尝试向下滚动并查看字母的变化。

我该如何使用Java将它们全部转换?请帮帮我 :(


请参考这个问题:https://dev59.com/zXVC5IYBdhLWcg3wliGe - 这个主题还应该有其他一些相关的问题,但我目前找不到它们。 - schnaader
1
你的第三个例子应该是Ȳ → Y吗? - Dour High Arch
2
为什么你想要这样做?如果我们知道你的总体目标,我们可能会更有帮助。 - David Thornley
这个对话以前已经完成了 - 请参见上面的@schnaader。 - dkretz
寻找 Unihandecode - n611x007
显示剩余4条评论
12个回答

209

如何在.NET中移除变音符号(重音符号)?转载我的文章。

这种方法在Java中有效(仅用于去除变音符号,也称为重音符号)。

它基本上将所有带重音符号的字符转换为它们的无重音符号副本,然后跟随它们的组合变音符号。现在可以使用正则表达式去除变音符号。

import java.text.Normalizer;
import java.util.regex.Pattern;

public String deAccent(String str) {
    String nfdNormalizedString = Normalizer.normalize(str, Normalizer.Form.NFD); 
    Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
    return pattern.matcher(nfdNormalizedString).replaceAll("");
}

5
InCombiningDiacriticalMarks不能将所有的Cyrillic字符都转换。例如,Општина Богомила没有被转换。如果能将其转换为Opstina Bogomila或其他类似的形式就好了。 - iwein
14
完全无法转换拼音。它只是删除分解的变音符号(“重音符号”)。前一步(Form.NFD)将á分解为a + ',即将带重音的字符分解为一个不带重音的字符和一个变音符号。这将把西里尔字母Ѽ转换为Ѡ,但不会再进一步处理。 - MSalters
1
George在http://glaforge.appspot.com/article/how-to-remove-accents-from-a-string#IDComment315485401中发表了一篇文章,建议使用\\p{IsM}而不是\\p{InCombiningDiacriticalMarks}。请注意,我还没有测试过它。 - ATorras
2
\p{IsM} 似乎无法处理像á ó ú ñ é í这样的西班牙口音。相反,"\p{InCombiningDiacriticalMarks}+ 对此很有效。 - Loic
@Tajchert 你的问题是无效的,因为Ł不能被分解。有问题的并不是字符规范化器,而是使用它来去除重音符号。 - Karol S
显示剩余4条评论

78

这个解决方案太棒了。它也适用于希腊语!谢谢。 - Tom
5
翻译自英语到中文。仅返回翻译后的文本:波兰字符 ł 和 Ł 的翻译不完美,缺失: 输入:ŚŻÓŁĄĆĘŹąółęąćńŃ 输出:SZOŁACEZaołeacnN - Robert Gonciarz
2
这是一个不错的工具,但由于它的代码与被接受的答案中显示的代码完全相同,并且您不想添加对Commons Lang的依赖项,因此您可以使用上述片段。 - polaretto
1
在我的情况下使用Apache Common:不要将Đ转换为D。 - Hoang
@Hoang,或许这是一个发送拉取请求的好机会,Robert :) - Ondra Žižka

19

试图“全部转换”是解决问题的错误方法。

首先,你需要了解你所尝试做的事情的局限性。正如其他人指出的那样,变音符号存在的原因是它们实际上是该语言字母表中具有自己含义/声音等的独特字母:删除这些标记就像在英文单词中替换随机字母一样。这还没有考虑到西里尔文字和其他基于文本脚本的文本(例如阿拉伯文),它们根本无法“转换”为英文。

如果你必须出于某种原因转换字符,则唯一合理的方法是首先缩小手头任务的范围。考虑输入源-如果你正在为“西方世界”编写应用程序,那么解析阿拉伯字符的可能性很小。同样,Unicode字符集包含数百个数学和绘画符号:用户没有(简单)的方法直接输入这些符号,因此可以假设它们可以被忽略。

通过采取这些逻辑步骤,你可以将要解析的可能字符数量减少到可以使用基于字典的查找/替换操作的程度。然后,创建字典可能需要一些微不足道的工作,并且执行替换的任务微不足道。如果你的语言支持本地Unicode字符(如Java),并且正确优化静态结构,则这样的查找和替换通常非常快速。

这来自于开发一款应用程序的经验,该应用程序需要允许终端用户搜索包含变音符号字符的文献数据。在我们的情况下,查找数组花费了大约1个工作日的时间,以覆盖所有西欧语言的所有变音标记。


iAn谢谢回答。实际上我没有处理阿拉伯语言或类似的东西。你知道有些人使用变音符号作为有趣的字符,我必须尽可能地删除它。例如,我在示例中说了“tђє Ŧค๓เℓy-->the Family”转换,但似乎很难完全转换。然而,我们可以用简单的方法进行转换“òéışöç->oeisoc”。但是具体的做法是什么?创建数组并手动替换吗?还是这种语言有关于这个问题的本地函数? - ahmet alp balkan

16
由于将“the Family”转换为“tђє Ŧค๓เℓy”的编码实际上是随机的,不遵循任何可以通过涉及Unicode代码点的信息来解释的算法,因此没有一般方法可以通过算法来解决这个问题。您需要构建Unicode字符到它们所类似的拉丁字符的映射。您可能可以通过对表示Unicode代码点的实际字形进行一些智能机器学习来做到这一点。但我认为手动构建该映射的工作量会比这更大,特别是如果您有足够的示例可用于构建映射。需要澄清的是:一些替换实际上可以通过Unicode数据来解决(如其他答案所示),但有些字母根本没有合理的与它们类似的拉丁字符关联。例如:“ђ”(U+0452 CYRILLIC SMALL LETTER DJE)与“d”更相关,而不是“h”,但用于表示“h”。 “Ŧ”(U+0166 LATIN CAPITAL LETTER T WITH STROKE)与“T”有些相关(正如名称所示),但用于表示“F”。 “ค”(U+0E04 THAI CHARACTER KHO KHWAI)与任何拉丁字符都没有关系,在您的示例中用于表示“a”。

15

测试字符串: ÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝß

测试结果:

  • Apache Commons Lang3的输出: AAAAAÆCEEEEIIIIÐNOOOOOØUUUUYß
  • ICU4j的输出: AAAAAÆCEEEEIIIIÐNOOOOOØUUUUYß
  • JUnidecode的输出: AAAAAAECEEEEIIIIDNOOOOOOUUUUUss(对于 Ý 和另一个问题有影响)
  • Unidecode的输出: AAAAAAECEEEEIIIIDNOOOOOOUUUUYss

最后的选择是最好的。


2
@mehmet 只需按照 https://github.com/xuender/unidecode 上的自述文件操作即可。导入依赖项后,应该类似于 Unidecode.decode("ÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝß")。 - cactuschibre
这是一个有趣的测试。但如果您写出使用了哪些不同库中的方法,那就更好了! - Lii

8
原始请求已经得到回答。
然而,我会在下面的答案中提供通用的转写代码,以将任何字符集转写为Java中的拉丁/英语。
转写的朴素含义是:目标字符集中的翻译字符串听起来像原始形式的字符串。如果我们想将任何字符集转写为拉丁字母(英语字母),那么ICU4(Java中的ICU4J库)可以胜任。
以下是Java中的代码片段:
    import com.ibm.icu.text.Transliterator; //ICU4J library import

    public static String TRANSLITERATE_ID = "NFD; Any-Latin; NFC";
    public static String NORMALIZE_ID = "NFD; [:Nonspacing Mark:] Remove; NFC";

    /**
    * Returns the transliterated string to convert any charset to latin.
    */
    public static String transliterate(String input) {
        Transliterator transliterator = Transliterator.getInstance(TRANSLITERATE_ID + "; " + NORMALIZE_ID);
        String result = transliterator.transliterate(input);
        return result;
    }

6
如果需要将"òéışöç->oeisoc"转换,你可以使用以下作为起点:
public class AsciiUtils {
    private static final String PLAIN_ASCII =
      "AaEeIiOoUu"    // grave
    + "AaEeIiOoUuYy"  // acute
    + "AaEeIiOoUuYy"  // circumflex
    + "AaOoNn"        // tilde
    + "AaEeIiOoUuYy"  // umlaut
    + "Aa"            // ring
    + "Cc"            // cedilla
    + "OoUu"          // double acute
    ;

    private static final String UNICODE =
     "\u00C0\u00E0\u00C8\u00E8\u00CC\u00EC\u00D2\u00F2\u00D9\u00F9"             
    + "\u00C1\u00E1\u00C9\u00E9\u00CD\u00ED\u00D3\u00F3\u00DA\u00FA\u00DD\u00FD" 
    + "\u00C2\u00E2\u00CA\u00EA\u00CE\u00EE\u00D4\u00F4\u00DB\u00FB\u0176\u0177" 
    + "\u00C3\u00E3\u00D5\u00F5\u00D1\u00F1"
    + "\u00C4\u00E4\u00CB\u00EB\u00CF\u00EF\u00D6\u00F6\u00DC\u00FC\u0178\u00FF" 
    + "\u00C5\u00E5"                                                             
    + "\u00C7\u00E7" 
    + "\u0150\u0151\u0170\u0171" 
    ;

    // private constructor, can't be instanciated!
    private AsciiUtils() { }

    // remove accentued from a string and replace with ascii equivalent
    public static String convertNonAscii(String s) {
       if (s == null) return null;
       StringBuilder sb = new StringBuilder();
       int n = s.length();
       for (int i = 0; i < n; i++) {
          char c = s.charAt(i);
          int pos = UNICODE.indexOf(c);
          if (pos > -1){
              sb.append(PLAIN_ASCII.charAt(pos));
          }
          else {
              sb.append(c);
          }
       }
       return sb.toString();
    }

    public static void main(String args[]) {
       String s = 
         "The result : È,É,Ê,Ë,Û,Ù,Ï,Î,À,Â,Ô,è,é,ê,ë,û,ù,ï,î,à,â,ô,ç";
       System.out.println(AsciiUtils.convertNonAscii(s));
       // output : 
       // The result : E,E,E,E,U,U,I,I,A,A,O,e,e,e,e,u,u,i,i,a,a,o,c
    }
}

JDK 1.6 提供了 java.text.Normalizer 类,可用于此任务。

不幸的是,它无法处理像Æ这样的连字。 - Dour High Arch
如果您需要以不同的方式检测和处理变音符号类别(即在LaTeX中转义特殊字符),则此方法特别有用。 - Parker

5
"将任意Unicode转换为ASCII的问题在于字符的含义与文化有关。例如,“ß”对于讲德语的人应该转换为“ss”,而英语说话者可能会将其转换为“B”。
此外,Unicode为相同字形具有多个代码点。
总之,唯一的方法是创建一个包含每个Unicode字符和要转换为的ASCII字符的大型表。您可以通过使用带重音字符的标准化形式KD来简化字符,但并非所有字符都能标准化为ASCII。另外,Unicode不定义字形的哪些部分是“重音符号”。
以下是一个执行此操作的应用程序的小摘录:"
switch (c)
{
    case 'A':
    case '\u00C0':  //  À LATIN CAPITAL LETTER A WITH GRAVE
    case '\u00C1':  //  Á LATIN CAPITAL LETTER A WITH ACUTE
    case '\u00C2':  //  Â LATIN CAPITAL LETTER A WITH CIRCUMFLEX
    // and so on for about 20 lines...
        return "A";
        break;

    case '\u00C6'://  Æ LATIN CAPITAL LIGATURE AE
        return "AE";
        break;

    // And so on for pages...
}

我同意。你应该为你的应用程序和预期受众创建一个专门的转换字典。例如,对于西班牙语听众,我只会翻译ÁÉÍÓÚÜÑáéíóúü¿¡。 - Roberto Bonvallet
Roberto,有成千上万个字符,我无法手动完成这个任务。 - ahmet alp balkan
2
你使用的人类语言中有“数千”个字符?是日语吗?你期望将“どうしようとしていますか”转换为什么? - Dour High Arch
6
你提供的例子不太合适:U+00DF 拉丁小写字母"ß"与U+03B2 希腊小写字母"β"不是同一个Unicode字母。 - Joachim Sauer

4

虽然我来晚了,但今天遇到这个问题后,我发现这个答案非常好:

String asciiName = Normalizer.normalize(unicodeName, Normalizer.Form.NFD)
    .replaceAll("[^\\p{ASCII}]", "");

Reference: https://dev59.com/YmQo5IYBdhLWcg3wKcnH#16283863


小警告 - 它会移除 U+00DF 拉丁小写字母 "ß"。 - rafalmag
还有Æ...太糟糕了。 - cactuschibre

4
你可以尝试使用 unidecode,它可作为 Ruby gem CPAN 上的 Perl 模块 使用。简而言之,它工作原理是将每个 Unicode 代码点与 ASCII 字符或字符串相关联,并作为一个大型查找表来运作。

你可能能够从这些中获取一个查找表。 - Kathy Van Stone
这是一个很棒的软件包,但它是音译汉字,例如将“北”转换为“Bei”,因为在普通话中这个汉字发音就是这样。我认为提问者想要将字形转换为它们在英语中视觉上的相似之处。 - Dour High Arch
它确实对拉丁字符进行了转换,例如将â转换为a等。@ahmetalpbalkan 我同意Kathy的观点,你可以将其用作构建自己的查找表的资源,逻辑应该很简单。不幸的是,似乎没有Java版本。 - Daniel Vandersluis
@ahmetalpbalkan 这是 Java 版本的 unidecode - Jakub Jirutka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接