如何从 java 中的字符串中删除无效的 unicode 字符

How to remove non-valid unicode characters from strings in java

我正在使用CoreNLP Neural Network Dependency Parser to parse some social media content. Unfortunately, the file contains characters which are, according to fileformat.info, not valid unicode characters or unicode replacement characters. These are for example U+D83D or U+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 只是忽略它无法处理的字符,所以这不是问题。

就像你有一个字符串一样

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

这将解决您的问题

删除特定不需要的字符:

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

如果您发现其他不需要的字符,只需将具有相同架构的字符添加到列表中即可。

更新:

unicode 字符被正则表达式引擎分成 7 个宏组(和几个子组),由一个字母(宏组)或两个字母(子组)标识。

我的论点基于你的例子和 unicode 类 在总是好的资源 Regular Expressions Site 中指出我认为你可以尝试一个独特的 only-good-pass 这样的方法:

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}: 任何一种白色space 或隐形分隔符
  • \p{Sm}\p{Sc}\p{Sk}:作为单个字符的数学、货币或通用标记
  • \p{Mc}*:一个字符打算与另一个占用额外space(许多东方语言中的元音符号)的字符组合。
  • \p{Pi}\p{Pf}\p{Pc}*:开头引号、结尾引号、单词连接符(即下划线)

*:我认为出于 CoreNPL 的目的,这些团体也有资格被删除。

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

在某种程度上,Mukesh Kumar and GsusRecovery 提供的两个答案都有帮助,但并不完全正确。

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);
}

不过,这不一定是不支持字符的完整列表,这就是我打开 issue on GitHub.

的原因

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

11 月 27 日更新

Christopher Manning just answered the GitHub Issue我开了。有几种方法可以使用 class 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 替换为其他选项。我引用曼宁的话:

"(...) the complete set of six options combining whether to log a warning for none, the first, or all, and whether to delete them or to include them as single character tokens in the output: noneDelete, firstDelete, allDelete, noneKeep, firstKeep, allKeep."

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

观察到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();
}