Python ElementTree won't update new file after parsing - python

Using ElementTree to parse attribute's value in an XML and writing a new XML file. It will console the new updated value and write a new file. But won't update any changes in the new file. Please help me understand what I am doing wrong. Here is XML & Python code:
XML
<?xml version="1.0"?>
<!--
-->
<req action="get" msg="1" rank="1" rnklst="1" runuf="0" status="1" subtype="list" type="60" univ="IL" version="fhf.12.000.00" lang="ENU" chunklimit="1000" Times="1">
<flds>
<f i="bond(long) hff" aggregationtype="WeightedAverage" end="2016-02-29" freq="m" sid="fgg" start="2016-02-29"/>
<f i="bond(short) ggg" aggregationtype="WeightedAverage" end="2016-02-29" freq="m" sid="fhf" start="2016-02-29"/>
</flds>
<dat>
<r i="hello" CalculationType="3" Calculate="1" />
</dat>
</req>
Python
import xml.etree.ElementTree as ET
with open('test.xml', 'rt') as f:
tree = ET.parse(f)
for node in tree.iter('r'):
port_id = node.attrib.get('i')
new_port_id = port_id.replace(port_id, "new")
print node
tree.write('./new_test.xml')

When you get the attribute i, and assign it to port_id, you just have a regular Python string. Calling replace on it is just the Python string .replace() method.
You want to use the .set() method of the etree node:
for node in tree.iter('r'):
node.set('i', "new")
print node

Related

Facing an error while modifying XML file with python

I am parsing an XML file and trying to delete a empty node but I am receiving the following error:
ValueError: list.remove(x): x not in lis
The XML file is as follows:
<toc>
<topic filename="GUID-5B8DE7B7-879F-45A4-88E0-732155904029.xml" docid="GUID-5B8DE7B7-879F-45A4-88E0-732155904029" TopicTitle="Notes, cautions, and warnings" />
<topic filename="GUID-89943A8D-00D3-4263-9306-CDC944609F2B.xml" docid="GUID-89943A8D-00D3-4263-9306-CDC944609F2B" TopicTitle="HCI Deployment with Windows Server">
<childTopics>
<topic filename="GUID-A3E5EA96-2110-46FF-9251-2291DF755F50.xml" docid="GUID-A3E5EA96-2110-46FF-9251-2291DF755F50" TopicTitle="Installing the OMIMSWAC license" />
<topic filename="GUID-7C4D616D-0D9A-4AE1-BE0F-EC6FC9DAC87E.xml" docid="GUID-7C4D616D-0D9A-4AE1-BE0F-EC6FC9DAC87E" TopicTitle="Managing Microsoft HCI-based clusters">
<childTopics>
</childTopics>
</topic>
</childTopics>
</topic>
</toc>
Kindly note that this is just an example format of my XML File. I this file, I want to remove the empty tag but I am getting an error. My current code is:
import xml.etree.ElementTree as ET
tree = ET.parse("toc2 - Copy.xml")
root = tree.getroot()
node_to_remove = root.findall('.//childTopics//childTopics')
for node in node_to_remove:
root.remove(node)
You need to call remove on the node's immediate parent, not on root. This is tricky using xml.etree, but if instead you use lxml.etree you can write:
import lxml.etree as ET
tree = ET.parse("data.xml")
root = tree.getroot()
node_to_remove = root.findall('.//childTopics//childTopics')
for node in node_to_remove:
node.getparent().remove(node)
print(ET.tostring(tree).decode())
Nodes in xml.etree do not have a getparent() method. If you're unable to use lxml, you'll need to look into other solutions for finding the parent of a node; this question has some discussion on that topic.

store content of a tag in a string with elementtree in python3

I'm using Python 3.7.2 and elementtree to copy the content of a tag in an XML file.
This is my XML file:
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.003.03">
<CstmrCdtTrfInitn>
<GrpHdr>
<MsgId>nBblsUR-uH..6jmGgZNHLQAAAXgXN1Lu</MsgId>
<CreDtTm>2016-11-10T12:00:00.000+01:00</CreDtTm>
<NbOfTxs>1</NbOfTxs>
<CtrlSum>6</CtrlSum>
<InitgPty>
<Nm>TC 03000 Kunde 55 Protokollr ckf hrung</Nm>
</InitgPty>
</GrpHdr>
</CstmrCdtTrfInitn>
</Document>
I want to copy the content of the 'MsgId' tag and save it as a string.
I've manage to do this with minidom before, but due to new circumstances, I have to settle with elementtree for now.
This is that code with minidom:
dom = xml.dom.minidom.parse('H:\\app_python/in_spsh/{}'.format(filename_string))
message = dom.getElementsByTagName('MsgId')
for MsgId in message:
print(MsgId.firstChild.nodeValue)
Now I want to do the exact same thing with elementtree. How can I achieve this?
To get the text value of a single element, you can use the findtext() method. The namespace needs to be taken into account.
from xml.etree import ElementTree as ET
tree = ET.parse("test.xml") # Your XML document
msgid = tree.findtext('.//{urn:iso:std:iso:20022:tech:xsd:pain.001.003.03}MsgId')
With Python 3.8 and later, it is possible to use a wildcard for the namespace:
msgid = tree.findtext('.//{*}MsgId')

Generate XML Document in Python 3 using Namespaces and ElementTree

I am having problems generating a XML document using the ElementTree framework in Python 3. I tried registering the namespace before setting up the document. Right now it seems that I can generate a XML document only by adding the namespace to each element like a=Element("{full_namespace_URI}element_name") which seems tedious.
How do I setup the default namespace and can omit putting it in each element?
Any help is appreciated.
I have written a small demo program for Python 3:
from io import BytesIO
from xml.etree import ElementTree as ET
ET.register_namespace("", "urn:dslforum-org:service-1-0")
"""
desired output
==============
<?xml version='1.0' encoding='utf-8'?>
<topNode xmlns="urn:dslforum-org:service-1-0"">
<childNode>content</childNode>
</topNode>
"""
# build XML document without namespaces
a = ET.Element("topNode")
b = ET.Element("childNode")
b.text = "content"
a.append(b)
tree = ET.ElementTree(a)
# build XML document with namespaces
a_ns = ET.Element("{dsl}topNode")
b_ns = ET.Element("{dsl}childNode")
b_ns.text = "content"
a_ns.append(b_ns)
tree_ns = ET.ElementTree(a_ns)
def print_element_tree(element_tree, comment, default_namespace=None):
"""
print element tree with comment to standard out
"""
with BytesIO() as buf:
element_tree.write(buf, encoding="utf-8", xml_declaration=True,
default_namespace=default_namespace)
buf.seek(0)
print(comment)
print(buf.read().decode("utf-8"))
print_element_tree(tree, "Element Tree without XML namespace")
print_element_tree(tree_ns, "Element Tree with XML namespace", "dsl")
I believe you are overthinking this.
Registering a default namespace in your code avoids the ns0: aliases.
Registering any namespaces you will use while creating a document allows you to designate the alias used for each namespace.
To achieve your desired output, assign the namespace to your top element:
a = ET.Element("{urn:dslforum-org:service-1-0}topNode")
The preceding ET.register_namespace("", "urn:dslforum-org:service-1-0") will make that the default namespace in the document, assign it to topNode, and not prefix your tag names.
<?xml version='1.0' encoding='utf-8'?>
<topNode xmlns="urn:dslforum-org:service-1-0"><childNode>content</childNode></topNode>
If you remove the register_namespace() call, then you get this monstrosity:
<?xml version='1.0' encoding='utf-8'?>
<ns0:topNode xmlns:ns0="urn:dslforum-org:service-1-0"><childNode>content</childNode></ns0:topNode>

Modify XML node using Python

I am taking an XML file as an input, have to search with a keyword i.e GENTEST05.
If found , then I need to pick up its parent node (in this example I want to pick up <ScriptElement>) and then replace the complete node <ScriptElement>blahblah</ScriptElement> with a new content.
...
...
<ScriptElement>
<ScriptElement>
<ScriptElement>
<ElementData xsi:type="anyData">
<DeltaTime>
<Area>
<Datatype>USER PROMPT [GENTEST05]</Datatype>
<Description />
<Multipartmessage>False<Multipartmessage>
<Comment>false</Comment>
</ElementData>
</ScriptElement>
<ScriptElement>
<ScriptElement>
...
...
...
I am trying to do this using Beautifulsoup. This is what I've done so far but not getting a proper way to proceed. Other than beautifulsoup, ElementTree or any other suggestion is welcome.
import sys
from BeautifulSoup import BeautifulStoneSoup as bs
xmlsoup = bs(open('file_xml' , 'r'))
a = raw_input('Enter Text')
paraText = xmlsoup.findAll(text=a)
print paraText
print paraText.findParent()
Ok here is some sample code to get you started. I used ElementTree because it's a builtin module and quite suitable for this type of task.
Here is the XML file I used:
<?xml version="1.0" ?>
<Script>
<ScriptElement/>
<ScriptElement/>
<ScriptElement>
<ElementData>
<DeltaTime/>
<Area/>
<Datatype>USER PROMPT [GENTEST05]</Datatype>
<Description/>
<Multipartmessage>False</Multipartmessage>
<Comment>false</Comment>
</ElementData>
</ScriptElement>
<ScriptElement/>
<ScriptElement/>
</Script>
Here is the python program:
import sys
import xml.etree.ElementTree as ElementTree
tree = ElementTree.parse("test.xml")
root = tree.getroot()
#The keyword to find and remove
keyword = "GENTEST05"
for element in list(root):
for sub in element.iter():
if sub.text and keyword in sub.text:
root.remove(element)
print ElementTree.tostring(root)
sys.exit()
I have kept the program simple so that you can improve on it. Since your XML has one root node, I am assuming you want to remove all parent elements of the keyword-matched element directly up to the root. In ElementTree, you can call root.remove() to remove the <ScriptElement> element that is the ancestory of the keyword-matched element.
This is just to get you started: this will only remove the first element, then print the resulting tree and quit.
Output:
<Script>
<ScriptElement />
<ScriptElement />
<ScriptElement />
<ScriptElement />
</Script>

Programmatically clean/ignore namespaces in XML - python

I'm trying to write a simple program to read my financial XML files from GNUCash, and learn Python in the process.
The XML looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<gnc-v2
xmlns:gnc="http://www.gnucash.org/XML/gnc"
xmlns:act="http://www.gnucash.org/XML/act"
xmlns:book="http://www.gnucash.org/XML/book"
{...}
xmlns:vendor="http://www.gnucash.org/XML/vendor">
<gnc:count-data cd:type="book">1</gnc:count-data>
<gnc:book version="2.0.0">
<book:id type="guid">91314601aa6afd17727c44657419974a</book:id>
<gnc:count-data cd:type="account">80</gnc:count-data>
<gnc:count-data cd:type="transaction">826</gnc:count-data>
<gnc:count-data cd:type="budget">1</gnc:count-data>
<gnc:commodity version="2.0.0">
<cmdty:space>ISO4217</cmdty:space>
<cmdty:id>BRL</cmdty:id>
<cmdty:get_quotes/>
<cmdty:quote_source>currency</cmdty:quote_source>
<cmdty:quote_tz/>
</gnc:commodity>
Right now, i'm able to iterate and get results using
import xml.etree.ElementTree as ET
r = ET.parse("file.xml").findall('.//')
after manually cleaning the namespaces, but I'm looking for a solution that could either read the entries regardless of their namespaces OR remove the namespaces before parsing.
Note that I'm a complete noob in python, and I've read: Python and GnuCash: Extract data from GnuCash files, Cleaning an XML file in Python before parsing and python: xml.etree.ElementTree, removing "namespaces" along with ElementTree docs and I'm still lost...
I've come up with this solution:
def strip_namespaces(self, tree):
nspOpen = re.compile("<\w*:", re.IGNORECASE)
nspClose = re.compile("<\/\w*:", re.IGNORECASE)
for i in tree:
start = re.sub(nspOpen, '<', tree.tag)
end = re.sub(nspOpen, '<\/', tree.tag)
# pprint(finaltree)
return
But I'm failing to apply it. I can't seem to be able to retrieve the tag names as they appear on the file.
I think below python code will be helpfull to you.
sample.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<gnc:prodinfo xmlns:gnc="http://www.gnucash.org/XML/gnc"
xmlns:act="http://www.gnucash.org/XML/act"
xmlns:book="http://www.gnucash.org/XML/book"
xmlns:vendor="http://www.gnucash.org/XML/vendor">
<gnc:change>
<gnc:lastUpdate>2018-12-21
</gnc:lastUpdate>
</gnc:change>
<gnc:bill>
<gnc:billAccountNumber>1234</gnc:billAccountNumber>
<gnc:roles>
<gnc:id>111111</gnc:id>
<gnc:pos>2</gnc:pos>
<gnc:genid>15</gnc:genid>
</gnc:roles>
</gnc:bill>
<gnc:prodtyp>sales and service</gnc:prodtyp>
</gnc:prodinfo>
PYTHON CODE: to remove xmlns for root tag.
import xml.etree.cElementTree as ET
def xmlns(str):
str1 = str.split('{')
l=[]
for i in str1:
if '}' in i:
l.append(i.split('}')[1])
else:
l.append(i)
var = ''.join(l)
return var
tree=ET.parse('sample.xml')
root = tree.getroot()
print(root.tag) #returns root tag with xmlns as prefix
print(xmlns(root.tag)) #returns root tag with out xmlns as prefix
Output:
{http://www.gnucash.org/XML/gnc}prodinfo
prodinfo

Categories

Resources