2008-10-06 154 views
32

我發現cElementTree的速度比xml.dom.minidom快30倍,我正在重寫我的XML編碼/解碼代碼。但是,我需要輸出包含CDATA部分的XML,並且似乎沒有辦法用ElementTree來實現這一點。如何使用ElementTree輸出CDATA

可以這樣做嗎?

+0

>我需要輸出包含CDATA部分的XML 爲什麼?這似乎是一個奇怪的要求。 – bortzmeyer 2008-10-15 12:14:57

+1

這是我的要求 - CDATA塊有時更易讀。 – grifaton 2010-09-06 22:39:35

+0

@bortzmeyer將它添加到KML(Google Maps XML文件)非常有用。 – 2016-06-23 11:59:31

回答

22

經過一番工作,我自己找到了答案。查看ElementTree.py源代碼,我發現對XML註釋和預處理指令進行了特殊處理。他們所做的是爲特殊元素類型創建一個工廠函數,該函數使用特殊(非字符串)標記值將其與常規元素區分開來。

def Comment(text=None): 
    element = Element(Comment) 
    element.text = text 
    return element 

隨後的ElementTree的_write功能實際輸出的XML,還有一種特殊情況處理的意見:

if tag is Comment: 
    file.write("<!-- %s -->" % _escape_cdata(node.text, encoding)) 

爲了支持CDATA部分,我創建一個名爲CDATA一個工廠函數,擴展ElementTree類並更改了_write函數以處理CDATA元素。

如果你想用CDATA部分解析XML然後用CDATA部分再輸出它,這仍然沒有幫助,但它至少允許你以編程方式用CDATA部分創建XML,這正是我需要的做。

該實現似乎與ElementTree和cElementTree一起工作。

import elementtree.ElementTree as etree 
#~ import cElementTree as etree 

def CDATA(text=None): 
    element = etree.Element(CDATA) 
    element.text = text 
    return element 

class ElementTreeCDATA(etree.ElementTree): 
    def _write(self, file, node, encoding, namespaces): 
     if node.tag is CDATA: 
      text = node.text.encode(encoding) 
      file.write("\n<![CDATA[%s]]>\n" % text) 
     else: 
      etree.ElementTree._write(self, file, node, encoding, namespaces) 

if __name__ == "__main__": 
    import sys 

    text = """ 
    <?xml version='1.0' encoding='utf-8'?> 
    <text> 
    This is just some sample text. 
    </text> 
    """ 

    e = etree.Element("data") 
    cdata = CDATA(text) 
    e.append(cdata) 
    et = ElementTreeCDATA(e) 
    et.write(sys.stdout, "utf-8") 
6

這不可能AFAIK ...這是可惜的。基本上,ElementTree模塊假設讀者是100%符合XML的,所以他們輸出一段作爲CDATA或其他生成等效文本的格式應該沒有關係。

請參閱Python郵件列表上的this thread以獲取更多信息。基本上,他們推薦使用某種基於DOM的XML庫。

+2

我不會稱之爲「可惜」。對於XML信息集(內容),「<![CDATA [&]]>」和「&」之間沒有區別......大多數XML解析器甚至不會讓您知道原始文檔中的內容。 – bortzmeyer 2008-10-15 12:13:55

+1

確實如此,但是一些數據可以在CDATA格式中更加高效地轉儲和分析。因此,無法通過這種方式告訴XML庫來處理它是一件痛苦的事情。 – 2008-10-15 21:02:26

6

其實這個代碼有缺陷,因爲你不趕]]>出現在數據要插入的CDATA

Is there a way to escape a CDATA end token in xml?

則應將其拆分爲兩個CDATA在這種情況下,分裂兩者之間的]]>

基本上data = data.replace("]]>", "]]]]><![CDATA[>")
(不一定正確,請確認)

1

在DOM(ATLEAST在第2級)的接口 DATASection和操作文檔:: createCDATASection。它們是 擴展接口,僅當實現支持 「xml」功能時才受支持。

從進口xml.dom的minidom命名

my_xmldoc = minidom命名。解析(XMLFILE)

my_xmldoc.createCDATASection(數據)

現在u有cadata節點添加它哪裏ü希望....

10

這裏是gooli的解決方案的一個變體使用Python 3.2的工作原理:

import xml.etree.ElementTree as etree 

def CDATA(text=None): 
    element = etree.Element('![CDATA[') 
    element.text = text 
    return element 

etree._original_serialize_xml = etree._serialize_xml 
def _serialize_xml(write, elem, qnames, namespaces): 
    if elem.tag == '![CDATA[': 
     write("\n<%s%s]]>\n" % (
       elem.tag, elem.text)) 
     return 
    return etree._original_serialize_xml(
     write, elem, qnames, namespaces) 
etree._serialize_xml = etree._serialize['xml'] = _serialize_xml 


if __name__ == "__main__": 
    import sys 

    text = """ 
    <?xml version='1.0' encoding='utf-8'?> 
    <text> 
    This is just some sample text. 
    </text> 
    """ 

    e = etree.Element("data") 
    cdata = CDATA(text) 
    e.append(cdata) 
    et = etree.ElementTree(e) 
    et.write(sys.stdout.buffer.raw, "utf-8") 
0

這是我的版本,它基於上面的gooli和amaury的答案。它適用於ElementTree 1.2.6和1.3.0,它們使用完全不同的方法。

請注意,gooli's不適用於1.3.0,這似乎是Python 2.7.x中的當前標準。

另請注意,此版本不使用CDATA()方法使用gooli。

import xml.etree.cElementTree as ET 

class ElementTreeCDATA(ET.ElementTree): 
    """Subclass of ElementTree which handles CDATA blocks reasonably""" 

    def _write(self, file, node, encoding, namespaces): 
     """This method is for ElementTree <= 1.2.6""" 

     if node.tag == '![CDATA[': 
      text = node.text.encode(encoding) 
      file.write("\n<![CDATA[%s]]>\n" % text) 
     else: 
      ET.ElementTree._write(self, file, node, encoding, namespaces) 

    def _serialize_xml(write, elem, qnames, namespaces): 
     """This method is for ElementTree >= 1.3.0""" 

     if elem.tag == '![CDATA[': 
      write("\n<![CDATA[%s]]>\n" % elem.text) 
     else: 
      ET._serialize_xml(write, elem, qnames, namespaces) 
0

我到了這裏尋找一種方法來「用CDATA部分解析XML然後再用CDATA部分輸出它」。

我能夠做到這一點(也許lxml已經從這篇文章更新?)與以下:(這是一個有點粗糙 - 對不起;-)。其他人可能有更好的方式來編程式地找到CDATA部分,但我太懶惰了。

parser = etree.XMLParser(encoding='utf-8') # my original xml was utf-8 and that was a lot of the problem 
tree = etree.parse(ppath, parser) 

for cdat in tree.findall('./ProjectXMPMetadata'): # the tag where my CDATA lives 
    cdat.text = etree.CDATA(cdat.text) 

# other stuff here 

tree.write(opath, encoding="UTF-8",) 
1

接受的解決方案不能與的Python 2.7工作。然而,還有一個叫做lxml的軟件包,雖然速度稍慢,但它與xml.etree.ElementTree的語法基本相同。 lxml能夠寫入和解析CDATA。文檔here

3

這最終在Python 2.7中爲我工作。類似於阿毛裏的回答。

import xml.etree.ElementTree as ET 

ET._original_serialize_xml = ET._serialize_xml 


def _serialize_xml(write, elem, encoding, qnames, namespaces): 
    if elem.tag == '![CDATA[': 
     write("<%s%s]]>%s" % (elem.tag, elem.text, elem.tail)) 
     return 
    return ET._original_serialize_xml(
     write, elem, encoding, qnames, namespaces) 
ET._serialize_xml = ET._serialize['xml'] = _serialize_xml 
1

我發現一個黑客獲得CDATA使用註釋工作:

node.append(etree.Comment(' --><![CDATA[' + data.replace(']]>', ']]]]><![CDATA[>') + ']]><!-- ')) 
3

我不知道的建議代碼以前的版本是否取得了良好效果是否ElementTree的模塊已更新,但我所面臨的問題與使用此招:

etree._original_serialize_xml = etree._serialize_xml 
def _serialize_xml(write, elem, qnames, namespaces): 
    if elem.tag == '![CDATA[': 
     write("\n<%s%s]]>\n" % (
       elem.tag, elem.text)) 
     return 
    return etree._original_serialize_xml(
     write, elem, qnames, namespaces) 
etree._serialize_xml = etree._serialize['xml'] = _serialize_xml 

這種方法的問題是,通過這個異常後,串行再次把它當作普通的標籤後秒。我得到這樣的東西:

<textContent> 
<![CDATA[this was the code I wanted to put inside of CDATA]]> 
<![CDATA[>this was the code I wanted to put inside of CDATA</![CDATA[> 
</textContent> 

當然,我們知道這隻會導致大量的錯誤。 這是爲什麼發生的?

答案就在這個小傢伙:

return etree._original_serialize_xml(write, elem, qnames, namespaces) 

我們不想通過原還原序列化功能再次檢查代碼,如果我們已經被困了CDATA並順利通過它通過。 因此,在「if」塊中,只有當CDATA不存在時,我們才能返回原始序列化函數。在返回原始函數之前,我們錯過了「其他」。

此外在我的版本ElementTree模塊中,serialize函數拼命地要求「short_empty_element」參數。因此,最新的版本,我會建議這個樣子的(也與「尾巴」):

from xml.etree import ElementTree 
from xml import etree 

#in order to test it you have to create testing.xml file in the folder with the script 
xmlParsedWithET = ElementTree.parse("testing.xml") 
root = xmlParsedWithET.getroot() 

def CDATA(text=None): 
    element = ElementTree.Element('![CDATA[') 
    element.text = text 
    return element 

ElementTree._original_serialize_xml = ElementTree._serialize_xml 

def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs): 

    if elem.tag == '![CDATA[': 
     write("\n<{}{}]]>\n".format(elem.tag, elem.text)) 
     if elem.tail: 
      write(_escape_cdata(elem.tail)) 
    else: 
     return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs) 

ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml 


text = """ 
<?xml version='1.0' encoding='utf-8'?> 
<text> 
This is just some sample text. 
</text> 
""" 
e = ElementTree.Element("data") 
cdata = CDATA(text) 
root.append(cdata) 

#tests 
print(root) 
print(root.getchildren()[0]) 
print(root.getchildren()[0].text + "\n\nyay!") 

我得到的輸出是:

<Element 'Database' at 0x10062e228> 
<Element '![CDATA[' at 0x1021cc9a8> 

<?xml version='1.0' encoding='utf-8'?> 
<text> 
This is just some sample text. 
</text> 


yay! 

祝你相同的結果!