Skip to main content
 首页 » 编程设计

python之在 Python 3 中使用 SAX 解析器解析 XML

2025年05月04日9cloudgamer

我正在尝试将一些代码移植到 Python 3,该代码将 xml.sax.make_parser 函数创建的解析器作为第二个参数传递给 xml.dom.minidom.parseString 解析 XML 文档。

在 Python 3 中,解析器似乎无法将 XML 文档解析为 bytes,但在解析之前我无法知道 XML 文档的编码。演示:

import xml.sax 
import xml.dom.minidom 
 
def try_parse(input, parser=None): 
    try: 
        xml.dom.minidom.parseString(input, parser) 
    except Exception as ex: 
        print(ex) 
    else: 
        print("OK") 
 
euro = u"\u20AC" # U+20AC EURO SIGN 
xml_utf8 = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>" 
xml_cp1252 = b"<?xml version=\"1.0\" encoding=\"windows-1252\"?>" 
 
test_cases = [ 
    b"<a>" + euro.encode("utf-8") + b"</a>", 
    u"<a>" + euro + u"</a>", 
    xml_utf8 + b"<a>" + euro.encode("utf-8") + b"</a>", 
    xml_cp1252 + b"<a>" + euro.encode("cp1252") + b"</a>", 
] 
 
for i, case in enumerate(test_cases, 1): 
    print("%d: %r" % (i, case)) 
    try_parse(case) 
    try_parse(case, xml.sax.make_parser()) 

python 2:

1: '<a>\xe2\x82\xac</a>' 
OK 
OK 
2: u'<a>\u20ac</a>' 
'ascii' codec can't encode character u'\u20ac' in position 3: ordinal not in range(128) 
'ascii' codec can't encode character u'\u20ac' in position 3: ordinal not in range(128) 
3: '<?xml version="1.0" encoding="utf-8"?><a>\xe2\x82\xac</a>' 
OK 
OK 
4: '<?xml version="1.0" encoding="windows-1252"?><a>\x80</a>' 
OK 
OK 

python 3:

1: b'<a>\xe2\x82\xac</a>' 
OK 
initial_value must be str or None, not bytes 
2: '<a>€</a>' 
OK 
OK 
3: b'<?xml version="1.0" encoding="utf-8"?><a>\xe2\x82\xac</a>' 
OK 
initial_value must be str or None, not bytes 
4: b'<?xml version="1.0" encoding="windows-1252"?><a>\x80</a>' 
OK 
initial_value must be str or None, not bytes 

如您所见,默认解析器能够很好地处理 bytes,但我需要 SAX 解析器来处理参数实体。这个问题有什么解决方案吗(除了在解析之前尝试猜测 bytes 的编码)?

请您参考如下方法:

我好像找到问题的原因了。 xml.dom.minidom.parseString 调用 xml.dom.pulldom.parseString 如果提供解析器(通过 _do_pulldom_parse)然后尝试构造解析时保存 XML 文档的 StringIO。将 StringIO 换成 BytesIO 可以解决问题,所以我想我会使用以下解决方法:

from io import StringIO, BytesIO 
 
def parseMaybeBytes(string, parser): 
    bufsize = len(string) 
    stream_class = BytesIO if isinstance(string, bytes) else StringIO 
    buf = stream_class(string) 
    return xml.dom.pulldom.DOMEventStream(buf, parser, bufsize) 
 
def parseString(string, parser=None): 
    """Parse a file into a DOM from a string.""" 
    if parser is None: 
        return xml.dom.minidom.parseString(string) 
 
    return xml.dom.minidom._do_pulldom_parse(parseMaybeBytes, (string,), 
                                             {'parser': parser})