csvgroovyjmetermarkupbuilderxml-builder

Create XML object with dynamical content from a source CSV file


I have the following CSV file:

iDocType,iDocId,iName,iDate
P,555551555,Braiden,2022-12-31
I,100000001,Dominique,2024-12-10
P,100000002,Joyce,2025-11-15

Using jmeter's JSR223 preprocessor element, I need to compose an XML parent node containing multiple (based on parametrization) child-nodes and each node must contain properties with values of each of these CSV rows.

I think that I need some way to loop over this CSV file and extract values from each row until all of my target objects are composed. Maybe the approach should be having a method called createMasterXml with 2 arguments like findTargetIdInCsv and targetNumberOfXmlNodes and a for loop inside which parses the csv file and composes child node inside it with the groovy.xml.MarkupBuilder. But I don't know how to approach the problem.

Target logic:

  1. find csv row based on an ID variable
  2. compose the first object with values from the first found row with this ID
  3. find next csv row downwards
  4. compose the 2nd object with values from the 2nd row .....
  5. do this until the target number of objects are created
  6. if the end of the file is reached start from the top row of the file (without the header)

For example, given the csv file described above:

I get a variable docId populated with the value 100000001 which is found on the 2nd row of data in the csv file (ignoring the header);

I define a variable numberOfNodes = 3;

Then I expect an object created by this mapping:

child node 1 - ValuesFromCsvRow2

child node 2 - ValuesFromCsvRow3 

child node 3 - ValuesFromCsvRow1

Update: JSR223 PreProcessor code: (Note, with this current approach I am not able to compose the sub-nodes objects based on my intended logic described above, because the current approach does not handle parsing the CSV file and extracting values - I am missing the knowledge to do that)

//input from csv file
docType = vars.get('iDocType').toString()
docId = vars.get('iDocId').toString()
name = vars.get('iName').toString()
date = vars.get('iExpiryDate').toString()

def numberOfNodes = 3

def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)

xml.nodes() {
createNode(xml, numberOfNodes, 'ID0000')
}

def createNode(builder, repeat, pReqID) {
   for (int i = 0 ; i < repeat ; i++) {
       builder.object(a:'false', b:'false', pReqID:pReqID +(i+1).toString()) {
        builder.al(ad:'2021-09-20', alc:'bla', bla:'2021-09-20T11:00:00.000Z', sn:'AB8912')
        builder.doc( docType: docType, docId: docId, name: name , date:date )
       }
    }
}

def nodeAsText = writer.toString()
//log.info(nodeAsText)

vars.put('passengers3XmlObj', nodeAsText)   

The values from the code line with builder.doc is the one which I need to change on each node creation, based on the values from each line in the source csv file.

Currently, in this situation, my master object looks like this, because in each jmeter iteration I know only how to get the values from one row from the csv file, per sampler (using CSV Data Set test plan element):

<objects>
  <object a='false' b='false' pReqID='ID00001'>
    <al ad='2021-09-20' alc='bla' bla='2021-09-20T11:00:00.000Z' sn='AB8912' />
    <doc docType='P' docId='100000001' date='2024-12-10' name='Dominique' />
  </object>
  <object a='false' b='false' pReqID='ID00002'>
    <al ad='2021-09-20' alc='bla' bla='2021-09-20T11:00:00.000Z' sn='AB8912' />
    <doc docType='P' docId='100000001' date='2024-12-10' name='Dominique' />
  </object>
  <object a='false' b='false' pReqID='ID00003'>
    <al ad='2021-09-20' alc='bla' bla='2021-09-20T11:00:00.000Z' sn='AB8912' />
    <doc docType='P' docId='100000001' date='2024-12-10' name='Dominique' />
  </object>
</objects>

But, I need it to look like this, keeping in mid the target logic:

<objects>
  <object a='false' b='false' c='ID00001'>
    <al ad='2021-09-20' alc='bla' dt='2021-09-20T11:00:00.000Z' sn='AB8912' />
    <doc docType='I' docId='100000001' date='2024-12-10' name='Dominique' />
  </object>
   <object a='false' b='false' c='ID00002'>
    <al ad='2021-09-20' alc='bla' dt='2021-09-20T11:00:00.000Z' sn='AB8912' />
    <doc docType='P' docId='100000002' date='2025-11-15' name='Joyce' />
  </object>
  <object a='false' b='false' c='ID00003'>
    <al ad='2021-09-20' alc='bla' dt='2021-09-20T11:00:00.000Z' sn='AB8912' />
    <doc docType='P' docId='555551555' date='2022-12-31' name='Braiden' />
  </object>
</objects>  

Can someone please help me achieve this ?


Solution

  • CSV Data Set Config by default reads next line on each iteration of each virtual user. The behaviour is controllable up to certain extend by the Sharing Mode setting but none of the sharing modes is suitable for reading the whole content of the CSV file at once.

    If you want to parse all the entries from the CSV file in a single shot - do the reading/parsing in Groovy itself

    Something like:

    def writer = new StringWriter()
    def xml = new groovy.xml.MarkupBuilder(writer)
    def pReqID = 'ID0000'
    def lines = new File('test.csv').readLines()
    
    
    xml.nodes() {
        1.upto(lines.size() - 1, { lineNo ->
            xml.object(a: 'false', b: 'false', pReqID: pReqID + (lineNo).toString()) {
                xml.al(ad: '2021-09-20', alc: 'bla', bla: '2021-09-20T11:00:00.000Z', sn: 'AB8912')
                xml.doc(docType: lines.get(lineNo).split(',')[0],
                        docId: lines.get(lineNo).split(',')[1],
                        name: lines.get(lineNo).split(',')[2],
                        date: lines.get(lineNo).split(',')[3])
            }
        })
    }
    
    def nodeAsText = writer.toString()