Copy xml element multiple time in given range

Viewed 182

I have case where need to copy xml element multiple time based on data received from csv file. we csv records in list of dictionaries and we are taking the length of csv file and also number of times we need to copy the same element into single head tag. Case: if we have len_csv records 14 and number of elements needs copy 4 into single head tag i.e 14/4=3.5 means 4 root tags to fit all 14 tags into all 4 tags means last one root tag should only have 2 tags. Sample xml:

<head_tag>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
</head_tag>

Expected Output:-

<head_tag>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
</head_tag>

What i am getting:

<head_tag>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
</head_tag>

Now we should have tag 14 times and number copies 4 in each root tag i.e. 3 and last root tag should only have 2 copies of tag as condition mentioned above.

Code:

from lxml import etree
from copy import deepcopy
src='abc.xml'
tree = etree.parse(src)
#Get the root element
root=tree.getroot()
#Get the namespace
nsmap = {k if k is not None else 'default':v for k,v in root.nsmap.items()}
check_value= lambda x: int(x) if x==int(x) else int(x)+1 
#let suppose length of csv_records is 14 which having list of dict
len_csv=14
def copy_element(tag_name, len_csv, num_of_copies, batches):

    #Copying the head tag as par the calculation in this case 4
    for name in root:
        for i in range(num_of_copies-1):
            new_name = deepcopy(name)
            name.addnext(new_name)

    k=batches-1
    #Copying the tag for xpath given in this case <a> tag as per the calculation
    for name in root.findall(tag_name, namespaces=nsmap):
        get_last_val=len_csv%num_of_copies
        for i in range(num_of_copies):
             if k==num_of_copies-1 and get_last_val!=0:
                batches=get_last_val
             if len_csv!=1:
                for j in range(batches-1):
                    if len_csv<=1:
                        break                        
                    new_name = deepcopy(name)
                    name.addnext(new_name)
                len_csv-=batches
                break
        k-=1

    #Caluclation how many head tags required for batch process
    head_tag_required=len_csv/4
    copy_element('root/body/values/ns:value/a', len_csv, check_value(head_tag_required),4)
    tree.write(src)

The above code is creating 4 tags correctly but tag creating 4 times in each head tag. I have tried to break the findall() condition but none of them are working.

So, My questions are:

  1. Is there any way we can fix the above issue.? How we can copy the same element multiple times for given xpath that can satisfy above conditions
  2. also the xpath we are passing is not working, it's unable to find the element due to namespace, How can we fix this issue? the output shown above is element doesn't have any namespace..
1 Answers

Your question has multiple questions which isn't that good for a Stack OVerflow question, therefore I tried to tackle 1 thing here:

Generate multiple branches in the tree structure:

import lxml
from lxml import etree
import xml.etree.ElementTree as ET
import copy

ns_dict={"ns": "http://schemas.org/"}

s = '''
<head_tag>
    <root xmlns:ns="http://schemas.org/">
        <body>
            <values>
                <ns:value>
                    <a>HI1</a>
                </ns:value>     
            </values>
        </body>
    </root>
</head_tag>
'''

n = 14
l = 4

lst_entries = [l if x*l<=n else n%l for x in range(1,1+round(n/l))]


root = lxml.etree.fromstring(s)

for i in range(0,round(n/l)):
    dupe = root.xpath(r"/head_tag/root/body/values/ns:value", namespaces=ns_dict)[0]
    for j in range(0, lst_entries[i]):
        dupe.append(etree.fromstring('<a>HI1</a>'))


print("\n".join(str(ET.tostring(root)).split()))

You then can calculate the stop value as you wish, if you do not want to have a exact copy at the end of the tree, i would suggest to modify the last branch after you made a simple copy of it. You can see the position o that code here: # remove the addtional <

Related