Recently I was presented with an interesting mapping requirement. This interface was to be part of a prototype scenario for MDM and ECC to transfer materials back and forth. Im going to concentrate strictly on the mapping and context changes in this blog instead of explaining the entire scenario. We start with an Excel document that is exported to CSV format. The original Excel document has several multidimensional fields, and looks like this:
As you can see, the fields UM (unit of measure), Weight, Length, Width, and Height all have multiple values. Each value separated by a semicolon ( ; ) corresponds to the other values separated by this delimiter. In other words, if we want to refer to the material by the first unit of measurement (EA), then the weight, length, width, and height of the material are all going to be the first values in each field. If we refer to the unit of measurement by the second value (IP), we need to call all of the values in the second delineated position in each corresponding field. Because of this requirement, it makes sense for us to create an XML structure that will make it easy to manage and read these values with the others that they correspond to. The XML structure that was created looks like this:
We are going to pass the first values in each field of the source message to the header, and all of the remaining values will get split up into new UOM contexts. A sample source message would look as follows:
And we want the resulting message to look something like this:
At first, I thought this may be possible to do strictly with graphical mapping. Because as many of you probably know, there is a function provided by SAP that almost makes this possible. The SplitByValue function, which will automatically create a context change (a context change is when the value gets put into the next instance of its parent, for example the image above) when it detects that the source field has occurred more than once. In other words, if our source message had those multidimensional fields split up into separate instances of the same field name, then SplitByValue would automatically create our context changes for us. However, we are not this lucky, because we have to get multiple values out of the source field, and SplitByValue only accepts one value as its input at a time in graphical mapping. So our only option is to create a user-defined function in XI and write a little bit of Java that will take care of this problem for us.
If you are not a sharp coder, dont fret. I had never written a line of Java in my life before being presented with this problem, and after digging around a bit I realized that the function is not going to require a deep knowledge of Java syntactically. Just as long as you can work through the logic, youll be set. So lets go head and jump into the mapping.
First, we are going to be making use of two function calls for working with context changes. The first being  result.addValue() and the second being  result.addContextChange(). Because we are going to be returning more than one value, we need to define our UDF (user-defined function) as type Queue. This will let us generate multiple values with only one function and source input. The result.addValue() function allows us to return a value, in other words, send data to the target field. The result.addContextChange() function basically tells the function that the next time it sends data, it needs to put that data in the next context (parent). So naturally, you may be asking what context is it going to go into? This is a good question, and a potential pitfall. Let me explain
When I was first approaching this problem, I was assuming that when you included the result.addContextChange() function in your code, that XI would automatically generate the parent structure for you so that the value could be passed there. This is not the case. You actually have to create the parent node in the mapping so that the child fields have some place to go when the context change is performed. In other words, we have to do a mapping on the parent node as well. Take a look at the next picture:
When you look closely, you will notice that the UM field is mapping to both the UOM parent field as well as the MEINS child field. Keep in mind the UM field in the source message will be holding the values (EA;IP;CA;PT). So we first have to create a Java function that will take each value from the source field (UM) and create a parent structure for all the child fields to be placed into. So we start by creating a new user-defined function:
We select type queue for reasons that were explained earlier. Now pay close attention to the arguments list. If you have done any programming in the past (I am a C hacker myself) you probably know that when you develop a function you have to define the variables that are passed to that function. For example: myFunction(int a, string b); Well similarly in XI, when you write a function, you have to store the incoming data in a variable. Each Argument you define is essentially going to become a variable in your function. So in this case we have defined an argument input. This means that when we use this function in our mapping, the UM field is going to pass its string into our function, and that data is now going to be in the variable input for us to manipulate. So lets do some quick coding.
The first thing that we need to do is split the incoming string into multiple variables, or an array. That way we can count how many times we need to create a new parent node. We can do that with a line of code such as the following:
String x = input.split(;);
Because we chose to use a queue function, the arguments that we define automatically become arrays, because XI is assuming that we want to operate on multiple input values with this function. And XI is correct in assuming this! So we need to be careful to specify that we are operating on the first input, which would be position  of input. Basically, the line of code above takes the incoming string and splits the values (separated by a semicolon, hence the ;) and places them into the array x. So if we were to operate on x, we would be operating on the value EA. If we were to operate on x, we would be operating on the value IP. Now we can make a loop that runs from 0 to the number of values stored in the array x. This can be done with a loop such as the following:
for (int i = 1; i < x.length; i++)
I chose to set i equal to 1 instead of zero because I will be passing the first value of the source string to a field outside of the UOM node, in the header parent. So we want to create one less UOM parent than the total. Then inside the loop, we want to simply pass a value containing a blank space back to the UOM parent. Keep in mind, we are not ultimately going to be using the UOM parent node to store values, we just need to to be created in order to place the child fields into it. So our next line of code looks like this:
Now, that is basically all that has to be done. I modified by program slightly, by placing a loop on the outside, because I will be expecting multiple source headers. So in order to accommodate for this, we need to loop through the input array as well. The code ultimately looks like this:
And the mapping looks like this:
Now, all we have left to do is create one more java function that we can use operate on the source fields in order to pass the values to the respective target fields. Lets take a look at the mapping for the first child in the UOM structure.
As you can see, there is a user-defined function called jumpContext. The code in here is very similar to that of the createContext UDF, with the only exception that we are not passing back a blank space, instead we are passing back the actual values. So we will change our code to have the following line:
As you can see we are going to be passing back the value x[i], where i is a variable defined in a loop. When we put it all together, our function looks like this:
So as you can see, this is a piece of cake! As you can probably also see, I am not a very good software developer. So Im sure that there are 20 different better ways to write that code, but this is the quick and dirty way that we took care of it. So, the final mapping will look like this:
The import maps are:
1. Material_Recordset\Material_Structure —.> Material_XML\Material_CSV_Header
2. Material_Recordset\Material_Structure\UM —.> Material_XML\Material_CSV_Header\UOM
3. Material_Recordset\Material_Structure\UM —.> Material_XML\Material_CSV_Header\UOM\MEINS
4. Material_Recordset\Material_Structure\WEIGHT —.> Material_XML\Material_CSV_Header\UOM\NTGEW
5. Material_Recordset\Material_Structure\LENGTH —.> Material_XML\Material_CSV_Header\UOM\LAENG
6. Material_Recordset\Material_Structure\WIDTH —.> Material_XML\Material_CSV_Header\UOM\BREIT
7. Material_Recordset\Material_Structure\HEIGHT —.> Material_XML\Material_CSV_Header\UOM\HOEHE
As I mentioned before, I had never coded any Java before this, but with the help of the SDN community I got this working. So hopefully this will be helpful to those who may have trouble with the other blogs / guides out there. Have fun mapping!