views:

130

answers:

2

In Python 2.6 using ElementTree, what's a good way to fetch the XML (as a string) inside a particular element, like what you can do in HTML and javascript with innerHTML?

Here's a simplified sample of the XML node I am starting with:

<label attr="foo" attr2="bar">This is some text <a href="foo.htm">and a link</a> in embedded HTML</label>

I'd like to end up with this string:

This is some text <a href="foo.htm">and a link</a> in embedded HTML

I've tried iterating over the parent node and concatenating the tostring() of the children, but that gave me only the subnodes:

# returns only subnodes (e.g. <a href="foo.htm">and a link</a>)
''.join([et.tostring(sub, encoding="utf-8") for sub in node])

I can hack up a solution using regular expressions, but was hoping there'd be something less hacky than this:

re.sub("</\w+?>\s*?$", "", re.sub("^\s*?<\w*?>", "", et.tostring(node, encoding="utf-8")))
A: 

The following worked for me:

from xml.etree import ElementTree as etree
xml = '<root>start here<child1>some text<sub1/>here</child1>and<child2>here as well<sub2/><sub3/></child2>end here</root>'
dom = etree.XML(xml)

(dom.text or '') + ''.join(map(etree.tostring, dom)) + (dom.tail or '')
# 'start here<child1>some text<sub1 />here</child1>and<child2>here as well<sub2 /><sub3 /></child2>end here'

dom.text or '' is used to get the text at the start of the root element. If there is no text dom.text is None.

Note that the result is not a valid XML - a valid XML should have only one root element.

Have a look at the ElementTree docs about mixed content.


Using Python 2.6.5, Ubuntu 10.04

Emil Ivanov
hi Emil - your solution works OK if all the text is inside subelements, but breaks in my case where the text is directly inside the parent element. Your note about mixed content clearly applies here, although I'm not sure (yet) how to combine head, tail, and subelements together to emit a coherent string.
Justin Grant
closer... but etree.tostring() doesn't include the tail of each subelement. And I think the final dom.tail is not needed since that's the string *after* an element and not inside it.
Justin Grant
I don't seem to understand you Justin - `start here`, `and` and `end here` is a text that is right inside the root element?!The snippet above might require some fiddling - you can create some test cases and improve it - see the link for the documentation on how to handle mixed content.
Emil Ivanov
A: 

How about:

from xml.etree import ElementTree as ET

xml = '<root>start here<child1>some text<sub1/>here</child1>and<child2>here as well<sub2/><sub3/></child2>end here</root>'
root = ET.fromstring(xml)

def content(tag):
    return tag.text + ''.join(ET.tostring(e) for e in tag)

print content(root)
print content(root.find('child2'))

Resulting in:

start here<child1>some text<sub1 />here</child1>and<child2>here as well<sub2 /><sub3 /></child2>end here
here as well<sub2 /><sub3 />
Mark Tolonen
@Justin, tostring() *does* include the tail, or the above wouldn't work.
Mark Tolonen