Python ElementTree adding a child - python

I have an xml file which looks like this:
<keyboard>
</keyboard>
I want to make it look like this:
<keyboard>
<keybind key="W-c-a"><action name="Execute"><command>sudo shutdown now</command></action></keybind>
</keyboard>
I have a function to add this which has parameters that will change the key and the command. Is this possible to do? If yes, how can I do this?
(The function):
def add_keybinding(self, keys, whatToExec):
keybinding = "<keybind key=\"%s\"><action name=\"Execute\"><command>%s</command><action></keybind>" % (keys, whatToExec)
f = open("/etc/xdg/openbox/rc.xml", "a")
try:
# I want to append the keybinding variable to the <keyboard>
except IOError as e:
print(e)

From the doc, you can try the following:
def add_keybinding(keys, whatToExec, filename):
keybind = ET.Element('keybind')
keybind.set("key", keys)
action = ET.SubElement(keybind, 'action')
action.set("name", "Execute")
command = ET.SubElement(action, 'command')
command.text = whatToExec
tree = ET.parse(filename)
tree.getroot().append(keybind)
tree.write(filename)
Explanation:
Create the keybind tag using xml.etree.ElementTree.Element : keybind = ET.Element('keybind')
Set a property using set: keybind.set("key", keys)
Create the action tag as a sub element of keybind using
xml.etree.ElementTree.SubElement: action = ET.SubElement(keybind, 'action')
Set the property as at step 2: action.set("name", "Execute")
Create command tag: action.set("name", "Execute")
Set command tag content using .text: command.text = whatToExec
Read file using xml.etree.ElementTree.parse: tree = ET.parse(filename)
Append keybind tag to the doc root element using append*
Export new xml to file using write
Full example:
import xml.etree.ElementTree as ET
from xml.dom import minidom
def add_keybinding(keys, whatToExec, filename):
keybind = ET.Element('keybind')
keybind.set("key", keys)
action = ET.SubElement(keybind, 'action')
action.set("name", "Execute")
command = ET.SubElement(action, 'command')
command.text = whatToExec
tree = ET.parse(filename)
tree.getroot().append(keybind)
tree.write(filename)
return tree
def prettify(elem):
rough_string = ET.tostring(elem, 'utf-8')
return minidom.parseString(rough_string).toprettyxml(indent=" ")
filename = "test.xml"
for i in range(3):
tree = add_keybinding(str(i), "whatToExec " + str(i), filename)
print(prettify(tree.getroot()))
Output:
<?xml version="1.0" ?>
<keyboard>
<keybind key="0">
<action name="Execute">
<command>whatToExec 0</command>
</action>
</keybind>
<keybind key="1">
<action name="Execute">
<command>whatToExec 1</command>
</action>
</keybind>
<keybind key="2">
<action name="Execute">
<command>whatToExec 2</command>
</action>
</keybind>
</keyboard>

Related

How to add Subelements and its in xml

I have a xml file which has subelements:-
.......
.......
<EnabledFeatureListForUsers>
<FeatureEntitlementDetail>
<UserName>xyz#xyz.com</UserName>
<FeatureList>
<FeatureDetail>
<FeatureId>X</FeatureId>
</FeatureDetail>
</FeatureList>
</FeatureEntitlementDetail>
</EnabledFeatureListForUsers>
.....
.....
I want to add a new sub element FeatureEntitlementDetail with all its subelements/children like username, Feature List, Feature Detail, Feature Id. I tried using SubElement function, but it only adds FeatureEntitlementDetail />. The code which I used was :-
import xml.etree.ElementTree as ET
filename = "XYZ.xml"
xmlTree = ET.parse(filename)
root = xmlTree.getroot()
for element in root.iter('EnabledFeatureListForUsers'):
ET.SubElement(element,"FeatureEntitlementDetail")
Any help is appreciated.
See below
import xml.etree.ElementTree as ET
xml = """
<EnabledFeatureListForUsers>
<FeatureEntitlementDetail>
<UserName>xyz#xyz.com</UserName>
<FeatureList>
<FeatureDetail>
<FeatureId>X</FeatureId>
</FeatureDetail>
</FeatureList>
</FeatureEntitlementDetail>
</EnabledFeatureListForUsers>
"""
root = ET.fromstring(xml)
fed = ET.SubElement(root,'FeatureEntitlementDetail')
un = ET.SubElement(fed,'UserName')
un.text = 'abc.zz.net'
fl = ET.SubElement(fed,'FeatureList')
df = ET.SubElement(fl,'FeatureDetail')
fi = ET.SubElement(df,'FeatureId')
fi.text = 'Z'
ET.dump(root)
output
<?xml version="1.0" encoding="UTF-8"?>
<EnabledFeatureListForUsers>
<FeatureEntitlementDetail>
<UserName>xyz#xyz.com</UserName>
<FeatureList>
<FeatureDetail>
<FeatureId>X</FeatureId>
</FeatureDetail>
</FeatureList>
</FeatureEntitlementDetail>
<FeatureEntitlementDetail>
<UserName>abc.zz.net</UserName>
<FeatureList>
<FeatureDetail>
<FeatureId>Z</FeatureId>
</FeatureDetail>
</FeatureList>
</FeatureEntitlementDetail>
</EnabledFeatureListForUsers>

Python etree appending element after removing

I want to be able to remove and append questions. This works in most cases, however appending doesn't work when the new question has the same q_nr as the question previously removed.
I'm having an xml file which looks like this.
<question>
<q_nr>Q1</q_nr>
<other />
</question
<question>
<q_nr>Q2</q_nr>
<other />
</question
function for removing:
def quest_del(request):
q_nr = request.POST['deleteQnr']
tree = ET.parse(filename)
root = tree.getroot()
for quest in root.findall('project/question'):
qt_nr = quest.find('q_nr').text
if qt_nr == q_nr:
for child in list(quest):
quest.remove(child)
for proj in root.findall('project'):
for quest in proj.findall('question'):
result = len(quest.getchildren())
if result == 0:
proj.remove(quest)
tree.write(file_name)
function for appending:
def quest_adder(request):
tree = ET.parse(source)
proj = tree.getroot().find("project")
q_text = ET.SubElement(proj, "question")
q_text.text = "\n\t\t"
q_text.tail = "\n\t"
q_nr = ET.SubElement(q_text, q_nr)
q_nr.text = request.POST['quest_nr']
q_nr.tail = "\n\t\t"
tree.write(filename)

Generating xml with LXML - attribute namespace

I need to generate xml which looks like this:
<definitions xmlns:ex="http://www.example1.org" xmlns="http://www.example2.org">
<typeRef xmlns:ns2="xyz">text</typeRef>
</definitions>
My code looks as follows:
class XMLNamespaces:
ex = 'http://www.example1.org'
xmlns = 'http://www.example2.org'
root = Element('definitions', xmlns='http://www.example2.org', nsmap = {'ex':XMLNamespaces.ex})
type_ref = SubElement(root, 'typeRef')
type_ref.attrib[QName(XMLNamespaces.xmlns, 'ns2')] = 'xyz'
type_ref.text = 'text'
tree = ElementTree(root)
tree.write('filename.xml', pretty_print=True)
The result looks like:
<definitions xmlns:ex="http://www.example1.org" xmlns="http://www.example2.org">
<typeRef xmlns:ns0="http://www.example2.org" ns0:ns2="xyz">text</typeRef>
</definitions>
So here is my question:
How to make attribute look like xmlns:ns2="xyz" instead of xmlns:ns0="http://www.example2.org" ns0:ns2="xyz"?
Simply run same process as your opening element where you defined the namespace dictionary with nsmap argument. Notice the added variable in your class object:
from lxml.etree import *
class XMLNamespaces:
ex = 'http://www.example1.org'
xmlns = 'http://www.example2.org'
xyz = 'xyz'
root = Element('definitions', xmlns='http://www.example2.org', nsmap={'ex':XMLNamespaces.ex})
type_ref = SubElement(root, 'typeRef', nsmap={'ns2':XMLNamespaces.xyz})
type_ref.text = 'text'
tree = ElementTree(root)
tree.write('filename.xml', pretty_print=True)
# <definitions xmlns:ex="http://www.example1.org" xmlns="http://www.example2.org">
# <typeRef xmlns:ns2="xyz">text</typeRef>
# </definitions>

How to insert a child subelement in Python ElementTree

My xml :-
<users>
</users>
i need to just append a child element :-
<users>
<user name="blabla" age="blabla" ><group>blabla</group>
</users>
My code giving some error :(
import xml.etree.ElementTree as ET
doc = ET.parse("users.xml")
root_node = doc.find("users")
child = ET.SubElement(root_node, "user")
child.set("username","srquery")
group = ET.SubElement(child,"group")
group.text = "fresher"
tree = ET.ElementTree(root_node)
tree.write("users.xml")
I missed out "append" , but i don't have idea where to add that. Thanks in advance.
Change this line
root_node = doc.find("users")
...to this line
root_node = doc.getroot()
The key takeaway here is that doc is already a reference to the root node, and is accessed with getroot(). doc.find('users') will not return anything, since users is not a child of the root, it's the root itself.
A slightlly modified version to explain what happens:
root = ET.fromstring('<users></users>') # same as your doc=ET.parse(...).find(...), btw. doc=root
el = ET.Element('group') # creating a new element/xml-node
root.append(el) # and adding it to the root
ET.tostring(root)
>>> '<users><group /></users>'
el.text = "fresher" # adding your text
ET.tostring(root)
>>> '<users><group>fresher</group></users>'

Generating XML dynamically in Python

Hey friends I am generating XML data using Python libraries as follow
def multiwan_info_save(request):
data = {}
init = "init"
try:
form = Addmultiwanform(request.POST)
except:
pass
if form.is_valid():
from_sv = form.save(commit=False)
obj_get = False
try:
obj_get = MultiWAN.objects.get(isp_name=from_sv.isp_name)
except:
obj_get = False
nameservr = request.POST.getlist('nameserver_mw')
for nm in nameservr:
nameserver1, is_new = NameServer.objects.get_or_create(name=nm)
from_sv.nameserver = nameserver1
from_sv.save()
# main(init)
top = Element('ispinfo')
# comment = Comment('Generated for PyMOTW')
#top.append(comment)
all_connection = MultiWAN.objects.all()
for conn in all_connection:
child = SubElement(top, 'connection number ='+str(conn.id)+'name='+conn.isp_name+'desc='+conn.description )
subchild_ip = SubElement(child,'ip_address')
subchild_subnt = SubElement(child,'subnet')
subchild_gtwy = SubElement(child,'gateway')
subchild_nm1 = SubElement(child,'probe_server1')
subchild_nm2 = SubElement(child,'probe_server2')
subchild_interface = SubElement(child,'interface')
subchild_weight = SubElement(child,'weight')
subchild_ip.text = str(conn.ip_address)
subchild_subnt.text = str(conn.subnet)
subchild_gtwy.text = str(conn.gateway)
subchild_nm1.text = str(conn.nameserver.name)
# subchild_nm2.text = conn.
subchild_weight.text = str(conn.weight)
subchild_interface.text = str(conn.interface)
print "trying to print _____________________________"
print tostring(top)
print "let seeeeeeeeeeeeeeeeee +++++++++++++++++++++++++"
But I am getting output like follow
<ispinfo><connection number =5name=Airtelllldesc=Largets TRelecome ><ip_address>192.168.1.23</ip_address><subnet>192.168.1.23</subnet><gateway>192.168.1.23</gateway><probe_server1>192.168.99.1</probe_server1><probe_server2 /><interface>eth0</interface><weight>160</weight></connection number =5name=Airtelllldesc=Largets TRelecome ><connection number =6name=Uninordesc=Uninor><ip_address>192.166.55.23</ip_address><subnet>192.166.55.23</subnet><gateway>192.168.1.23</gateway><probe_server1>192.168.99.1</probe_server1><probe_server2 /><interface>eth0</interface><weight>160</weight></connection number =6name=Uninordesc=Uninor><connection number =7name=Airteldesc=Largets TRelecome ><ip_address>192.168.1.23</ip_address><subnet>192.168.1.23</subnet><gateway>192.168.1.23</gateway><probe_server1>192.168.99.1</probe_server1><probe_server2 /><interface>eth0</interface><weight>160</weight></connection number =7name=Airteldesc=Largets TRelecome ></ispinfo>
I just want to know that how can I write this XML in proper XML format ?
Thanks in advance
UPDATED to include simulation of both creating and printing of the XML tree
The Basic Issue
Your code is generating invalid connection tags like this:
<connection number =5name=Airtelllldesc=Largets TRelecome ></connection number =5name=Airteldesc=Largets TRelecome >
when they should look like this (I am omitting the sub-elements in between. Your code is generating these correctly):
<connection number="5" name="Airtellll" desc="Largets TRelecome" ></connection>
If you had valid XML, this code would print it neatly:
from lxml import etree
xml = '''<ispinfo><connection number="5" name="Airtellll" desc="Largets TRelecome" ><ip_address>192.168.1.23</ip_address><subnet>192.168.1.23</subnet><gateway>192.168.1.23</gateway><probe_server1>192.168.99.1</probe_server1><probe_server2 /><interface>eth0</interface><weight>160</weight></connection></ispinfo>'''
xml = etree.XML(xml)
print etree.tostring(xml, pretty_print = True)
Generating Valid XML
A small simulation follows:
from lxml import etree
# Some dummy text
conn_id = 5
conn_name = "Airtelll"
conn_desc = "Largets TRelecome"
ip = "192.168.1.23"
# Building the XML tree
# Note how attributes and text are added, using the Element methods
# and not by concatenating strings as in your question
root = etree.Element("ispinfo")
child = etree.SubElement(root, 'connection',
number = str(conn_id),
name = conn_name,
desc = conn_desc)
subchild_ip = etree.SubElement(child, 'ip_address')
subchild_ip.text = ip
# and pretty-printing it
print etree.tostring(root, pretty_print=True)
This will produce:
<ispinfo>
<connection desc="Largets TRelecome" number="5" name="Airtelll">
<ip_address>192.168.1.23</ip_address>
</connection>
</ispinfo>
A single line is proper, in the sense that a XML parser will understand it.
For pretty-printing to sys.stdout, use the dump method of Element.
For pretty-printing to a stream, use the write method of ElementTree.

Categories