用ASCII近似值替换Unicode标点符号

26

我正在用Java编写一个程序,读取一些文本文件,并希望替换一些Unicode字符为ASCII近似字符。这些文件最终将被分成句子,然后输入给OpenNLP。 OpenNLP无法识别Unicode字符,并且在许多符号上结果不正确(它会将“girl's”标记为“girl”和“'s”,但如果它是Unicode引号,则会将其视为单个标记)。

例如,源句子可能包含Unicode方向性引号U2018(‘),我想将其转换为U0027(')。最终,我将删除剩余的Unicode。

我知道这样做会丢失信息,而且我知道我可以编写正则表达式来转换每个这样的符号,但我想知道是否有可重用的代码可以将其中一些符号转换。

以下是我已经写好的代码,但我肯定会出错/漏掉某些东西等:

    // double quotation (")
    replacements.add(new Replacement(Pattern.compile("[\u201c\u201d\u201e\u201f\u275d\u275e]"), "\""));

    // single quotation (')
    replacements.add(new Replacement(Pattern.compile("[\u2018\u2019\u201a\u201b\u275b\u275c]"), "'"));

replacements是一个自定义类,我稍后会对其进行处理并应用替换。

    for (Replacement replacement : replacements) {
         text = replacement.pattern.matcher(text).replaceAll(r.replacement);
    }

正如你所看到的,我必须找到:

  • 左单引号
  • 右单引号
  • 单低9引号(这是什么/我应该替换它吗?)
  • 单高反转9引号(这是什么/我应该替换它吗?)

你是在寻找某种语言的库和/或示例代码吗?还是你正在寻找一个现成的Unicode字符到ASCII近似值的映射?我不确定正则表达式和可重用代码之间的区别是什么。 - Mu Mind
我正在寻找一个Java库。我可以编写正则表达式,但我肯定会在这个过程中错过一些东西。我想知道是否有其他人已经为我做出了决策。你读过《GEB》、《Mu Mind》吗? - schmmd
这些Unicode链接已失效。 - user833970
7个回答

16

4
我将那个列表翻译成了Scala,并放在这里:https://gist.github.com/dirkgr/6349f379740880209475 - Dirk Groeneveld
@schmmd在下面有一个更全面的版本。 - Dirk Groeneveld

8

我跟随@marek-stoj的链接创建了一个Scala应用程序,它可以清除字符串中的Unicode字符并保持字符串长度不变。它会删除变音符号(重音符号)并使用@marek-stoj建议的映射将非ASCII Unicode字符转换为其ASCII近似值。

import java.text.Normalizer

object Asciifier {
  def apply(string: String) = {
    var cleaned = string
      for ((unicode, ascii) <- substitutions) {
        cleaned = cleaned.replaceAll(unicode, ascii)
      }

    // convert diacritics to a two-character form (NFD)
    // http://docs.oracle.com/javase/tutorial/i18n/text/normalizerapi.html
    cleaned = Normalizer.normalize(cleaned, Normalizer.Form.NFD)

    // remove all characters that combine with the previous character
    // to form a diacritic.  Also remove control characters.
    // http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html
    cleaned.replaceAll("[\\p{InCombiningDiacriticalMarks}\\p{Cntrl}]", "")

    // size must not change
    require(cleaned.size == string.size)

    cleaned
  }

  val substitutions = Set(
      (0x00AB, '"'),
      (0x00AD, '-'),
      (0x00B4, '\''),
      (0x00BB, '"'),
      (0x00F7, '/'),
      (0x01C0, '|'),
      (0x01C3, '!'),
      (0x02B9, '\''),
      (0x02BA, '"'),
      (0x02BC, '\''),
      (0x02C4, '^'),
      (0x02C6, '^'),
      (0x02C8, '\''),
      (0x02CB, '`'),
      (0x02CD, '_'),
      (0x02DC, '~'),
      (0x0300, '`'),
      (0x0301, '\''),
      (0x0302, '^'),
      (0x0303, '~'),
      (0x030B, '"'),
      (0x030E, '"'),
      (0x0331, '_'),
      (0x0332, '_'),
      (0x0338, '/'),
      (0x0589, ':'),
      (0x05C0, '|'),
      (0x05C3, ':'),
      (0x066A, '%'),
      (0x066D, '*'),
      (0x200B, ' '),
      (0x2010, '-'),
      (0x2011, '-'),
      (0x2012, '-'),
      (0x2013, '-'),
      (0x2014, '-'),
      (0x2015, '-'),
      (0x2016, '|'),
      (0x2017, '_'),
      (0x2018, '\''),
      (0x2019, '\''),
      (0x201A, ','),
      (0x201B, '\''),
      (0x201C, '"'),
      (0x201D, '"'),
      (0x201E, '"'),
      (0x201F, '"'),
      (0x2032, '\''),
      (0x2033, '"'),
      (0x2034, '\''),
      (0x2035, '`'),
      (0x2036, '"'),
      (0x2037, '\''),
      (0x2038, '^'),
      (0x2039, '<'),
      (0x203A, '>'),
      (0x203D, '?'),
      (0x2044, '/'),
      (0x204E, '*'),
      (0x2052, '%'),
      (0x2053, '~'),
      (0x2060, ' '),
      (0x20E5, '\\'),
      (0x2212, '-'),
      (0x2215, '/'),
      (0x2216, '\\'),
      (0x2217, '*'),
      (0x2223, '|'),
      (0x2236, ':'),
      (0x223C, '~'),
      (0x2264, '<'),
      (0x2265, '>'),
      (0x2266, '<'),
      (0x2267, '>'),
      (0x2303, '^'),
      (0x2329, '<'),
      (0x232A, '>'),
      (0x266F, '#'),
      (0x2731, '*'),
      (0x2758, '|'),
      (0x2762, '!'),
      (0x27E6, '['),
      (0x27E8, '<'),
      (0x27E9, '>'),
      (0x2983, '{'),
      (0x2984, '}'),
      (0x3003, '"'),
      (0x3008, '<'),
      (0x3009, '>'),
      (0x301B, ']'),
      (0x301C, '~'),
      (0x301D, '"'),
      (0x301E, '"'),
      (0xFEFF, ' ')).map { case (unicode, ascii) => (unicode.toChar.toString, ascii.toString) }
}

1
你有一个错误:replaceAll不会改变字符串。你需要将replaceAll的结果赋值给cleaned - slawek

7
每个Unicode字符都被分配了一个类别。引号有两个单独的类别:

有了这些列表,如果您想手动编写正则表达式,您应该能够适当地处理所有引号。

Java Character.getType给出了字符的类别,例如FINAL_QUOTE_PUNCTUATION

现在,您可以获取每个(标点符号)字符的类别,并将其替换为ASCII中的适当补充。

你可以相应地使用其他标点符号类别。在'标点符号, 其他'中有一些字符,例如PRIME ,您可能也希望用撇号替换它们。

我只能使用自定义映射,尽可能多地定义字符,因为基本字符分配的Unicode类别似乎不足。例如,基本的单引号和双引号字符(例如,您使用键盘在记事本中键入的字符)被归类为“其他标点符号”,而不是您期望它们被归类为“初始标点符号”和“结束标点符号”的类别。 - Triynko
@Triynko - 问题在于:只有一个“正常”的(ASCII)单引号和一个双引号,因此将其标记为 INITIALFINAL 引号标点也是错误的。 - Stephen P

3

虽然这并不完全回答了你的问题,但是你可以将Unicode文本转换为US-ASCII,用“?”符号替换非ASCII字符。

String input = "aáeéiíoóuú"; // 10 chars.

Charset ch = Charset.forName("US-ASCII");
CharsetEncoder enc = ch.newEncoder();
enc.onUnmappableCharacter(CodingErrorAction.REPLACE);
enc.replaceWith(new byte[]{'?'});

ByteBuffer out = null;

try {
    out = enc.encode(CharBuffer.wrap(input));
} catch (CharacterCodingException e) { 
    /* ignored, shouldn't happen */ 
}

String outStr = ch.decode(out).toString();

// Prints "a?e?i?o?u?"
System.out.println(outStr);

1
我使用Normalizer.normalize(text, Normalizer.Form.NFD)来去除变音符号,然后再使用Pattern.compile("\p{InCombiningDiacriticalMarks}+")进行替换。 - schmmd
使用这种解决方案,基本的标点符号如引号应该被映射到ASCII引号,但实际上并没有被正确映射。许多其他Unicode字符,你可能会认为“这基本上与这个ASCII字符是一样的”,也不会被正确映射。因此,我认为使用一个包含所有合理替换的自定义映射将会取得更好的结果。 - Triynko

3

2
我为类似的替换所做的事情是创建一个Map(通常是HashMap),其中Unicode字符作为键,它们的替代品作为值。
伪代码-Java; for循环取决于您将用作方法参数的字符容器的类型,例如String、CharSequence等。
StringBuilder output = new StringBuilder();
for (each Character 'c' in inputString)
{
    Character replacement = xlateMap.get( c );
    output.append( replacement != null ? replacement : c );
}
return output.toString();

地图中的任何内容都将被替换,而地图中没有的内容将保持不变并复制到输出。


1
String lstring = "my string containing all different simbols";

lstring = lstring.replaceAll("\u2013", "-")
    .replaceAll("\u2014", "-")
    .replaceAll("\u2015", "-")
    .replaceAll("\u2017", "_")
    .replaceAll("\u2018", "\'")
    .replaceAll("\u2019", "\'")
    .replaceAll("\u201a", ",")
    .replaceAll("\u201b", "\'")
    .replaceAll("\u201c", "\"")
    .replaceAll("\u201d", "\"")
    .replaceAll("\u201e", "\"")
    .replaceAll("\u2026", "...")
    .replaceAll("\u2032", "\'")
    .replaceAll("\u2033", "\"");

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