如何从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+D83D
errors 似乎消失了,也许还有其他人(我有一个巨大的语料库,所以我不确定)。我仍然得到的是U+FFFD
、U+FE0F
、U+203C
和U+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字符的主要内容,如果未能解决你的问题,请参考以下文章
如何从python中的unicode字符串中删除除数字和“,”之外的所有字符?
Flex TextArea - 从 Word 复制/粘贴 - xml 解析中的 unicode 字符无效