2012-06-20 61 views
12

我嘗試獲取開始xml標記和關閉對象之間的整個內容。下面如何在Python中的兩個xml標籤之間獲取整個內容?

獲得像title直案件的內容很容易,但我怎麼能得到標記之間的全部內容如果混合內容使用,我想保持內標籤

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

我想是兩個text標籤,包括任何標籤的內容:Some text with <extradata>data</extradata> in it. It spans <sometag>multiple lines: <tag>one</tag>, <tag>two</tag> or more</sometag>.

現在我使用正則表達式,但它得到的有點亂,我不喜歡這種方式。我傾向於基於XML解析器的解決方案。我查看了minidom,etree,lxmlBeautifulSoup,但找不到這種情況下的解決方案(整個內容,包括內部標籤)。

回答

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

這裏的訣竅是t是可迭代的,迭代時產生所有子節點。由於etree避免了文本節點,因此您還需要在第一個子標記之前恢復文本,並使用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>' 

或者:

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想要獲取特定元素的內容。在這種情況下,您的解決方案不起作用,至少不是直接。 II得到一個帶有e = t.xpath('// text')[0]'的元素並試過('''.join(map(etree.tostring,e))'),但結果是'其中有數據。 – brandizzi

+0

@brandizzi好點。更新以反映這一點。 – Marcin

+0

需要測試一些更多的案例,但你的最後一個例子對我來說工作得很好(到目前爲止)。當使用'find'而不是'xpath'時,它也可以與標準的''etree''一起使用。 – Brutus

-2

就找到了解決辦法,很簡單:

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是definitly去這裏的路!

編輯://對不起,我以爲你只想要孩子之間的文字,是我不好

+1

我可以用'x.find('text')。get_text()''獲得相同的結果。 **但是**這種方法不包括內部標籤,我需要它們。 – Brutus

+1

這實際上並沒有以任何方式解決OP問題。 *需要*維護內部標籤。 – brandizzi

+0

它確實保持內部標籤,只有不超過一個級別,請參閱我的編輯,'itertext'獲取所有內容 – dav1d

7

這裏的東西,對我來說,你的樣本工程:

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()')) 

產量:

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

xpath選擇<text>元素的所有子節點,並且如果它們是字符串/ unicode子類(<class 'lxml.etree._ElementStringResult'>)或cal,則將它們直接呈現爲unicode如果它是Elementwith_tail=False就可以避免重複尾部。

您可能需要處理其他節點類型(如果它們存在)。

+0

+1使用'node()' – dusan

+1

這可以寫得更緊湊。在這個單行內容中:'''.join(el,if isinstance(el,str)else lxml.etree.tostring(el,with_tail = False)for doc.xpath('/ review/text/node()' ))' –

+0

你可以不加區分地使用'tostring'。 – Marcin

1

那是相當容易lxml *,使用parse()tostring()功能:

from lxml.etree import parse, tostring 

首先,解析文檔,讓你的元素(我使用XPath,但你可以使用任何你想要的):

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

tostring()函數返回的元素的文本表示:

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

然而,你不希望外部因素,所以我們可以用一個簡單的str.replace()調用其刪除:

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

注意str.replace()收到1作爲第三個參數,因此它只會移除第一次出現的開標籤。也可以用結束標籤來完成。現在,而不是1,我們通過-1來代替:

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

的解決方案,當然,是在一次做的一切:

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

編輯:@Charles取得了良好的點:這個代碼很脆弱,因爲標籤可以有屬性。一種可能的,但仍有限的解決方案是拆分在第一>字符串:

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

獲得第二生成的字符串:

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

然後rsplitting它:

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

,並終於得到第一個結果:

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

儘管如此,這個代碼仍然很脆弱,因爲>是XML中完全有效的字符,甚至是屬性內部的字符。我不得不承認MattH solution是真正的通用解決方案。

*實際上,該解決方案也適用於ElementTree,如果您不想依賴lxml,這種方法非常好。唯一的區別是你將無法使用XPath。

+1

文字替換在這裏增加了很多脆弱性。如果你的輸入文件碰巧有它的屬性?一個名稱空間前綴? –

+0

我有這種感覺,我不會用這種方法獲得很多純正規表達。由於開始標籤至少有一個屬性,它也得到了片狀。 – Brutus

+0

不需要文本修改。 – Marcin

1

我喜歡@以上馬辛的解決方案,但是我發現,使用他的第二個選項時(將一個子節點,而不是樹的根),它不處理實體。

他從上述(修改以添加的實體)代碼:

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

回報:

this & that. 

與裸/未逸出 '&' 字符,而不是一個適當的實體(」 &安培;')。

我的解決辦法是使用在節點級別(而不是在所有兒童)來調用etree.tostring,然後剝離使用正則表達式的開始和結束標籤:

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 

生產:

this &amp; that. 

我使用re.DOTALL來確保這適用於包含換行符的XML。

相關問題