2012-06-20 39 views
12

Tôi cố gắng lấy toàn bộ nội dung giữa thẻ mở xml và đó là đối tác đóng.Làm thế nào để có được toàn bộ nội dung giữa hai thẻ xml bằng Python?

Lấy nội dung trong trường hợp thẳng như title dưới đây là dễ dàng, nhưng làm thế nào tôi có thể nhận được toàn bộ nội dung giữa các thẻ nếu hỗn hợp nội dung được sử dụng và tôi muốn giữ gìn các thẻ nội?

<?xml version="1.0" encoding="UTF-8"?> 
<review> 
    <title>Some testing stuff</title> 
    <text sometimes="attribute">Some text with <extradata>data</extradata> in it. 
    It spans <sometag>multiple lines: <tag>one</tag>, <tag>two</tag> 
    or more</sometag>.</text> 
</review> 

Những gì tôi muốn là nội dung giữa hai text thẻ, bao gồm bất kỳ thẻ: Some text with <extradata>data</extradata> in it. It spans <sometag>multiple lines: <tag>one</tag>, <tag>two</tag> or more</sometag>.

Để bây giờ tôi sử dụng biểu thức thông thường nhưng nó có được của kinda lộn xộn và tôi không thích phương pháp này. Tôi dựa vào giải pháp dựa trên XML parser. Tôi đã xem qua minidom, etree, lxmlBeautifulSoup nhưng không thể tìm thấy giải pháp cho trường hợp này (toàn bộ nội dung, kể cả thẻ bên trong).

Trả lời

3
from lxml import etree 
t = etree.XML(
"""<?xml version="1.0" encoding="UTF-8"?> 
<review> 
    <title>Some testing stuff</title> 
    <text>Some text with <extradata>data</extradata> in it.</text> 
</review>""" 
) 
(t.text + ''.join(map(etree.tostring, t))).strip() 

Bí quyết ở đây là t có thể lặp lại và khi được lặp lại, sinh ra tất cả các nút con. Bởi vì etree tránh các nút văn bản, bạn cũng cần khôi phục văn bản trước thẻ con đầu tiên, với t.text.

In [50]: (t.text + ''.join(map(etree.tostring, t))).strip() 
Out[50]: '<title>Some testing stuff</title>\n <text>Some text with <extradata>data</extradata> in it.</text>' 

Hoặc:

In [6]: e = t.xpath('//text')[0] 

In [7]: (e.text + ''.join(map(etree.tostring, e))).strip() 
Out[7]: 'Some text with <extradata>data</extradata> in it.' 
+0

OP muốn lấy nội dung của một yếu tố cụ thể. Giải pháp của bạn không hoạt động trong trường hợp này, ít nhất là không trực tiếp. II lấy một phần tử với 'e = t.xpath ('// text') [0]' và thử nó (''' .join (map (etree.tostring, e))') nhưng kết quả là '' dữ liệu trong đó.''. – brandizzi

+0

@brandizzi Điểm tốt. Cập nhật để phản ánh điều đó. – Marcin

+0

Cần thử nghiệm trên một số trường hợp khác, nhưng ví dụ cuối cùng của bạn hoạt động tốt cho tôi (cho đến nay). Khi sử dụng 'find' thay cho' xpath', nó cũng hoạt động với '' etree'' chuẩn. – Brutus

-2

Chỉ cần tìm thấy các giải pháp, khá dễ dàng:

In [31]: t = x.find('text') 

In [32]: t 
Out[32]: <Element text at 0xa87ed74> 

In [33]: list(t.itertext()) 
Out[33]: ['Some text with ', 'data', ' in it.'] 

In [34]: ''.join(_) 
Out[34]: 'Some text with data in it.' 

itertext là chắc chắn là con đường để đi đây!

Edit: // Xin lỗi tôi nghĩ bạn muốn chỉ văn bản giữa các trẻ em, tôi xấu

+1

Tôi có thể nhận được như vậy - Tôi nghĩ - với 'x.find ('văn bản'). Get_text()'. ** Nhưng ** cách tiếp cận này loại trừ các thẻ bên trong và tôi cần chúng. – Brutus

+1

Điều này không giải quyết được vấn đề OP trong bất kỳ cách nào, thực sự. Đó là * bắt buộc * để duy trì các thẻ bên trong. – brandizzi

+0

Nó duy trì các thẻ bên trong, không chỉ nhiều hơn một cấp, xem chỉnh sửa của tôi, 'itertext' get mọi thứ – dav1d

7

Dưới đây là một cái gì đó mà làm việc cho tôi và mẫu của bạn:

from lxml import etree 
doc = etree.XML(
"""<?xml version="1.0" encoding="UTF-8"?> 
<review> 
    <title>Some testing stuff</title> 
    <text>Some text with <extradata>data</extradata> in it.</text> 
</review>""" 
) 

def flatten(seq): 
    r = [] 
    for item in seq: 
    if isinstance(item,(str,unicode)): 
     r.append(unicode(item)) 
    elif isinstance(item,(etree._Element,)): 
     r.append(etree.tostring(item,with_tail=False)) 
    return u"".join(r) 

print flatten(doc.xpath('/review/text/node()')) 

Sản lượng:

Some text with <extradata>data</extradata> in it. 

Con đường xpath chọn tất cả các nút con của phần tử <text> và hiển thị chúng thành unicode trực tiếp nếu chúng là một phân lớp chuỗi/unicode (<class 'lxml.etree._ElementStringResult'>) hoặc cal Nếu Element, with_tail=False tránh trùng lặp đuôi.

Bạn có thể cần phải xử lý các loại nút khác nếu chúng có mặt.

+0

+1 Để sử dụng 'nút()' – dusan

+1

Điều này có thể được viết gọn hơn. Lấy một lớp lót: ''' .join (el if isinstance (el, str) khác lxml.etree.tostring (el, with_tail = False) cho el trong doc.xpath ('/ review/text/node()')) ' –

+0

Bạn có thể chỉ sử dụng' tostring' bừa bãi. – Marcin

1

Đó là đáng kể dễ dàng với lxml *, sử dụng parse()tostring() chức năng:

from lxml.etree import parse, tostring 

Trước tiên, bạn phân tích doc và nhận được yếu tố của bạn (Tôi đang sử dụng XPath, nhưng bạn có thể sử dụng bất cứ điều gì bạn muốn):

doc = parse('test.xml') 
element = doc.xpath('//text')[0] 

chức năng tostring() trả về một đại diện văn bản của nguyên tố của bạn:

>>> tostring(element) 
'<text>Some <text>text</text> with <extradata>data</extradata> in it.</text>\n' 

Tuy nhiên, bạn không muốn các yếu tố bên ngoài, vì vậy chúng ta có thể loại bỏ chúng với một đơn giản str.replace() gọi:

>>> tostring(element).replace('<%s>'%element.tag, '', 1) 
'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n' 

Lưu ý rằng str.replace() nhận 1 là tham số thứ ba, vì vậy nó sẽ loại bỏ chỉ sự xuất hiện đầu tiên của thẻ mở. Người ta cũng có thể làm điều đó với thẻ đóng.Bây giờ, thay vì 1, chúng ta vượt qua -1 để thay thế:

>>> tostring(element).replace('</%s>'%element.tag, '', -1) 
'<text>Some <text>text with <extradata>data</extradata> in it.\n' 

Các giải pháp, tất nhiên, là để làm tất cả mọi thứ cùng một lúc:

>>> tostring(element).replace('<%s>'%element.tag, '', 1).replace('</%s>'%element.tag, '', -1) 
'Some <text>text with <extradata>data</extradata> in it.\n' 

EDIT: @Charles làm một điểm tốt : mã này rất mong manh vì thẻ có thể có thuộc tính. Một giải pháp được nêu ra vẫn còn hạn chế có thể là để phân chia các chuỗi tại > đầu tiên:

>>> tostring(element).split('>', 1) 
['<text', 
'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n'] 

có được chuỗi kết quả thứ hai:

>>> tostring(element).split('>', 1)[1] 
'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n' 

sau đó rsplitting nó:

>>> tostring(element).split('>', 1)[1].rsplit('</', 1) 
['Some <text>text</text> with <extradata>data</extradata> in it.', 'text>\n'] 

và cuối cùng nhận được kết quả đầu tiên:

>>> tostring(element).split('>', 1)[1].rsplit('</', 1)[0] 
'Some <text>text</text> with <extradata>data</extradata> in it.' 

Tuy nhiên, mã này vẫn còn mong manh, vì > là một char hoàn toàn hợp lệ trong XML, ngay cả bên trong các thuộc tính.

Dù sao, tôi phải thừa nhận rằng MattH solution là giải pháp chung, thực sự.

* Thực ra, giải pháp này cũng hoạt động với ElementTree, điều tuyệt vời nếu bạn không muốn phụ thuộc vào lxml. Điểm khác biệt duy nhất là bạn sẽ không có cách nào để sử dụng XPath.

+1

Việc thay thế văn bản được thêm khá nhiều mong manh ở đây. Nếu tệp đầu vào của bạn xảy ra có thuộc tính trên đó? Tiền tố không gian tên? –

+0

Tôi có cảm giác rằng tôi sẽ không đạt được nhiều hơn các biểu thức chính quy thuần túy với cách tiếp cận này. Kể từ khi thẻ mở có ít nhất một thuộc tính, nó có được flaky quá. – Brutus

+0

Không có văn bản mangling là cần thiết. – Marcin

1

Tôi thích @ giải pháp Marcin của trên, tuy nhiên tôi thấy rằng khi sử dụng tùy chọn thứ 2 của mình (chuyển đổi một sub-node, không phải là gốc của cây) nó không xử lý thực thể.

mã của mình từ trên cao (sửa đổi để thêm một thực thể):

from lxml import etree 
t = etree.XML("""<?xml version="1.0" encoding="UTF-8"?> 
<review> 
    <title>Some testing stuff</title> 
    <text>this &amp; that.</text> 
</review>""") 
e = t.xpath('//text')[0] 
print (e.text + ''.join(map(etree.tostring, e))).strip() 

lợi nhuận:

this & that. 

với một unescaped '&' nhân vật trần/thay vì một thực thể thích hợp (' & amp ; ').

Giải pháp của tôi là sử dụng để gọi etree.tostring ở cấp nút (thay vì trên tất cả trẻ em), sau đó lột thời gian bắt đầu và kết thúc thẻ sử dụng một biểu thức chính quy:

import re 
from lxml import etree 
t = etree.XML("""<?xml version="1.0" encoding="UTF-8"?> 
<review> 
    <title>Some testing stuff</title> 
    <text>this &amp; that.</text> 
</review>""") 

e = t.xpath('//text')[0] 
xml = etree.tostring(e) 
inner = re.match('<[^>]*?>(.*)</[^>]*>\s*$', xml, flags=re.DOTALL).group(1) 
print inner 

sản xuất:

this &amp; that. 

Tôi đã sử dụng re.DOTALL để đảm bảo điều này phù hợp với XML chứa các dòng mới.

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