Search code examples
pythonxmlelementtree

How to write xml file root value with Python?


I have a script which I want to use to create Write Files to update a serial number on a device.

It does a folder/file path check -> Copies the Read.xml and Creates a Write.xml (Copy) -> Obtains the Serial Number to update using .getroot -> Then I want to update/write a new value to this Write.xml.

How do I change the value at root_IM1[1][2].text?

#Import File Path Verification
import os
import sys
import os.path
import time
from pathlib import Path
import shutil

#XML
import xml.etree.ElementTree as ET

#Shell Command
import subprocess

#Enter Serial No.
NAC_Ser = 012345678

#Check Folder Structure
NAC_FilePath = Path(f"C:\Folder\\{(NAC_Ser}")
folp1_2 = Path(f"C:\Folder\{(NAC_Ser}\Folder1")
folp2_2 = Path(f"C:\Folder\{(NAC_Ser)}\Folder1\Folder2_1")
folp3_2 = Path(f"C:\Folder\{(NAC_Ser)}\Folder1\Folder2_2")

if (NAC_FilePath.exists() == True) and (folp1_2.exists() == True) and (folp2_2.exists() == True) and (folp3_2.exists() == True):
    print("\n")
    print("Folder Check Passed\n")

else:
    print("\n")
    print("Failed Folder Check")
    
#Declare Filepaths Global
global fp1_2

#Check File Exist
fp1_2 = Path(f"C:\Folder\{(NAC_Ser)}\Folder1\Folder2_1\ReadIm1.xml")

if (fp1_2.exists() == True):
    print("File Check Passed\n")
    print("XML File Imported\n")
else:
    print("Failed File Check")

# Copy Read Files
# Rename Read Files (Copy) to Write Files
# Write New Values to  Write.XML 

print("\n")
print("Creating Write XML Files...")

# Source Filepath for Copy -> Destination Filepath -> New File Name
print("\n")
print("Source FilePath to Copy:")
print(fp1_2)
print("\n")
print("New Destination FilePath & New FileName:")
dest_fp1 = Path(f"C:\Folder\{(NAC_Ser)}\Folder1\Folder2_1\WriteIM1.xml")
print(dest_fp1)
shutil.copy2(fp1_2, dest_fp1) # Copy the file


#XML Query 
tree_IM1 = ET.parse(dest_fp1)
root_IM1 = tree_IM1.getroot()

#IM1 -> IIB PCB Serial (Current)
IM1_IIBSerial = (f"{root_IM1[1][2].text}")
print(IM1_IIBSerial)

#IM1 -> IIB PCB Serial (New Updated)
IM1_IIBSerial_New = 12345
print(IM1_IIBSerial_New)

Solution

  • You can assign the new value to root_IM1[1][2].text before writing the new file.

    import xml.etree.ElementTree as ET
    
    tree_IM1 = ET.parse(dest_fp1)
    root_IM1 = tree_IM1.getroot()
    root_IM1[1][2].text = "12345"
    tree_IM1.write(dest_fp1)
    

    This method is potentially prone to errors if the order of the tags were to change. A safer approach would be to use a XPath expression to specify the tag which is to be updated.

    To demonstrate here is a toy XML file, data.xml which uses the same structure to that outlined in the question. The root tag users has two child elements, each of which has three child elements. We want to change the city of the second user to Paris.

    <users>
        <user>
            <name>Jane Doe</name>
            <age>25</age>
            <city>New York</city>
        </user>
        <user>
            <name>John Smith</name>
            <age>27</age>
            <city>London</city>
        </user>
    </users>
    

    We could use the same code as provided above.

    import xml.etree.ElementTree as ET
    
    xml_file_path = "data.xml"
    
    tree = ET.parse(xml_file_path)
    root = tree_IM1.getroot()
    root[1][2].text = "Paris"
    tree.write(xml_file_path)
    

    A more robust solution is possible using XPath to find the element to update. Notice that the XML is parsed using a different module, this is because the xml.etree.ElementTree module does not support XPath expressions like lxml does.

    from lxml import etree
    
    # Path to XML file
    xml_file_path = "data.xml"
    
    # Parse the XML file using lxml
    tree = etree.parse(xml_file_path)
    
    # Define the XPath to find the city in the second user element
    xpath_expression = "/users/user[2]/city"
    
    # Use XPath to find the city element
    city_elements = tree.xpath(xpath_expression)
    
    # Check if city_elements is not empty (i.e., city is found)
    if city_elements:
        # Select the first city element (assuming there's only one)
        city_element = city_elements[0]
    
        # Update the text of the city element
        city_element.text = "Paris"
    
        # Write the updated XML back to the file
        tree.write(xml_file_path)
    
        print("City updated successfully for the second user.")
    else:
        print("City not found for the second user.")
    

    Resulting XML file:

    <users>
        <user>
            <name>Jane Doe</name>
            <age>25</age>
            <city>New York</city>
        </user>
        <user>
            <name>John Smith</name>
            <age>27</age>
            <city>Paris</city>
        </user>
    </users>
    

    For completeness, here is a way to emulate the XPath solution using functions available in xml.etree.ElementTree:

    import xml.etree.ElementTree as ET
    
    # Path to XML file
    xml_file_path = "data.xml"
    
    # Parse the XML file
    tree = ET.parse(xml_file_path)
    root = tree.getroot()
    
    # Define the XPath-like expression to find the city in the second user element
    user_elements = root.findall("user")
    if len(user_elements) > 1:
        second_user = user_elements[1]  # Get the second user
        city_element = second_user.find("city")  # Find the city element under the second user
        
        # Check if city_element is found
        if city_element is not None:
            # Update the text of the city element
            city_element.text = "Paris"
    
            # Write the updated XML back to the file
            tree.write(xml_file_path)
    
            print("City updated successfully for the second user.")
        else:
            print("City not found for the second user.")
    else:
        print("Second user not found in the XML data.")