xmltodict.unparse 未正确处理 CDATA
xmltodict.unparse is not handling CDATA properly
我正在尝试使用 xmltodict 将 XML 内容作为 python 对象进行操作,但我遇到了正确处理 CDATA 的问题。我想我在某处遗漏了一些东西,这是我的代码:
import xmltodict
data = """<node1>
<node2 id='test'><![CDATA[test]]></node2>
<node3 id='test'>test</node3>
</node1>"""
data = xmltodict.parse(data,force_cdata=True, encoding='utf-8')
print data
print xmltodict.unparse(data, pretty=True)
这是输出:
OrderedDict([(u'node1', OrderedDict([(u'node2', OrderedDict([(u'@id', u'test'), ('#text', u'test')])), (u'node3', OrderedDict([(u'@id', u'test'), ('#text', u'test')]))]))])
<?xml version="1.0" encoding="utf-8"?>
<node1>
<node2 id="test">test</node2>
<node3 id="test">test</node3>
</node1>
这里可以看到生成的node2缺少CDATA,而且node2和node3是一样的。但是,在输入中节点不同。
此致
我终于通过执行这个 monkey-patch 让它工作了。我仍然对它不太满意,这真的是一个 'hack' 这个功能应该包含在某个地方:
import xmltodict
def escape_hacked(data, entities={}):
if data[0] == '<' and data.strip()[-1] == '>':
return '<![CDATA[%s]]>' % data
return escape_orig(data, entities)
xml.sax.saxutils.escape = escape_hacked
然后 运行 您的 python 代码正常:
data = """<node1>
<node2 id='test'><![CDATA[test]]></node2>
<node3 id='test'>test</node3>
</node1>"""
data = xmltodict.parse(data,force_cdata=True, encoding='utf-8')
print data
print xmltodict.unparse(data, pretty=True)
解释一下,下行检测数据是否有效XML,然后在其周围添加 CDATA 标签:
if data[0] == '<' and data.strip()[-1] == '>':
return '<![CDATA[%s]]>' % data
此致
我想澄清一下,没有官方支持的方法来保留 CDATA
部分。
您可以在此处查看 the issue。
基于以上事实,你需要DIY。有两种方法:
首先,让我们创建一些辅助函数。
def cdata(s):
return '<![CDATA[' + s + ']]>'
def preprocessor(key, value):
'''Unneccessary if you've manually wrapped the values. For example,
xmltodict.unparse({
'node1': {'node2': '<![CDATA[test]]>', 'node3': 'test'}
})
'''
if key in KEEP_CDATA_SECTION:
if isinstance(value, dict) and '#text' in value:
value['#text'] = cdata(value['#text'])
else:
value = cdata(value)
return key, value
- 取消转义被转义 XML
import xmltodict
from xml.sax.saxutils import unescape
KEEP_CDATA_SECTION = ['node2']
out_xml = xmltodict.unparse(data, preprocessor=preprocessor)
out_xml = unescape(out_xml) # not safe !
你不应该在不可信的数据上尝试,因为这种方法不仅对字符数据进行了转义,而且还对节点的属性进行了转义。
- 子类化
XMLGenerator
为了缓解 unescape()
的安全问题,我们可以删除 XMLGenerator
中的 escape()
调用,这样就不需要再次对 XML 进行转义。
class XMLGenerator(xmltodict.XMLGenerator):
def characters(self, content):
if content:
self._finish_pending_start_element()
self._write(content) # also not safe, but better !
xmltodict.XMLGenerator = XMLGenerator
这不是 hack,所以它不会改变 xmltodict
除了 unparse()
之外的其他行为。更重要的是,它不会污染内置库 xml
.
对于一线粉丝
xmltodict.XMLGenerator.characters = xmltodict.XMLGenerator.ignorableWhitespace # now, it is a hack !
更进一步,你可以像下面这样直接将字符数据包裹在XMLGenerator
中。
class XMLGenerator(xmltodict.XMLGenerator):
def characters(self, content):
if content:
self._finish_pending_start_element()
self._write(cdata(content))
从现在开始,每个有字符数据的节点都将保留CDATA
部分。
我正在尝试使用 xmltodict 将 XML 内容作为 python 对象进行操作,但我遇到了正确处理 CDATA 的问题。我想我在某处遗漏了一些东西,这是我的代码:
import xmltodict
data = """<node1>
<node2 id='test'><![CDATA[test]]></node2>
<node3 id='test'>test</node3>
</node1>"""
data = xmltodict.parse(data,force_cdata=True, encoding='utf-8')
print data
print xmltodict.unparse(data, pretty=True)
这是输出:
OrderedDict([(u'node1', OrderedDict([(u'node2', OrderedDict([(u'@id', u'test'), ('#text', u'test')])), (u'node3', OrderedDict([(u'@id', u'test'), ('#text', u'test')]))]))])
<?xml version="1.0" encoding="utf-8"?>
<node1>
<node2 id="test">test</node2>
<node3 id="test">test</node3>
</node1>
这里可以看到生成的node2缺少CDATA,而且node2和node3是一样的。但是,在输入中节点不同。
此致
我终于通过执行这个 monkey-patch 让它工作了。我仍然对它不太满意,这真的是一个 'hack' 这个功能应该包含在某个地方:
import xmltodict
def escape_hacked(data, entities={}):
if data[0] == '<' and data.strip()[-1] == '>':
return '<![CDATA[%s]]>' % data
return escape_orig(data, entities)
xml.sax.saxutils.escape = escape_hacked
然后 运行 您的 python 代码正常:
data = """<node1>
<node2 id='test'><![CDATA[test]]></node2>
<node3 id='test'>test</node3>
</node1>"""
data = xmltodict.parse(data,force_cdata=True, encoding='utf-8')
print data
print xmltodict.unparse(data, pretty=True)
解释一下,下行检测数据是否有效XML,然后在其周围添加 CDATA 标签:
if data[0] == '<' and data.strip()[-1] == '>':
return '<![CDATA[%s]]>' % data
此致
我想澄清一下,没有官方支持的方法来保留 CDATA
部分。
您可以在此处查看 the issue。
基于以上事实,你需要DIY。有两种方法:
首先,让我们创建一些辅助函数。
def cdata(s):
return '<![CDATA[' + s + ']]>'
def preprocessor(key, value):
'''Unneccessary if you've manually wrapped the values. For example,
xmltodict.unparse({
'node1': {'node2': '<![CDATA[test]]>', 'node3': 'test'}
})
'''
if key in KEEP_CDATA_SECTION:
if isinstance(value, dict) and '#text' in value:
value['#text'] = cdata(value['#text'])
else:
value = cdata(value)
return key, value
- 取消转义被转义 XML
import xmltodict
from xml.sax.saxutils import unescape
KEEP_CDATA_SECTION = ['node2']
out_xml = xmltodict.unparse(data, preprocessor=preprocessor)
out_xml = unescape(out_xml) # not safe !
你不应该在不可信的数据上尝试,因为这种方法不仅对字符数据进行了转义,而且还对节点的属性进行了转义。
- 子类化
XMLGenerator
为了缓解 unescape()
的安全问题,我们可以删除 XMLGenerator
中的 escape()
调用,这样就不需要再次对 XML 进行转义。
class XMLGenerator(xmltodict.XMLGenerator):
def characters(self, content):
if content:
self._finish_pending_start_element()
self._write(content) # also not safe, but better !
xmltodict.XMLGenerator = XMLGenerator
这不是 hack,所以它不会改变 xmltodict
除了 unparse()
之外的其他行为。更重要的是,它不会污染内置库 xml
.
对于一线粉丝
xmltodict.XMLGenerator.characters = xmltodict.XMLGenerator.ignorableWhitespace # now, it is a hack !
更进一步,你可以像下面这样直接将字符数据包裹在XMLGenerator
中。
class XMLGenerator(xmltodict.XMLGenerator):
def characters(self, content):
if content:
self._finish_pending_start_element()
self._write(cdata(content))
从现在开始,每个有字符数据的节点都将保留CDATA
部分。