0

I am attempting to find and replace XML attributes with new values based on indexing. I can replace the attribute values if I hard code the attribute values themselves into my find/replace function, but I need to do so via indexing, specifically for the first two listed attributes with "text" values for both the <foo_1> and <foo_2> elements. Below is the XML, along with the script I am using and the new values to be added to the XML and desired output:

The XML ("foo_bar.xml")

<?xml version="1.0" encoding="UTF-8"?>
<Overlay>
    <foo_1>
        <bar key="value">text_1</bar>
        <bar key="value">text_2</bar>
        <bar key="value">text_3</bar>
    </foo_1>
    <foo_2>
        <bar key="value">text_4</bar>
        <bar key="value">text_5</bar>
        <bar key="value">text_6</bar>
    </foo_2>
</Overlay>

Script

import lxml.etree as ET
xml = ET.parse("C:\\Users\\mdl518\\Desktop\\bar_foo.xml")
tree=xml.getroot()

new_val_1 = float(100/202)
new_val_2 = float(200/500)
new_val_3 = float(4/44)
new_val_4 = float(4/1000)

# Find and replace first and second "bar" subelement attribute values for each "foo" parent element
for elem in tree.getiterator():
    if elem.text:
        elem.text=elem.text.replace(text_1,new_val_1)
    if elem.text:
        elem.text=elem.text.replace(text_2,new_val_2)
    if elem.text:
        elem.text=elem.text.replace(text_4,new_val_3)
    if elem.text:
        elem.text=elem.text.replace(text_5,new_val_4)
    print(elem.text)

Desired result

<?xml version="1.0" encoding="UTF-8"?>
<Overlay
    <foo_1>
        <bar key="value">new_val_1</bar>
        <bar key="value">new_val_2</bar>
        <bar key="value">text</bar>
    </foo_1>
    <foo_2>
        <bar key="value">new_val_3</bar>
        <bar key="value">new_val_4</bar>
        <bar key="value">text</bar>
    </foo_2>
</Overlay>

Is there a convenient way to index the subelement attribute values and replace them with the desired values (i.e. "new_val_#") and write to XML? Any assistance is most appreciated!

2
  • 2
    Please show desired result as I am confused of your meaning of attributes since you do not show attempt of elem.attrib in code. Commented Feb 11, 2021 at 22:23
  • I do not see in desired result how key attribute is ever used. Commented Feb 11, 2021 at 22:50

1 Answer 1

1

Consider elementwise loop, zip, on list of your needed values and iterfind generator. Run a nested loop for aligning sets of elements and values. Also there is no need to check if elem.text since every XML element has an underlying text node (empty or not). And if entire string contains text simply assign rather than replace. Do note: zip stops elementwise looping on shorter list:

# LIST OF VALUES
new_vals = [float(100/202), float(200/500), float(4/44), float(4/1000)]

# SUBLIST OF VALUES BY 2 (ADJUST 2 FOR ANY OTHER NUMBER)
sub_new_vals = [new_vals[i:i+2]  for i in range(0, len(new_vals), 2)]

for nvs, el in zip(sub_new_vals, tree.iterfind('./*')):
    # Find and replace first and second attribute values
    for nv, elem in zip(nvs, el.iterfind('./*')):
        #elem.attrib["key"] = str(round(nv, 3))       # UPDATE ATTRIBUTE VALUE
        elem.text = str(round(nv, 3))                 # UPDATE ELEMENT TEXT
        print(elem.text)
                          
output = ET.tostring(tree, 
                     encoding="UTF-8",
                     method="xml", 
                     xml_declaration=True, 
                     pretty_print=True)
  
print(output.decode("utf-8"))

Output

0.495
0.4
0.091
0.004
<?xml version='1.0' encoding='UTF-8'?>
<Overlay>
    <foo_1>
        <bar key="value">0.495</bar>
        <bar key="value">0.4</bar>
        <bar key="value">text_3</bar>
    </foo_1>
    <foo_2>
        <bar key="value">0.091</bar>
        <bar key="value">0.004</bar>
        <bar key="value">text_6</bar>
    </foo_2>
</Overlay>
Sign up to request clarification or add additional context in comments.

3 Comments

Hey, @Parfait, thank you for your edits - It works pretty slick! My apologies for doing so, but I actually updated my original question as to inquire about how to do this for two sets of elements/attributes. Does the same logic apply? Thanks again!!
Hey, @Parfait, I made one ( final) update to my original post to make the xml more well formed and more clearly pose the question at hand. When I re-ran your edit, I get the following output: <Overlay> <foo_1>0.495<bar key="value">text_1</bar> <bar key="value">text_2</bar> <bar key="value">text_3</bar> </foo_1> <foo_2>0.4<bar key="value">text_4</bar> <bar key="value">text_5</bar> <bar key="value">text_6</bar> </foo_2> </Overlay>. The 0.495 and 0.4 values need to be where 'text_1' and 'text_4' are, but it's getting closer!
Thank you, @Parfait, the updated nested loop works beautifully! I'll confirm this as the correct solution, much appreciated!! :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.