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

3

我正在尝试将一些代码移植到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)的编码之外,是否有其他解决方法?

1个回答

1
我似乎找到了问题的原因。如果提供了解析器(通过_do_pulldom_parse),xml.dom.minidom.parseString会调用xml.dom.pulldom.parseString,然后尝试构建一个StringIO来保存XML文档进行解析。将那个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})

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