1

Hi I have duplicate node names in my xml and would like to make each of them unique. As the amount of nodes is unknown, I would like to implement a for each loop and add a counter to the name of each UserGroup node using XSLT.

I am using :

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

My input xml is:

<groups>
        <UserGroup>
                <integrationKey>xxa</integrationKey>
                <uid>001</uid>]
        </UserGroup>
        <UserGroup>
                <integrationKey>xxb</integrationKey>
                <uid>002</uid>
        </UserGroup>
</groups>

How can I use XSLT to transform the xml into:

<groups>
        <UserGroup1>
                <integrationKey>xxa</integrationKey>
                <uid>001</uid>]
        </UserGroup1>
        <UserGroup2>
                <integrationKey>xxb</integrationKey>
                <uid>002</uid>
        </UserGroup2>
</groups>

EDIT Hi, the reason is I am processing a xml message payload that is built up and then needs to be converted to JSON (The groups part is attached to the main xml). The target system the payload is going to accepts a certain "format" that doesnt include UserGroup. Unfortunately the amount of UserGroups is dynamic and the mapping has limited control. I have attempted to build the Json in Groovy with:

import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import groovy.json.JsonBuilder

    def body = """

{"B2BCustomer":{"integrationKey":"","customerID":"xxx","email":"xxx","name":"xxx","uid":"xxx","businessUnit":{"BU":{"integrationKey":"","code":"xxx"}},"groups":[{"UserGroup":{"integrationKey":"xxx","uid":"xxx"},"UserGroup":{"integrationKey":"yyy","uid":"yyy"}}]}}"""
 
    //Setup output JSON
    def jsonParser = new JsonSlurper();
    def jsonObject = jsonParser.parseText(body);
    body = JsonOutput.toJson(jsonObject["B2BCustomer"]);
    output = jsonParser.parseText(body);
    jsonString = jsonParser.parseText(body);
    
    //Create default b2b unit JSON
    if(output.containsKey("defaultB2BUnit")){
        output.remove("defaultB2BUnit"); 
        defaultB2BUnit = JsonOutput.toJson(jsonString["defaultB2BUnit"]);
        jsonObject = jsonParser.parseText(defaultB2BUnit);
        defaultB2BUnit =  JsonOutput.toJson(jsonObject["B2BUnit"]);
        output.put("defaultB2BUnit", defaultB2BUnit);
    }
    //Create businessUnit JSON
    if(output.containsKey("businessUnit")){
        output.remove("businessUnit"); 
        businessUnit = JsonOutput.toJson(jsonString["businessUnit"]);
        jsonObject = jsonParser.parseText(businessUnit);
        businessUnit =  JsonOutput.toJson(jsonObject["BU"]);
        output.put("businessUnit", businessUnit);
    }
    //Create groups JSON
    if(output.containsKey("groups")){
        output.remove("groups"); 
        groups = JsonOutput.toJson(jsonString["groups"]);
        jsonObject = jsonParser.parseText(groups);
        groups =  JsonOutput.toJson(jsonObject["UserGroup"]);
        output.put("groups", groups);
    }
    //Build output JSON
    def builder = new JsonBuilder();
    builder(output);
    def builderString = builder.toString().replace('"{\\', '{').replace('\\','').replace('}"','}').replace('"[','[').replace(']"',']');
    println builderString;

This works if the keys are not duplicate (It only pulls a single UserGroup out. My plan is to run this code still, but the groups part in a loop to get the desired JSON output.

If someone has a way I dont have to do string replacements that would be awesome too. Unfortunately I can't change the structure of the target systems metadata.

Current output:

{
    "integrationKey": "",
    "customerID": "xxx",
    "email": "xxx",
    "name": "xxx",
    "uid": "xxx",
    "businessUnit": {
        "integrationKey": "",
        "code": "xxx"
    },
    "groups": [{
            "integrationKey": "yyy",
            "uid": "yyy"
        }
    ]
}

Desired Output:

{
    "integrationKey": "",
    "customerID": "xxx",
    "email": "xxx",
    "name": "xxx",
    "uid": "xxx",
    "businessUnit": {
        "integrationKey": "",
        "code": "xxx"
    },
    "groups": [{
            "integrationKey": "xxx",
            "uid": "xxx"
        },
        {
            "integrationKey": "yyy",
            "uid": "yyy"
        }
    ]
}

Extra note I'm considering just storing the groups in an exhange property and Running a custom xml -> Json Groovy script to build it exactly how I want without weird effects from JSON slurper etc. Thank you for your effort and advice in advance!

5
  • Where exactly are you stuck with this? It's a fairly trivial task. -- P.S. Are you sure you want to do this? You will be creating a format that is difficult to process. Adding an attribute with a serial number could be a better way. Commented Jan 25, 2022 at 5:33
  • 1
    What you are doing is a thoroughly bad idea: the resulting document will be much harder to work with than the original. Tell us instead why the duplicate element names are a problem. Commented Jan 25, 2022 at 9:37
  • "I have duplicate node names in my xml and would like to make each of them unique." - It may seem like this at first, but you really don't want to make them unique. Can you explain where this would be an improvement? Commented Jan 25, 2022 at 12:18
  • Hi, the reason is I am processing a xml message payload that is built up and then needs to be converted to JSON (The groups part is attached to the main xml). The system the payload is going to accepts a certain "format" that doesnt include UserGroup. Unfortunately the amount of UserGroups is dynamic and the mapping has limited control. Commented Jan 25, 2022 at 20:34
  • I edited the main post to give more context hopefully. Commented Jan 25, 2022 at 20:57

1 Answer 1

1

Given XSLT 3, one way would be to use an accumulator:

  <xsl:accumulator name="UserGroupCounter" as="xs:integer" initial-value="0">
    <xsl:accumulator-rule match="groups" select="0"/>
    <xsl:accumulator-rule match="groups/UserGroup" select="$value + 1"/>
  </xsl:accumulator>
  
  <xsl:template match="UserGroup">
    <xsl:element name="{name()}{accumulator-before('UserGroupCounter')}">
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <xsl:mode on-no-match="shallow-copy" use-accumulators="UserGroupCounter"/>

I agree with the comments made that the result format is not a good XML format. The usual advice, if you need some element index, is to not put it into the element name, but rather use an attribute e.g.

  <xsl:accumulator name="UserGroupCounter" as="xs:integer" initial-value="0">
    <xsl:accumulator-rule match="groups" select="0"/>
    <xsl:accumulator-rule match="groups/UserGroup" select="$value + 1"/>
  </xsl:accumulator>
  
  <xsl:template match="UserGroup">
    <xsl:copy>
      <xsl:attribute name="userGroupIndex" select="accumulator-before('UserGroupCounter')"/>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

  <xsl:mode on-no-match="shallow-copy" use-accumulators="UserGroupCounter"/>
Sign up to request clarification or add additional context in comments.

Comments

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.