Search code examples
xmlgroovyxml-parsingnodessap-pi

Add node to the XML if does not exist?


i have following problem. I need to add a Node, if its not available. For example

Input:

<DataTable>
    <DataRow>
        <Field1>1</Field1>
    </DataRow>
    <DataRow>
        <Field1>1</Field1>
        <Field2>1</Field2>
    </DataRow>
    <DataRow>
        <Field1>1</Field1>
        <Field2>1</Field2>
        <Field3>1</Field3>
    </DataRow>
</DataTable>

Expected output:

<DataTable>
    <DataRow>
        <Field1>1</Field1>
        <Field2/>
        <Field3/>
    </DataRow>
    <DataRow>
        <Field1>1</Field1>
        <Field2>1</Field2>
        <Field3/>
    </DataRow>
    <DataRow>
        <Field1>1</Field1>
        <Field2>1</Field2>
        <Field3>1</Field3>
    </DataRow>
</DataTable>

So if the three nodes in a row are not available, than they should be added to the result XML.

This the code I tried, it works only with a flat xml.

import com.sap.gateway.ip.core.customdev.util.Message
import groovy.xml.XmlUtil

Message processData(Message message) {
    def fields = ['Field1', 'Field2', 'Field3']
    def payload = new XmlParser().parse(message.getBody(Reader))
    (fields - (payload.children() as List<Node>)*.name()).each { payload.appendNode(it) }
    message.setBody(XmlUtil.serialize(payload))
    return message
}

I want to add Tag to the output file. Thank you


Solution

  • So, for the conversion we can write a function that takes a string (the input xml) and a list of required fields, and returns the new xml

    import groovy.xml.XmlParser
    import groovy.xml.XmlNodePrinter
    
    String extendFields(String xmlInput, List<String> fields) {
        def xml = new XmlParser().parseText(xmlInput)
        xml.findAll { it.name() == 'DataRow' }.each { row ->
            fields.each { field ->
                if (!row."$field") {
                    row.appendNode(field)
                }
            }
        }
        new StringWriter().with { out ->
            new XmlNodePrinter(new PrintWriter(out)).with { writer ->
                writer.preserveWhitespace = true
                writer.print(xml)
            }
            out.toString()
        }
    }
    

    Calling this with your input XML:

    def input = '''<DataTable>
        <DataRow>
            <Field1>1</Field1>
        </DataRow>
        <DataRow>
            <Field1>1</Field1>
            <Field2>1</Field2>
        </DataRow>
        <DataRow>
            <Field1>1</Field1>
            <Field2>1</Field2>
            <Field3>1</Field3>
        </DataRow>
    </DataTable>'''
    
    println extendFields(input, ['Field1', 'Field2', 'Field3'])
    

    prints

    <DataTable>
      <DataRow>
        <Field1>1</Field1>
        <Field2/>
        <Field3/>
      </DataRow>
      <DataRow>
        <Field1>1</Field1>
        <Field2>1</Field2>
        <Field3/>
      </DataRow>
      <DataRow>
        <Field1>1</Field1>
        <Field2>1</Field2>
        <Field3>1</Field3>
      </DataRow>
    </DataTable>
    

    So then, you can change your method in the question to call this new method:

    Message processData(Message message) {
        def fields = ['Field1', 'Field2', 'Field3']
        def payload = extendFields(message.getBody(Reader), fields)
        message.setBody(payload)
        return message
    }
    

    Pulling this out into a separate method also has the advantage of making it more easily testable 😀

    Edit

    If you want to sort the nodes in the order of the fields you provide, you can add a sort on the children like so:

    String extendFields(String xmlInput, List<String> fields) {
        def xml = new XmlParser().parseText(xmlInput)
        xml.findAll { it.name() == 'DataRow' }.each { row ->
            fields.each { field ->
                if (!row."$field") {
                    row.appendNode(field)
                }
            }
            row.children().sort { fields.indexOf(it.name()) }
        }
        new StringWriter().with { out ->
            new XmlNodePrinter(new PrintWriter(out)).with { writer ->
                writer.preserveWhitespace = true
                writer.print(xml)
            }
            out.toString()
        }
    }
    

    Then calling this with the fields in the order we want:

    println extendFields(input, ['Field3', 'Field1', 'Field2'])
    

    Prints:

    <DataTable>
      <DataRow>
        <Field3/>
        <Field1>1</Field1>
        <Field2/>
      </DataRow>
      <DataRow>
        <Field3/>
        <Field1>1</Field1>
        <Field2>1</Field2>
      </DataRow>
      <DataRow>
        <Field3>1</Field3>
        <Field1>1</Field1>
        <Field2>1</Field2>
      </DataRow>
    </DataTable>