【问题标题】:xmltodict.unparse is not handling CDATA properlyxmltodict.unparse 未正确处理 CDATA
【发布时间】:2016-01-29 16:20:31
【问题描述】:

我正在尝试使用 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相同。但是,在输入中的节点是不同的。

问候

【问题讨论】:

标签: python xmltodict


【解决方案1】:

我想澄清一下,没有官方支持的方式来保留 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
  1. 取消转义转义的 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 !

你不应该在不受信任的数据上尝试,因为这种方法不仅对字符数据进行转义,而且对节点的属性进行转义。

  1. 子类化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部分。

【讨论】:

    【解决方案2】:

    我终于设法通过执行这个猴子补丁来让它工作。我对它仍然不是很满意,这真的是一个“黑客”,这个功能应该被正确地包含在某个地方:

    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
    

    问候

    【讨论】:

      猜你喜欢
      • 2011-05-30
      • 1970-01-01
      • 2020-07-23
      • 2014-08-31
      • 2014-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-12
      相关资源
      最近更新 更多