Python中过滤非法XML Unicode字符的快速方法是什么?

31

XML规范列出了一些非法或“不建议使用”的Unicode字符。给定一个字符串,如何从中删除所有非法字符?

我想到了下面的正则表达式,但它有点冗长。

illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]')
clean = illegal_xml_re.sub('', dirty)

Python 2.5不支持Unicode字符超过0xFFFF,因此无需过滤这些字符。


Python的最大Unicode代码点取决于编译时的配置,可以通过检查sys.maxunicode来确定。 - u0b34a0f6ae
你说得对。我想这可能更加复杂了。 - itsadok
2
在我的电脑上,使用这个正则表达式处理一个2.3MB的字符串只需要0.34秒。这对我来说似乎相当快。 - Robert Rossney
当您提供完整运行代码时,它可以帮助其他人帮助您!import redirty = "我可能是错误的。" - MasterControlProgram
3个回答

22

最近,我们(Trac XmlRpcPlugin 的维护者)被告知上述正则表达式在Python窄版本中剥夺了代理对(请参见th:comment:13:ticket:11050)。另一种替代方法是使用以下正则表达式(请参见th:changeset:13729)。

_illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), 
                        (0x7F, 0x84), (0x86, 0x9F), 
                        (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)] 
if sys.maxunicode >= 0x10000:  # not narrow build 
        _illegal_unichrs.extend([(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), 
                                 (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF), 
                                 (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF), 
                                 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), 
                                 (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF), 
                                 (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF), 
                                 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), 
                                 (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF)]) 

_illegal_ranges = ["%s-%s" % (unichr(low), unichr(high)) 
                   for (low, high) in _illegal_unichrs] 
_illegal_xml_chars_RE = re.compile(u'[%s]' % u''.join(_illegal_ranges)) 

p.s. 请查看有关替代字符的这篇文章,解释它们的作用。

更新,以避免匹配(替换)0x0D,因为它是一个有效的XML字符


请注意,代理对在W3C XML规范的合法字符中被明确排除,因此包含它们的任何XML都不能保证在其他库中正确解析。但是,由于通常您会将XML序列化为utf-8或utf-16,所以问题应该会消失。只需避免使用utf-32即可。 - itsadok
我已更新正则表达式以匹配0x0D字符。请参见[th:ticket:11635](http://trac-hacks.org/ticket/11635),[th:changeset:13776](http://trac-hacks.org/changeset/13776)和[XML字符范围定义](http://www.w3.org/TR/REC-xml/#NT-Char)。 - Olemis Lang
对于一个清理过的字符串,可以这样写: myString = _illegal_xml_chars_RE.sub("", myString) - MasterControlProgram
对于像我这样被包含了许多范围的人来说,其中许多并不是无效的 XML 字符,只是不建议使用 - Indigenuity

9

这是Olemis Lang为Python 3提供的更新版本答案:

import re
import sys

illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F),
                   (0x7F, 0x84), (0x86, 0x9F),
                   (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]
if sys.maxunicode >= 0x10000:  # not narrow build
    illegal_unichrs.extend([(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF),
                            (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF),
                            (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
                            (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF),
                            (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF),
                            (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
                            (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF),
                            (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF)])

illegal_ranges = [fr'{chr(low)}-{chr(high)}' for (low, high) in illegal_unichrs]
xml_illegal_character_regex = '[' + ''.join(illegal_ranges) + ']'
illegal_xml_chars_re = re.compile(xml_illegal_character_regex)
# filtered_string = illegal_xml_chars_re.sub('', original_string)

4

你也可以使用Unicode的translate方法来删除选定的码点。然而,你所拥有的映射很大(2128个码点),这可能会比仅使用正则表达式慢得多:

ranges = [(0, 8), (0xb, 0x1f), (0x7f, 0x84), (0x86, 0x9f), (0xd800, 0xdfff), (0xfdd0, 0xfddf), (0xfffe, 0xffff)]
# fromkeys creates  the wanted (codepoint -> None) mapping
nukemap = dict.fromkeys(r for start, end in ranges for r in range(start, end+1))
clean = dirty.translate(nukemap)

1
经过一些测试,这似乎比正则表达式慢得多,特别是对于大字符串。 - itsadok

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