如何在Java中从字符串中删除非有效的Unicode字符

8

我正在使用CoreNLP神经网络依存解析器来解析一些社交媒体内容。不幸的是,该文件包含了一些根据fileformat.info不属于有效Unicode字符或Unicode替换字符的字符,例如U+D83DU+FFFD。如果文件中存在这些字符,则CoreNLP会响应类似于以下错误消息的消息:

Nov 15, 2015 5:15:38 PM edu.stanford.nlp.process.PTBLexer next
WARNING: Untokenizable: ? (U+D83D, decimal: 55357)

根据this的回答,我尝试使用document.replaceAll("\\p{C}", "");来删除这些字符。这里的document只是一个字符串文档。但这并没有帮助。

在将字符串传递给coreNLP之前,我该如何将这些字符从中删除?

更新(11月16日):

为了完整起见,我应该提到,我问这个问题只是为了避免预处理文件时出现大量错误消息。CoreNLP只会忽略它无法处理的字符,因此这不是问题的原因。


replaceAll 方法会创建一个新的 String 对象,它不会修改原有的 document 对象。你是否使用了 document = document.replaceAll(...) 或者其他方式来获取返回值? - Ted Hopp
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Yanick Nedderhoff
4个回答

8

从某种程度上讲,Mukesh KumarGsusRecovery 提供的两个答案都有帮助,但并不完全正确。

document.replaceAll("[^\\u0009\\u000a\\u000d\\u0020-\\uD7FF\\uE000-\\uFFFD]", "");

看起来替换了所有无效字符。但是CoreNLP似乎不支持更多。我通过在整个语料库上运行解析器手动找出了它们,结果如下:

document.replaceAll("[\\uD83D\\uFFFD\\uFE0F\\u203C\\u3010\\u3011\\u300A\\u166D\\u200C\\u202A\\u202C\\u2049\\u20E3\\u300B\\u300C\\u3030\\u065F\\u0099\\u0F3A\\u0F3B\\uF610\\uFFFC]", "");

现在我运行了两个replaceAll()命令,然后将文档交给解析器。完整的代码片段如下:

// remove invalid unicode characters
String tmpDoc1 = document.replaceAll("[^\\u0009\\u000a\\u000d\\u0020-\\uD7FF\\uE000-\\uFFFD]", "");
// remove other unicode characters coreNLP can't handle
String tmpDoc2 = tmpDoc1.replaceAll("[\\uD83D\\uFFFD\\uFE0F\\u203C\\u3010\\u3011\\u300A\\u166D\\u200C\\u202A\\u202C\\u2049\\u20E3\\u300B\\u300C\\u3030\\u065F\\u0099\\u0F3A\\u0F3B\\uF610\\uFFFC]", "");
DocumentPreprocessor tokenizer = new DocumentPreprocessor(new StringReader(tmpDoc2));
for (List<HasWord> sentence : tokenizer) {
    List<TaggedWord> tagged = tagger.tagSentence(sentence);
    GrammaticalStructure gs = parser.predict(tagged);
    System.err.println(gs);
}

这不一定是所有不支持字符的完整列表,这也是我在GitHub上开了一个问题的原因。

请注意,CoreNLP会自动删除那些不支持的字符。我想预处理我的语料库的唯一原因是为了避免所有这些错误消息。

更新11月27日

Christopher Manning刚刚回答了我打开的GitHub问题。有几种方法可以使用类edu.stanford.nlp.process.TokenizerFactory;来处理这些字符。使用此代码示例对文档进行标记:

DocumentPreprocessor tokenizer = new DocumentPreprocessor(new StringReader(document));
TokenizerFactory<? extends HasWord> factory=null;
factory=PTBTokenizer.factory();
factory.setOptions("untokenizable=noneDelete");
tokenizer.setTokenizerFactory(factory);

for (List<HasWord> sentence : tokenizer) {
    // do something with the sentence
}

你可以将第4行中的noneDelete替换为其他选项。我引用了Manning的话:

"(...)完整的六个选项集合,包括是否记录警告信息以及删除还是将它们作为单个字符标记包含在输出中: noneDelete、firstDelete、allDelete、noneKeep、firstKeep、allKeep."

这意味着,为了保留字符而不获取所有这些错误消息,最好的方法是使用选项noneKeep。这种方式比任何尝试删除这些字符更加优雅。

干得好,我已经更新了我的答案,使用单个“不在允许的Unicode组之一”方法来优化该过程。尝试并阅读相关文档。等待官方回复以选择性地完善它,我认为这可能是最好的方法。 - Giuseppe Ricupero

3

使用以下代码删除特定的不需要字符:

document.replaceAll("[\\uD83D\\uFFFD\\uFE0F\\u203C\\u3010]", "");

如果你发现其他不需要的字符,只需按照相同的模式添加到列表中即可。
更新:
Unicode字符由正则表达式引擎分为7个宏组(和多个子组),由一个字母(宏组)或两个字母(子组)标识。
基于你的示例和始终很好的资源正则表达式网站中指定的Unicode类别,我认为你可以尝试一个唯一的“仅限良好通过”的方法,如下所示:
document.replaceAll("[^\\p{L}\\p{N}\\p{Z}\\p{Sm}\\p{Sc}\\p{Sk}\\p{Pi}\\p{Pf}\\p{Pc}\\p{Mc}]","")

这个正则表达式会移除非以下内容的所有字符:

  • \p{L}: 任何语言中的字母
  • \p{N}: 数字
  • \p{Z}: 任何类型的空格或不可见分隔符
  • \p{Sm}\p{Sc}\p{Sk}: 数学、货币或通用标记作为单个字符
  • \p{Mc}*: 旨在与另一个占用额外空间的字符组合的字符(许多东方语言中的元音符号)。
  • \p{Pi}\p{Pf}\p{Pc}*: 开始引号、结束引号、单词连接符(即下划线)

*: 我认为这些组也可以被删除,以便用于CoreNPL。

这样,您只需要一个单一的正则表达式过滤器,就可以处理具有相同目的的字符组(而不是单个情况)。


谢谢更新。我认为这可能太多了。例如,一个问题是“U+3010”(http://www.fileformat.info/info/unicode/char/3010/index.htm),它属于组“Ps”(任何类型的开括号)。但是,左括号、中括号或大括号也会被不必要地删除吗?在我开始删除我不想要的东西之前,我宁愿忍受错误消息并让CoreNLP自己完成工作。 - Yanick Nedderhoff
测试一下使用过滤器(CoreNPL)提供的输出是否存在差异(也许是这样,也许不是)。作为一个白名单,你可以简单地将想要保留的字符添加到列表中,如"[^\\p{L}..\\(\\)\\[\\]\\{\\})]" - Giuseppe Ricupero
是的,你说得对。可能是解决我的问题的最佳方案。谢谢! - Yanick Nedderhoff

1

就像您有一个字符串一样

String xml = "...."; xml = xml.replaceAll("[^\u0009\u000a\u000d\u0020-\uD7FF\uE000-\uFFFD]", "");

这将解决您的问题。


它说:“字符串文字未用双引号正确关闭”。 - Yanick Nedderhoff
2
所有的\u都需要双重转义 -> \\u - Giuseppe Ricupero
嗯,好的,这样做就可以了。 U+D83D错误似乎已经消失了,也许其他错误也没有了(我有一个巨大的语料库,所以不确定)。 我仍然得到U+FFFDU+FE0FU+203CU+3010。 至少在匆忙中我没有看到其他的。 我该如何摆脱这些? 另外一件事,你能具体说明删除了什么吗? 我想确保没有删除我不想删除的内容。 - Yanick Nedderhoff

0
观察到在进行replaceAll时可能会对其他地方产生负面影响。因此,我建议仅在非BPM字符的情况下替换字符,方式如下:
private String removeNonBMPCharacters(final String input) {
    StringBuilder strBuilder = new StringBuilder();
    input.codePoints().forEach((i) -> {
        if (Character.isSupplementaryCodePoint(i)) {
            strBuilder.append("?");
        } else {
            strBuilder.append(Character.toChars(i));
        }
    });
    return strBuilder.toString();
}

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