2008-10-06 31 views
32

Tôi đã phát hiện ra rằng cElementTree nhanh hơn khoảng 30 lần so với xml.dom.minidom và tôi đang viết lại mã giải mã/mã hóa XML của mình. Tuy nhiên, tôi cần xuất XML có chứa các phần CDATA và dường như không có cách nào để làm điều đó với ElementTree.Cách xuất CDATA bằng ElementTree

Nó có thể được thực hiện?

+0

> Tôi cần xuất XML có chứa phần CDATA Tại sao? Có vẻ như một yêu cầu kỳ lạ. – bortzmeyer

+1

Đó là một yêu cầu mà tôi có - các đoạn CDATA đôi khi dễ đọc hơn nhiều. – grifaton

+0

@bortzmeyer Thật hữu ích khi thêm HTML vào KML (tệp XML của Google Maps). –

Trả lời

22

Sau một chút công việc, tôi tự tìm thấy câu trả lời. Nhìn vào mã nguồn của ElementTree.py, tôi thấy có cách xử lý đặc biệt các nhận xét XML và các hướng dẫn tiền xử lý. Những gì họ làm là tạo ra một chức năng nhà máy cho loại phần tử đặc biệt có sử dụng một giá trị thẻ đặc biệt (không phải chuỗi) để phân biệt nó với các phần tử thông thường.

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

Sau đó, trong _write chức năng của ElementTree mà thực sự kết quả đầu ra XML, có một trường hợp đặc biệt xử lý để lấy ý kiến:

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

Để hỗ trợ phần CDATA, tôi tạo ra một chức năng máy gọi CDATA, mở rộng lớp ElementTree và thay đổi hàm _write để xử lý các phần tử CDATA.

Điều này vẫn không giúp ích nếu bạn muốn phân tích cú pháp XML với các phần CDATA và sau đó xuất nó lại với phần CDATA, nhưng ít nhất nó cũng cho phép bạn tạo XML với phần CDATA theo chương trình. làm.

Triển khai có vẻ như hoạt động với cả ElementTree và 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") 
+1

Điều này dường như không còn nữa vì phương thức _write không có ở đó và các hàm _serialize_ * là tĩnh –

+0

Tôi nên làm gì vì tôi không thể sử dụng _write? Điều đó có nghĩa là tôi không thể sử dụng xml.elementtree? Thật là kinh khủng. – elwc

+2

Thsio reciep sẽ không hoạt động với Python 2.7 hoặc 3.2 (và 3.3) - kiểm tra câu trả lời của @ amaury dưới đây. BAsically, mới ElementTree không có một phương pháp "_write" có thể được overriden nữa. – jsbueno

6

Không thể AFAIK ... thật đáng tiếc. Về cơ bản, các mô-đun ElementTree giả định rằng người đọc tuân thủ 100% XML, vì vậy nó không quan trọng nếu chúng xuất một phần như CDATA hoặc một số định dạng khác tạo ra văn bản tương đương.

Xem this thread trên danh sách gửi thư của Python để biết thêm thông tin. Về cơ bản, họ đề xuất một số loại thư viện XML dựa trên DOM thay thế.

+2

Tôi sẽ không gọi nó là "đáng tiếc". Đối với XML infoset (nội dung), không có sự khác biệt giữa "" và "&" ... Hầu hết các trình phân tích cú pháp XML thậm chí sẽ không cho bạn biết những gì có trong tài liệu gốc. – bortzmeyer

+1

Đó là sự thật, nhưng một số dữ liệu có thể được bán phá giá và phân tích cú pháp hiệu quả hơn ở định dạng CDATA. Vì vậy, đó là một nỗi đau để không thể nói với một thư viện XML để xử lý nó theo cách này. –

+0

Liên kết có vẻ như không có sẵn ngay bây giờ. –

6

Trên thực tế mã này có một lỗi, vì bạn không bắt ]]> xuất hiện trong các dữ liệu bạn đang chèn như CDATA

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

bạn nên chia nó thành hai của CDATA trong trường hợp đó, tách các ]]> giữa hai.

cơ bản data = data.replace("]]>", "]]]]><![CDATA[>")
(không nhất thiết phải đúng, vui lòng kiểm tra)

1

DOM có (ít nhất ở cấp độ 2) một giao diện DATASection, và phẫu thuật Tài liệu :: createCDATASection. Chúng là các giao diện mở rộng , chỉ được hỗ trợ nếu triển khai hỗ trợ tính năng "xml".

từ xml.dom nhập minidom

my_xmldoc = minidom.phân tích cú pháp (xmlFile)

my_xmldoc.createCDATASection (dữ liệu)

bây giờ u có nút cadata thêm nó bất cứ nơi nào u muốn ....

10

Đây là một biến thể của giải pháp gooli của mà làm việc cho 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

Công việc shoudl này cũng làm việc với Python 2.7 - như công thức ban đầu thì không. Tôi jsut đã đưa ra một điều khác đó là chế độ phức tạp hơn này. – jsbueno

+1

Điều này cần cập nhật để thêm kwarg mã hóa vào '_serialize_xml' def – Patrick

+3

cho python 2.7 thêm một mã hóa arg vào ký tự tuần tự hóa. thay đổi 'def _serialize_xml (write, elem, qnames, không gian tên): ' cho' def _serialize_xml (viết, elem, mã hóa, qnames, không gian tên): ' thay đổi' write, elem, qnames, không gian tên)' để 'write, elem, encoding, qnames, namespaces' thay đổi 'et.write (sys.stdout.buffer.raw," utf-8 ")' thành 'et.write (sys.stdout," utf-8) ")' – Kevin

0

Đây là phiên bản của tôi dựa trên câu trả lời của cả gooli và amaury ở trên. Nó hoạt động cho cả ElementTree 1.2.6 và 1.3.0, sử dụng các phương pháp khác nhau để thực hiện điều này.

Lưu ý rằng gooli không hoạt động với 1.3.0, có vẻ như là tiêu chuẩn hiện tại trong Python 2.7.x.

Cũng lưu ý rằng phiên bản này không sử dụng gooli của phương thức CDATA().

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

Tôi đã ở đây tìm kiếm một cách để "phân tích cú pháp XML với phần CDATA và sau đó đầu ra nó một lần nữa với các phần CDATA".

Tôi đã có thể thực hiện việc này (có thể lxml đã được cập nhật kể từ bài đăng này?) Với những điều sau đây: (nó hơi thô lỗ - xin lỗi ;-). Người khác có thể có cách tốt hơn để tìm các phần CDATA theo chương trình nhưng tôi quá lười.

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

Các giải pháp chấp nhận không thể làm việc với Python 2.7. Tuy nhiên, có một gói khác được gọi là lxml (mặc dù hơi chậm hơn) được chia sẻ với cú pháp giống hệt với số xml.etree.ElementTree. lxml có thể viết và phân tích cú pháp CDATA. Tài liệu here

3

Điều này đã kết thúc với tôi trong Python 2.7. Tương tự như câu trả lời của Amaury.

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

tôi đã phát hiện ra một hack để có được CDATA để làm việc bằng các comment:

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

Tôi không biết liệu các phiên bản trước của mã đề nghị làm việc rất tốt và liệu ElementTree mô-đun đã được cập nhật nhưng Tôi đã gặp sự cố khi sử dụng mẹo này:

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 

Vấn đề với cách tiếp cận này là sau khi vượt qua ngoại lệ này, bộ nối tiếp lại coi nó là thẻ bình thường sau đó S. Tôi đã nhận được một cái gì đó như:

<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> 

Và tất nhiên chúng tôi biết rằng sẽ chỉ gây ra nhiều lỗi. Tại sao điều đó xảy ra?

Câu trả lời là ở anh chàng này ít:

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

Chúng tôi không muốn để kiểm tra mã một lần nữa thông qua chức năng tự các gốc nếu chúng tôi đã bị mắc kẹt CDATA của chúng tôi và thành công thông qua nó thông qua. Vì vậy, trong khối "if", chúng ta phải trả về hàm serialize ban đầu chỉ khi CDATA không có ở đó. Chúng tôi đã bỏ lỡ "else" trước khi trở về chức năng ban đầu.

Hơn nữa trong mô-đun ElementTree phiên bản của tôi, chức năng tuần tự hóa đã tuyệt vọng yêu cầu đối số "short_empty_element". Vì vậy, các phiên bản gần đây nhất mà tôi muốn giới thiệu vẻ như thế này (cũng với "đuôi"):

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!") 

Kết quả tôi nhận được:

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

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


yay! 

Chúc các bạn kết quả tương tự!

+0

Cảm ơn bạn! Giải pháp của bạn làm việc tuyệt vời cho tôi trong Python 3.4.3, và nó thực sự thú vị mà bạn chỉ đăng nó ngày hôm qua, và tôi cần nó ngày hôm nay. Đã không được thử nghiệm trong 3,5, nhưng tôi đoán nó sẽ phá vỡ sớm hay muộn vẫn còn, có lẽ trong phiên bản tiếp theo. Thở dài. – 4ae1e1

+0

Bạn được chào đón. Hãy ghi nhớ rằng trong khi sử dụng ElementTree.parse, bạn sẽ chỉ hiển thị nội dung CDATA (không có thẻ cdata). Trong mã của tôi: 'xmlParsedWithET = ElementTree.parse ("testing.xml")'. Tôi đã tìm ra cách sửa đổi mã chỉ một chút, bằng cách sử dụng lxml bạn có thể bảo tồn các thẻ cdata quý giá của chúng tôi. Hãy cho tôi biết nếu bạn quan tâm đến điều đó hoặc chỉ các lib chuẩn là ok cho bạn – Kamil

+0

Tôi đã viết một trình tạo cho blog của tôi và phải lắp ráp nguồn cấp dữ liệu Atom 1.0. Đây là một nhiệm vụ một lần (nếu nó phá vỡ trong tương lai, tôi luôn có thể sử dụng một virtualenv 3.4), do đó, một hack trên STL là chấp nhận được với tôi. – 4ae1e1

Các vấn đề liên quan