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

Posted

技术标签:

【中文标题】如何从java中的字符串中删除无效的unicode字符【英文标题】:How to remove non-valid unicode characters from strings in java 【发布时间】:2016-02-16 18:18:50 【问题描述】:

我正在使用CoreNLP Neural Network Dependency Parser 解析一些社交媒体内容。不幸的是,根据fileformat.info,该文件包含的字符不是有效的 unicode 字符或 unicode 替换字符。例如,U+D83D 或 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("\\pC", ""); 来删除这些字符。 document 这里只是作为字符串的文档。但这没有帮助。

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

更新(11 月 16 日):

为了完整起见,我应该提一下,我提出这个问题只是为了通过预处理文件来避免大量错误消息。 CoreNLP 只是忽略了它无法处理的字符,所以这不是问题。

【问题讨论】:

replaceAll 方法创建一个新的String;它不会修改document。你有没有做document = document.replaceAll(...)(或其他东西来捕获返回值)? 我在DocumentProcessor类的实例化中使用了这一行:DocumentPreprocessor tokenizer = new DocumentPreprocessor(new StringReader(document.replaceAll("\\pC", ""))); 【参考方案1】:

就像你有一个字符串一样

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

这会解决你的问题

【讨论】:

上面写着String literal is not properly closed by a double-quote 所有\u 需要双重转义 -> \\u 嗯,好的,成功了。 U+D83Derrors 似乎消失了,也许还有其他人(我有一个巨大的语料库,所以我不确定)。我仍然得到的是U+FFFDU+FE0FU+203CU+3010。至少我在匆忙中没有看到其他任何东西。我怎样才能摆脱那些?另一件事,你能具体说明删除了什么吗?我想确保没有任何我不想被删除的内容被删除。【参考方案2】:

删除特定的不需要的字符:

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

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

更新

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

根据您的示例和始终良好的资源Regular Expressions Site 中指出的 unicode 类,我认为您可以尝试一种独特的 only-good-pass 方法,例如:

document.replaceAll("[^\\pL\\pN\\pZ\\pSm\\pSc\\pSk\\pPi\\pPf\\pPc\\pMc]","")

这个正则表达式删除任何不是:

\pL: 任何语言的一封信 \pN:一个数字 \pZ: 任何类型的空格或不可见的分隔符 \pSm\pSc\pSk: 数学、货币或通用标记为单个字符 \pMc*:旨在与占用额外空间的另一个字符组合的字符(许多东方语言中的元音符号)。 \pPi\pPf\pPc*:开头引号、结尾引号、单词连接符(即下划线)

*:我认为这些组也可以出于 CoreNPL 的目的而被删除。

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

【讨论】:

感谢您的更新。不过,我认为这可能太多了。例如,一个问题是U+3010 (fileformat.info/info/unicode/char/3010/index.htm),它属于Ps 组(任何类型的左括号)。但是在我的情况下,是否也不需要删除 (, [ 或 ?在我开始删除我不想删除的内容之前,我宁愿忍受错误消息并让 CoreNLP 自己完成这项工作。跨度> 使用过滤器测试CoreNPL提供的输出是否存在差异(可能是这种情况,也可能不是)。作为白名单,您始终可以像"[^\\pL..\\(\\)\\[\\]\\\\)]" 一样简单地将要保存的字符添加到列表中。 是的,你是对的。可能是我问题的最佳解决方案。谢谢!【参考方案3】:

在某种程度上,Mukesh Kumar 和 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() 命令。完整的代码sn -p是

// 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 上打开issue 的原因。

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

11 月 27 日更新

Christopher Manning 刚刚回复了我打开的GitHub Issue。使用类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替换为其他选项。我在引用曼宁:

"(...) 完整的六个选项集,组合了是否记录无、第一个或全部警告,以及是否删除它们或将它们作为单个字符标记包含在输出中:noneDelete、firstDelete , allDelete, noneKeep, firstKeep, allKeep。”

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

【讨论】:

干得好,我已经更新了我的答案以使用单一的“不在允许的 unicode-group 之一中”方法来优化流程。试试看并阅读相关文档。等待官方回复选择性地完善它,我认为可能是最好的方法。【参考方案4】:

在我们做 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();

【讨论】:

以上是关于如何从java中的字符串中删除无效的unicode字符的主要内容,如果未能解决你的问题,请参考以下文章

从java中的字符串中删除无效的XML字符

如何从python中的unicode字符串中删除除数字和“,”之外的所有字符?

Flex TextArea - 从 Word 复制/粘贴 - xml 解析中的 unicode 字符无效

具有无效字符的java xml

从文本文件中删除 Unicode 字符 - sed ,其他 Bash/shell 方法

从 Python 字符串中删除零宽度空格 unicode 字符