Adding subelement with ElementTree - python

The below code, which should add a subelemenet to a given XML element, gives the error:
xml.SubElement(new,xml.Element(self.XMLEntriesList['RiverCallPower']))
TypeError: must be xml.etree.ElementTree.Element, not None
But when I check, the element in question is confirmed to be an Element, and not None.
self.XMLEntriesList['RiverCallPower']
Out[3]: Element 'RiverCallPower' at 0x04B83420
What am I doing wrong?
import xml.etree.ElementTree as xml
self.tree = xml.parse('strategies.xml')
self.root = self.tree.getroot()
...
new=self.root.append(xml.Element('newElement'))
xml.SubElement(new,xml.Element(self.XMLEntriesList['RiverCallPower']))

I suspect the problem is not in the XMLEntriesList['RiverCallPower'] part, but the new variable that is None. And that happen because append() simply adds the new element to the list of root element's children and doesn't return anything. Try this way :
.......
new = xml.Element('newElement')
self.root.append(new)
xml.SubElement(new,xml.Element(self.XMLEntriesList['RiverCallPower']))

Related

How can I implement doubly linked lists into my tree structure in Python?

So I'm trying to represent an XML file using python. I already managed to do that. But each child node in my tree has to be doubly linked and I don't know how to do that. I've found a few examples of code online but they all use classes and the professor doesn't want us using classes. Here's my code:
from xml.etree.ElementTree import ElementTree
from xml.etree.ElementTree import Element
import xml.etree.ElementTree as etree
def create_tree(): #This function creates the root element of my tree
n = input("Enter your root Element: ")
root = Element(n)
tree = ElementTree(root)
print(etree.tostring(root))
return root
def add_child():
root = create_tree()
new = True
while new == True:
ask = input("If you wish to add a new child type 'yes', else type something else: ")
if ask == 'yes':
n = input("Enter the name of the node: ") #This block of code creates a child and appends it to the root element
node = Element(n)
root.append(node)
print(etree.tostring(root))
else:
break
return etree.tostring(root)
add_child()
For those of you wondering, the purpose of this project is to create a rooted tree with unbounded branching. I hope that once I can implement the doubly linked list I will be able to add a child node within a child node.
You should be able to implement a linked list using lists. You can define
each element as a list with the element itself, the item before it, and the item after it. If the list contains the previous node, the next node, and the element itself, respectively, and the head is stored in head, the syntax to append newElement to the beginning of the list would be as follows:
newNode = [None,head,newElement]
head[0] = newNode
head = newNode

How to replace an element in a tree? (elementtree/markdown)

How can I replace an element during iteration in an elementtree? I'm writing a treeprocessor for markdown and would like to wrap an element.
<pre class='inner'>...</pre>
Should become
<div class='wrapper'><pre class='inner'>...</pre></div>
I use getiterator('pre') to find the elements, but I don't know how to wrap it. The trouble point is replacing the found element with the new wrapper, but preserving the existing one as the child.
This is a bit of a tricky one. First, you'll need to get the parent element as described in this previous question.
parent_map = dict((c, p) for p in tree.getiterator() for c in p)
If you can get markdown to use lxml, this is a little easier -- I believe that lxml elements know their parents already.
Now, when you get your element from iterating, you can also get the parent:
for elem in list(tree.getiterator('pre')):
parent = parent_map[elem]
wrap_elem(parent, elem)
Note that I've turned the iterator from the tree into a list -- We don't want to modify the tree while iterating over it. That could be trouble.
Finally, you're in position to move the element around:
def wrap_elem(parent, elem)
parent_index = list(parent).index(elem)
parent.remove(elem)
new_elem = ET.Element('div', attrib={'class': 'wrapper'})
parent.insert(parent_index, new_elem)
new_elem.append(elem)
*Note that I haven't tested this code exactly... let me know if you find any bugs.
In my experience, you can use the method below to get what you want:
xml.etree.ElementTree.SubElement( I will just call it ET.Subelement) http://docs.python.org/2/library/xml.etree.elementtree.html#xml.etree.ElementTree.SubElement
Here is the steps:
Before your iteration, you should get the parent element of these iterated element first, store it into variable parent.
Then,
1, store the element <pre class='inner'>...</pre> into a variable temp
2, add a new subelement div into parent:
div = ET.SubElement(parent, 'div')
and set the attrib of div:
div.set('class','wrapper')
3, add the element in step 1 as a subelement of div,
ET.SubElement(div, temp)
4, delete the element in step 1:
parent.remove(temp)
Something like this works for one:
for i, element in enumerate(parent):
if is_the_one_you_want_to_replace(element):
parent.remove(element)
parent.insert(i, new_element)
break
Something like this works for many:
replacement_map = {}
for i, element in enumerate(parent):
if is_an_element_you_want_to_replace(element):
replacement_map[i] = el_to_remove, el_to_add
for index, (el_to_remove, el_to_add) in replacement_map.items():
parent.remove(el_to_remove)
parent.insert(index, el_to_add)
Another solution that works for me, similar to lyfing's.
Copy the element into a temp; retag the original element with the wanted outer tag and clear it, then append the copy into the original.
import copy
temp = copy.deepcopy(elem)
elem.tag = "div"
elem.set("class","wrapper")
elem.clear()
elem.append(temp)

Python - lxml - how to 'move' around the tree when building the tree

Basic question - how do you 'move' around in a tree when you are building a tree.
I can populate the first level:
import lxml.etree as ET
def main():
root = ET.Element('baseURL')
root.attrib["URL"]='www.com'
root.attrib["title"]='Level Title'
myList = [["www.1.com","site 1 Title"],["www.2.com","site 2 Title"],["www.3.com","site 3 Title"]]
for i in xrange(len(myList)):
ET.SubElement(root, "link_"+str(i), URL=myList[i][0], title=myList[i][1])
This gives me something like:
baseURL:
link_0
link_1
link_2
from there, I want to add a subtree from each of the new nodes so it looks something like:
baseURL:
link_0:
link_A
link_B
link_C
link_1
link_2
I can't see how to 'point' the subElement call to the next node down - I tried:
myList2 = [["www.A.com","site A Title"],["www.B.com","site B Title"],["www.C.com","site C Title"]]
for i in xrange(len(myList2)):
ET.SubElement('link_0', "link_"+str(i), URL=myList2[i][0], title=myList2[i][1])
But that throws the error:
TypeError: Argument '_parent' has incorrect type (expected lxml.etree._Element, got str)
as I am giving the subElement call a string, not an element reference. I also tried it as a variable, (i.e. link_0' rather than"link_0"`) and that gives a global missing variable, so my reference is obviously incorrect.
How do I 'point' my lxml builder to a child as a parent, and write a new child?
ET.SubElement(parent_node,type) creates a new XML element node as a child of parent_node. It also returns this new node.
So you could do this:
import lxml.etree as ET
def main():
root = ET.Element('baseURL')
myList = [1,2,3]
children = []
for x in myList:
children.append( ET.SubElement(root, "link_"+str(x)) )
for y in myList:
ET.SubElement( children[0], "child_"+str(y) )
But keeping track of the children is probably excessive since lxml already provides you with many ways to get to them.
Here's a way using lxmls built in children lists:
node = root[0]
for y in myList:
ET.SubElement( node, "child_"+str(y) )
Here's a way using XPath (possibly better if your XML is getting ugly)
node = root.xpath("/baseURL/link_0")[0]
for y in myList:
ET.SubElement( node, "child_"+str(y) )
Found the answer. I should be using the python array referencing, root[n] not trying to get to it via list_0

Turning ElementTree findall() into a list

I'm using ElementTree findall() to find elements in my XML which have a certain tag. I want to turn the result into a list. At the moment, I'm iterating through the elements, picking out the .text for each element, and appending to the list. I'm sure there's a more elegant way of doing this.
#!/usr/bin/python2.7
#
from xml.etree import ElementTree
import os
myXML = '''<root>
<project project_name="my_big_project">
<event name="my_first_event">
<location>London</location>
<location>Dublin</location>
<location>New York</location>
<month>January</month>
<year>2013</year>
</event>
</project>
</root>
'''
tree = ElementTree.fromstring(myXML)
for node in tree.findall('.//project'):
for element in node.findall('event'):
event_name=element.attrib.get('name')
print event_name
locations = []
if element.find('location') is not None:
for events in element.findall('location'):
locations.append(events.text)
# Could I use something like this instead?
# locations.append(''.join.text(*events) for events in element.findall('location'))
print locations
Outputs this (which is correct, but I'd like to assign the findall() results directly to a list, in text format, if possible;
my_first_event
['London', 'Dublin', 'New York']
You can try this - it uses a list comprehension to generate the list without having to create a blank one and then append.
if element.find('location') is not None:
locations = [events.text for events in element.findall('location')]
With this, you can also get rid of the locations definition above, so your code would be:
tree = ElementTree.fromstring(myXML)
for node in tree.findall('.//project'):
for element in node.findall('event'):
event_name=element.attrib.get('name')
print event_name
if element.find('location') is not None:
locations = [events.text for events in element.findall('location')]
print locations
One thing you will want to be wary of is what you are doing with locations - it won't be defined if location doesn't exist, so you will get a NameError if you try to print it and it doesn't exist. If that is an issue, you can retain the locations = [] definition - if the matching element isn't found, the result will just be an empty list.

Python Lxml: Adding and deleting tags

I am attempting to add and remove tags in an xml tree (snip below). I have a dict of boolean values that I use to determine whether to add or remove a tag. If the value is true, and the element does not exist, it creates the tag (and its parent if it doesn't exist). If false, it deletes the value.
However, it doesn't seem to work, and I can't find out why.
<Assets>
<asset name="Adham">
<pos>
<x>27913.769923</x>
<y>5174.627773</y>
</pos>
<GFX>
<space>P03.png</space>
<exterior>snow.png</exterior>
</GFX>
<presence>
<faction>Dvaered</faction>
<value>10.000000</value>
<range>1</range>
</presence>
<general>
<class>P</class>
<population>100</population>
<services>
<land/>
<refuel/>
</services>
<commodities/>
<description>Fooo</description>
<bar>(null)</bar>
</general>
</asset>
</Assets>
Code:
def writeflagX(self, root, x_path, _flag):
''' Writes flag to tree: deletes if false and already exists
and adds if true but doesn't exist yet)
'''
try:
if root.xpath(x_path):
if not self.flag[_flag]:
#delete value
temp1 = root.xpath(x_path)
temp1.getparent().remove(temp1)
print "removed"
#yeah, pretty ugly
except AttributeError:
#element does not exist, so create it if true value is here
#first, see if parent tag of list items exists, create it if neccesary
#split xpath into leader and item
leader = x_path.split("/")[0]
print leader
item = x_path.split("/")[1]
try:
if root.xpath(leader): #try to see if parent tag exists
child = etree.Subelement(root.xpath(leader), item)
print "no errors"
print "not caught"
except AttributeError:
l2 = leader.split("/")[0]
print l2 + " hi"
try:
l3 = leader.split("/")[1]
if l3: #if this tag is not a direct child of the root
child1 = etree.Subelement(root.xpath(l2), l3)
child1.append(etree.Element(item))
print "no dex error"
except IndexError: #if this tag is a direct child of the root
print "dex error"
child2 = etree.SubElement(root, l2)
def writeALLflagsX(self, _root):
'''Uses writeflagX and sets all the flags
'''
for k in self.flag:
self.writeflagX(_root, self.flagPaths[k], k)
I try to change the mission flag from false to true, and the refuel flag from true to false.
#Change Missions to true and refuel to false
foo = Asset()
###parsing code###
foo.alist["Adham"].flag["Is_missions"] = True
foo.alist["Adham"].flag["Is_refuel"] = False
foo.alist["Adham"].writeALLflagsX(foo.alist["Adham"].node)
foo.writeXML("output.xml")
I am stumped. The missions tag does not get added and the refuel tag does not get deleted.
Does this have something to do with me nesting the try/except statements?
Edit:
Ok, fixed the deleting problem by using a for loop as suggested:
temp1 = root.xpath(x_path)
for n in temp1:
n.getparent().remove(n)
Still can't add a node.
I think I am going to set up a new question that is simpler, since this is too convoluted.
Edit edit: new question that is much better: How to handle adding elements and their parents using xpath
There are several things could be improved in the code:
node.xpath returns the list of nodes - i.e. you can't do root.xpath(path).getparent(), check the list and take the node #0 if you're sure that it should exist (you node deletion code uses this);
when working with attributes try using node.attrib dictionary. Working with attributes becomes as easy as modifying python dictionary (del node.attrib[attr] and node.attrib[attr] = value, make sure the value is str though);
it might be useful to use etree.XML('<myelement><child/></myelement>') for creating new nodes.
Hope it helps.

Categories

Resources