Build with BUILD (and Docker on Windows)

Some time ago a really nice open source and collaborative design tool was presented to the world of UI/UX designers: BUILD. It can be used for Web and Mobile Apps prototyping and User Research. It eases the collection of feedback from users or customers and jumpstarts the development. OK.. so far so good. This is something that you can check on the project web site.

If you check the GitHub repository you can find an explanation how you can install it on your own machine. It is quite simple really: just install Git, npm, nodejs and mongodb and after couple of command lines you are good to go!

Well.. why am I writing this post when I am sure you have already clicked on the first link, quickly installed everything and already have your first prototype up and running? I was curious about what Docker is and how it works so I decided to install Build with Docker on my Windows machine and it didn’t work that simple so I had to invest some time looking for a solution.

The steps in the README.md are the following:

1.Install Docker Engine – worked like charm if you just follow the link and install it.

2. Create file docker-compose.yml – weeeell this is where things got a bit complicated when I realized that I need to somehow install mongodb on Docker which is not described in the README file. So I spent some time trying to find a way how to install mongo (watch out it should be version 2.6.11). So how I did this?

2.1 First I created a folder (just to have it separated) docker-mongo

folder

2.2 Created the following Dockerfile there:

# Dockerizing MongoDB: Dockerfile for building MongoDB images
# Based on ubuntu:latest, installs MongoDB following the instructions from:
# http://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/

FROM ubuntu:latest
MAINTAINER Docker

# Installation:
# Import MongoDB public GPG key AND create a MongoDB list file
RUN apt-key adv –keyserver hkp://keyserver.ubuntu.com:80 –recv 7F0CEB10
RUN echo ‘deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen’ | sudo tee /etc/apt/sources.list.d/mongodb.list

# Update apt-get sources AND install MongoDB
RUN apt-get update
RUN apt-get install -y mongodb-org=2.6.11 mongodb-org-server=2.6.11 mongodb-org-shell=2.6.11 mongodb-org-mongos=2.6.11 mongodb-org-tools=2.6.11

# Create the MongoDB data directory
RUN mkdir -p /data/db

# Expose port #27017 from the container to the host
EXPOSE 27017

# Set /usr/bin/mongod as the dockerized entry-point application
ENTRYPOINT [“/usr/bin/mongod”]

2.3 Ran the command docker build -t XXX/mongo (where XXX can be anything you like)

2.4 Then adjusted the docker-compose.yml file so that it uses the mongodb (again change XXX here)

web:
image: sapbuild/build:0.3.3
ports:
– “9000:9000”
links:
– mongo

mongo:
image: XXX/mongo

3. Pull and start containers here (same step as in the README.md) – just run docker-compose up wait a bit here it might take a while and when you see something like: server cluster started and then you might go to step 4

compose

4. Opened  192.168.99.100:9000 and voila

voila

proto

Maybe there is an easier way and maybe I have done something wrong, but as my first experience with Docker it seemed a bit weird and I was happy to see it running.. It seems to work now and I hope it will help someone who might have trouble setting everything up.

Since I am interested in how it jumstarts development and now have to find out how I can later import the projects in SAP Web IDE. But that is a next open issue that I have to solve…

Cheers and happy prototyping!

 

What does SAP have against gay marriage?

I noticed today that if one tries to add a marriage relationship between business partners with same sex in SAP a pop-up comes up informing the user that the partners have the same sex!

I am aware that this messages can be  deactivated, but leaving it activated by default means something.. or am I wrong? 🙂

Here as a proof:

Unbenannt

Android Client App for consuming SAP SOAP Web services using ksoap2 library

Recently SAP lauched their SAP Mobile Platform 3.0 (more about that here ) and I decide to write about how one can develop an Android Client App that can consume SAP SOAP Web Services with the ksoap2 library. I am maybe a bit late but it still deserves some attention.

Even though recently I had the chance to try out for myself the SAP Mobile Platform 2.2 and develop some Demo Apps testing both MBO and OData concepts I still personally feel that there are many people out there that are still developing mobile apps for SAP the old-fashion-way: ask for their SAP ABAP developers to prepare a couple of web services and develop mobile clients for these.

One point is that many companies still haven’t updated their Netweaver Gateways, so they can not use the OData goodies that come along with the newer versions. Another reason is that a change of mindset is needed and acceptance of the new web technologies and REST as an architecture.  And lest but not least is that there is still not a stable concept about the offline data management. MBOs were built with keeping that in mind, but SAP has decided not to support it anymore as it was a propriatery “protocol”. The Delta Query support that they mention sounds promising here but I have to try it out in order to judge it 🙂

I have one very old post about consuming SAP web services (or it should be consuming .NET web services that call RFC-enabled Function Modules from an SAP-Backend) where the landscape I had for development was SAP Backend, Visual Studio and SAP .NET connector library. Well, this time I had SAP Backend with enabled Webservices Runtime and as any normal developer would do: I made the use of the web services that you can build with SAP from any Remote-enabled Function module and develop clients that would consume this services. The best library out there that one can make use of at the moment is ksoap2 and since it is free you can use it and adapt it to your needs. I actually came to the idea of this blog post after reading many forums where people complained that they didn’t manage to get it work or they had to change the source and rebuild it after changing the names of the tags or something in the XML bulding logic.

How am I using the ksoap2 library to consume SAP Web services? Very elegant and smooth actually.

1. I developed an wrapper class for the SAPSerializationEnvelope that inherits from the SoapSerializationEnvelope where I just make it create XML-Requests in the SAP manner.

import java.io.IOException;
import org.ksoap2.SoapFault;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.xmlpull.v1.XmlSerializer;

public class SAPSerializationEnvelope extends SoapSerializationEnvelope {

public String namespace;
 public SAPSerializationEnvelope(int version, String namespace) {
 super(version);
 this.namespace= namespace;
 }

 @Override
 public void write(XmlSerializer writer) throws IOException {

writer.setPrefix("urn", namespace);
 writer.setPrefix("soapenv", env);
 writer.startTag(env, "Envelope");
 writer.startTag(env, "Header");
 writeHeader(writer);
 writer.endTag(env, "Header");
 writer.startTag(env, "Body");
 writeBody(writer);
 writer.endTag(env, "Body");
 writer.endTag(env, "Envelope");

 }

@Override
 public Object getResponse() throws SoapFault {
 return super.getResponse();
 }

}

2. Made a helper class for making calls.

import java.util.ArrayList;
import java.util.List;
import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

public class SAPServiceCaller {

String NAMESPACE_SOAP = "http://schemas.xmlsoap.org/soap/envelope/";
 static String NAMESPACE = "urn:sap-com:document:sap:soap:functions:mc-style";
 static String SOAP_ACTION = "";

 public static SoapObject webServiceCall(SoapObject request, String URL, boolean imp_types){
 SoapObject response = null;

 SAPSerializationEnvelope envelope = new SAPSerializationEnvelope(
 SoapEnvelope.VER11, NAMESPACE);
 envelope.implicitTypes = imp_types;
 envelope.dotNet = false;
 envelope.setAddAdornments(false);
 envelope.env = SoapSerializationEnvelope.ENV;

 envelope.setOutputSoapObject(request);

 HttpTransportSE androidHttpTransport = new HttpTransportSE(URL, 30000);
 try {
 List<HeaderProperty> headerList = new ArrayList<HeaderProperty>();
 headerList.add(new HeaderProperty("Authorization", "Basic "
 + org.kobjects.base64.Base64.encode("USER:PASS"
 .getBytes())));
 headerList.add(new HeaderProperty("Connection", "Keep-Alive"));
 androidHttpTransport.debug = true;
 androidHttpTransport.call(SOAP_ACTION, envelope, headerList);
 String answer = androidHttpTransport.responseDump;
 response = (SoapObject) envelope.bodyIn;
 }catch(Exception e){
 e.printStackTrace();
 }

 return response;
 }

}

3. And used it later in calls. A simple call is as follows.

SoapObject request = new SoapObject(
 "urn:sap-com:document:sap:soap:functions:mc-style",
 "ZbapiGetRoutes");
 request.addProperty("RoutesList", "");
SoapObject response;
response = SAPServiceCaller.webServiceCall(request, URL, false);
 SoapObject routesList = (SoapObject) response.getProperty("RoutesList");

This is a simple code that calls a method to return a list of Routes that one has to visit and read the meters in homes and upload them later to the backend.
With this simple “trick” I managed to build a Demo app for an SAP Backend with ISU Component that was displaying the Routes and devices from which the Meter data were read and uploaded to the backend for later billing purposes.

Hope it helps someone out there.

In the following posts I will try to keep up with the trends and write something about what I learned from the course that SAP was offered at openSAP  about Mobile development with the SAP Mobile Platform and maybe other ways how to achieve this as well.

Happy coding!

Something about Dynamic ABAP

It has been a while that I haven’t written anything new but it was due to making some ‘life-changing’ decisions recently and their impact on my ‘free-time’ activities.
First of all I decided to move to the lovely city of Dresden and start working in a company here, then I decided to get married and since my partner moved with me we had to find a new place to stay and equip it (it really takes some time to do that). OK, ok.. I admit I have been also searching for a wedding dress, invitation design, good wedding photographer and so it really takes some time.. 😉 I will now get to the point of the post in order not to annoy the male readers.
As the title says this time I will explain couple of concepts that are offered in ABAP that offer Real Time Type Services with a real-world problem.

Following is taken from help.sap.com:

The RTTS are implemented through a hierarchy of type classes that contain the methods for RTTC (Run Time Type Creation) and RTTI (Run Time Type Identification). With the help of these system classes it is possible to determine type information of existing instances and type names of the ABAP type system at runtime and to define new data types at runtime.
The properties of the types are represented by the attributes of type objects. For each type there is exactly one type object. The attributes of the type object contain information on the properties of the type. For each kind of type (elementary type, table, class, and so on), there is a type class with special attributes for special type properties. The class hierarchy of the type classes corresponds to the hierarchy of the type kinds in the ABAP type system.
In addition, type classes for complex types, references, classes, and interfaces have special methods for specifying references to partial types. With these methods, you can navigate to all partial types via a composite type.
Type objects can only be created through the methods of type classes. To get a reference to a type object of a type, you can use the static methods of the class CL_ABAP_TYPEDESCR or call methods of the special type classes.
Most important classes that every ABAPer has to know are:
CL_ABAP_TYPEDESCR
|
|–CL_ABAP_DATADESCR
|        |
|        |–CL_ABAP_ELEMDESCR
|        |–CL_ABAP_REFDESCR
|        |–CL_ABAP_COMPLEXDESCR
|          |
|          |–CL_ABAP_STRUCTDESCR
|          |–CL_ABAP_TABLEDESCR
|
|–CL_ABAP_OBJECTDESCR
|
|–CL_ABAP_CLASSDESCR
|–CL_ABAP_INTFDESCR

One can not understand the meaning of Dynamic programming without an example so let’s see one possible scenario.
Let’s suppose you need to upload some data in a table, but you have to make the code flexible and useful for uploading data in any table, not just a pre-defined one. Usually we create an internal table, use GUI_UPLOAD and we are done. But the bad thing about this approach is that sometimes we need to reuse the code for another table but before we have to change the type of the internal table, save, activate, test the code, transfer it to productive system and these all steps take a lot of time. When you don’t really have it, it is better to find some smart fast solution 🙂
So let’s explain the code step by step:

1. Creating the variables that we will need:


REPORT  zsax_upload_zmig_tables.

TYPE-POOLS: abap, sydes.

FIELD-SYMBOLS:  TYPE table.
FIELD-SYMBOLS: .
FIELD-SYMBOLS:  TYPE ANY.

DATA: lv_tab TYPE c VALUE cl_abap_char_utilities=&gt;horizontal_tab.

DATA: lt_ftab TYPE lvc_t_fcat,
      wa_ftab TYPE LINE OF lvc_t_fcat,
      lt_ftab1 TYPE lvc_t_fcat WITH HEADER LINE,
      vals TYPE string,
      word TYPE string.

DATA: it_file TYPE REF TO data,
      is_file TYPE REF TO data,
      dref    TYPE REF TO data.

DATA: serv_dir TYPE string VALUE '/usr/sap/SAX/SYS/global',
      filepath TYPE authb-filename,
      data_tab TYPE TABLE OF string,
      wa_line TYPE string,
      wa_tab TYPE string,
      itab TYPE TABLE OF string,
      comp TYPE string.

DATA: gv_table_name   TYPE string,
      gr_type_desc    TYPE REF TO cl_abap_typedescr,
      gr_struct_desc  TYPE REF TO cl_abap_structdescr,
      gr_table_desc   TYPE REF TO cl_abap_tabledescr,
      gv_t            TYPE c,
      gv_comp         TYPE i,
      gr_table_ref    TYPE REF TO data,
      gr_struc_ref   TYPE REF TO data,
      descr_ref TYPE REF TO cl_abap_typedescr.

DATA: gt_itab   TYPE TABLE OF string,
      gt_split  TYPE TABLE OF string,
      gv_str    TYPE string,
      gv_stro    TYPE string.

DATA: td       TYPE sydes_desc.

FIELD-SYMBOLS: <table>    TYPE ANY TABLE,
               <struct>   TYPE ANY,
               <comp>     TYPE ANY.

PARAMETERS: l_file TYPE string DEFAULT 'D:\Zentraltabelle.txt'.
PARAMETERS: table LIKE dd02l-tabname DEFAULT 'ZENTRAL'.

2. Upload data in a text table


CALL FUNCTION 'C13Z_UPLOAD'
 EXPORTING
   codepage                      = '1100'
   filename                      = l_file
  TABLES
    data_tab                      = data_tab
          .

IF sy-subrc  0.
write: 'Error!'.
ENDIF.

3. Call to dynamically create the table


CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
  EXPORTING
    i_structure_name = table
  CHANGING
    ct_fieldcat      = lt_ftab
  EXCEPTIONS
    OTHERS           = 1.

IF sy-subrc NE 0.
  WRITE: / 'Fehler bei Field catalog '.
  STOP.
ENDIF.

CALL METHOD cl_alv_table_create=&gt;create_dynamic_table
  EXPORTING
    it_fieldcatalog = lt_ftab
  IMPORTING
    ep_table        = it_file.

CREATE DATA dref TYPE (table).
ASSIGN dref-&gt;* TO .

ASSIGN it_file-&gt;* TO .

cl_abap_tabledescr=&gt;describe_by_name(
      EXPORTING p_name = table
      RECEIVING p_descr_ref = gr_type_desc
      EXCEPTIONS type_not_found = 4 ).

gr_struct_desc ?= gr_type_desc.
gr_table_desc = cl_abap_tabledescr=&gt;create( gr_struct_desc ).
CREATE DATA gr_table_ref TYPE HANDLE gr_table_desc.
CREATE DATA gr_struc_ref TYPE HANDLE gr_struct_desc.
ASSIGN gr_table_ref-&gt;* TO <table>.
ASSIGN gr_struc_ref-&gt;* TO .
DESCRIBE FIELD  TYPE gv_t COMPONENTS gv_comp.

3. Perform some parameter checks (if you happen to be dealing with Dates and crazy decimals)


LOOP AT data_tab INTO gv_str.
  CLEAR: gt_split.

  SPLIT gv_str AT lv_tab INTO TABLE gt_split.

  DO gv_comp TIMES.
    READ TABLE gt_split INTO gv_str INDEX sy-index.
    ASSIGN COMPONENT sy-index OF STRUCTURE  TO .

    DESCRIBE FIELD   INTO  td.
    descr_ref = cl_abap_typedescr=&gt;describe_by_data(  ).

    CASE descr_ref-&gt;type_kind.
        " DATUM TYPE
      WHEN 'D'.

        CALL FUNCTION 'CONVERSION_EXIT_DATEX_INPUT'
          EXPORTING
            input  = gv_str
          IMPORTING
            output = gv_stro.
         = gv_stro.

      WHEN 'P'.
        " DECIMAL TYPE
        CALL FUNCTION 'HRCM_STRING_TO_AMOUNT_CONVERT'
              EXPORTING
                string                    = gv_str
               decimal_separator         = ','
               thousands_separator       = '.'
*       WAERS                     = ' '
             IMPORTING
               betrg                     =
*     EXCEPTIONS
*       CONVERT_ERROR             = 1
*       OTHERS                    = 2
                      .

      WHEN 'F'.
        " DECIMAL TYPE
        CALL FUNCTION 'HRCM_STRING_TO_AMOUNT_CONVERT'
              EXPORTING
                string                    = gv_str
               decimal_separator         = ','
               thousands_separator       = '.'
*       WAERS                     = ' '
             IMPORTING
               betrg                     =
*     EXCEPTIONS
*       CONVERT_ERROR             = 1
*       OTHERS                    = 2
                      .

      WHEN 'T'.
        " TIME TYPE
        CALL FUNCTION 'CONVERSION_EXIT_TIMLO_INPUT'
          EXPORTING
            input  = gv_str
          IMPORTING
            output = gv_stro.
         = gv_stro.

      WHEN OTHERS.
        " CHAR, NUMC... TYPE
         = gv_str.
    ENDCASE.

    CLEAR gv_str.
  ENDDO.

  INSERT INTO (table) VALUES .
ENDLOOP.

4. Keep on ABAPing… 🙂
Next time I will write about dynamic creation of objects and dynamic calls of methods from objects.

A little bit of C#… – Extracting correct table structure from a Word document

After a summer break (or  I can say a busy summer) I decided to blog about a problem that I bumped into and I see that is bothering many people but I didn’t manage to find nice implementation.

Recently I had some task to extract data from word documents and well I thought: “Easy one… just use the API that Microsoft has and in all will be done in no-time”. I was so naïve to believe that the API will be without bugs (some of them reported and many unreported ones…).

One of the greatest challenges that I faced was exactly determining the structure of the table in a word document. The table can have horizontally or vertically merged cells and table header row(s). I didn’t pay much attention for the borders and shades since that is artistic thing and not really important for the table structure (when referring the information that the table holds).

So first thing is simple table (no merging, just pure table):

Column 1 Column2 Column 3
Line 1
Line 2
Line 3

Let’s suppose that I have created following classes for representing Cells and Tables

using System;
using System.Collections.Generic;
using Word = Microsoft.Office.Interop.Word;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Tools.Word;
using System.Windows.Forms;
using Microsoft.Office.Interop.Word;
using System.Xml;

public class CustomCell
{
public int cellRow;
public int cellColumn;
public int cellRowSpan;
public int cellColumnSpan;
public String cellText;
}
public class CustomTable
{
public int rowsField;
public int columnsField;
public List cells;
}

In order to get the structure of the given table we simply need to loop through each row and cell and its contents. Simple as 1,2,3… If we have selected a table in Word we can use the following code:


Table wordTable = Application.Selection.Tables[1];
CustomTable table = new CustomTable();
table.cells = new List();
for (int row = 1; row <= wordTable.Rows.Count; row++)
{
for (int column = 1; column <= wordTable.Rows[row].Cells.Count; column++)
{
CustomCell cell = new CustomCell();
cell.cellColumn = column;
cell.cellRow = row;
cell.cellText = wordTable.Cell(row, column).Range.Text;
table.cells.Add(cell);
}
}

This code works also for a table like this:

Merged cell
Line 1
Line 2

Usually the users get creative and want something more in the tables like merged columns or cells for better data representation. In these cases this code is useless…  In case of vertically merged cells this code will still work but it is not the case if there are horizontally merged cells. There is a nice solution to use the Cell.Next method that the Word API offers. In that case the code would look like:


Table wordTable = Application.Selection.Tables[1];
Cell wordCell = wordTable.Cell(1, 1);
CustomTable table = new CustomTable();

table.cells = new List();
while(wordCell!=null)
{
CustomCell cell = new CustomCell();
cell.cellColumn = wordCell.RowIndex;
cell.cellRow = wordCell.ColumnIndex;
cell.cellText = wordCell.Range.Text;
table.cells.Add(cell);
wordCell = wordCell.Next;
}

Here is some a little bit more complex table:

Text Text Text
Text
Text

So when you think that all your problems are solved someone comes and asks for exact row and column span of each cell because maybe they want to have Word documents viewer or just embed the table in a web site. In this case the API doesn’t help with a method or attribute.. you have to find your own algorithm how to get this information. I read and read many ideas and forums: some had broken cells apart and them merged them (?!), then someone was using the height value/weight attribute to decide if the cells have row or column span and many more creative ideas. They all seemed a bit impossible for me because I thought of many exceptions that might happen and the code wouldn’t work properly. So I bumped into a comment saying that the XML structure of the table is a good place to start. So since there is a lot of documentation how to form a legal XML structure for a table I will refer to the following link http://msdn.microsoft.com/en-us/library/office/ff951689.aspx

After you master somewhat the structure you will understand the following code that goes through the cells and gets the information you need:

Table wordTable = Application.Selection.Tables[1];
Cell wordCell = wordTable.Cell(1, 1);
CustomTable table = new CustomTable();
table.cells = new List();

String s = Application.Selection.Tables[1].Range.XML;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(s);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsmgr.AddNamespace("w", "http://schemas.microsoft.com/office/word/2003/wordml");

while (wordCell != null)
{
CustomCell cell = new CustomCell();
cell.cellRow = wordCell.RowIndex;
cell.cellColumn = wordCell.ColumnIndex;
int colspan;
XmlNode exactCell = xmlDoc.SelectNodes("//w:tr[" + wordCell.RowIndex.ToString() + "]/w:tc[" + wordCell.ColumnIndex.ToString() + "]/w:tcPr/w:gridSpan", nsmgr)[0];
if (exactCell != null)
{
colspan = Convert.ToInt16(exactCell.Attributes["w:val"].Value);
}
else
{
colspan = 1;
}

int rowspan = 1;
Boolean endRows = false;
int nextRows = wordCell.RowIndex + 1;
XmlNode exactCellVMerge = xmlDoc.SelectNodes("//w:tr[" + wordCell.RowIndex.ToString() + "]/w:tc[" + wordCell.ColumnIndex.ToString() + "]/w:tcPr/w:vmerge", nsmgr)[0];

if ((exactCellVMerge == null) || (exactCellVMerge != null && exactCellVMerge.Attributes["w:val"] == null))
{
rowspan = 1;
}
else
{
while (nextRows <= wordTable.Rows.Count && !endRows)
{
XmlNode nextCellMerge = xmlDoc.SelectNodes("//w:tr[" + nextRows.ToString() + "]/w:tc[" + wordCell.ColumnIndex.ToString() + "]/w:tcPr/w:vmerge", nsmgr)[0];
if (nextCellMerge != null && (nextCellMerge.Attributes["w:val"] == null))
{
nextRows++;
rowspan++;
continue;
}
else
{
endRows = true;
}
}
}
cell.cellRowSpan = rowspan;
cell.cellColumnSpan = colspan;
cell.cellText = wordCell.Range.Text;
table.cells.Add(cell);
wordCell = wordCell.Next;
}

Last words: Copy, paste, test, use, reuse but don’t abuse 🙂

Material master creation for all organizational levels with BAPI_MATERIAL_SAVEDATA

I have to admit that I am lazy when it comes to creating test data before user trainings. Too much clicking for nothing. I’d rather stay overnight and prepare LSMW tool, eCATT tool or write some ABAP code that would either generate data or simply upload some Excel data then click it myself. This is what I’d do even if I have to add manually 3 test cases. Another good thing is that in the process of creating the tool you learn a lot about the system.

This time the problem is different, there was a need to create material master data for all organizational levels… And there were lots of them! Imagine 9 company codes, each with 1-3 plants, each plant with 1-5,6,7-13, 14, 15 or more storage locations, multiply these and you’ll see why I say “lazy”… Then there was the WM part with the warehouses, storage types and storage bins and all the materials could be placed everywhere! And the PM team asked me to do this every other week so that they could test some different scenarios… So I ABAPed my way out of it 😉

The point is the following code is just to show you that sometimes ABAPing is better than all the other tools! And with BAPI_MATERIAL_SAVEDATA you can do it all at once and you have a better insight of what is happening than with LSMW (OK.. this is just my opinion).

So let’s see what this code is all about.

First check the FM through se37 -> BAPI_MATERIAL_SAVEDATA and see which tables/structures you need. If you don’t really understand which you should use consult Google or even better consult a senior and you will realize which of them you really need. HINT: Another starting point how you can discover which of the structures you need to use is to create manually one new material in the system and see which are obligatory fields while you create it and through the technical information find out the structure where they belong. So in an hour 0r two you will (like it or not) learn all the tables that make the material master data.

REPORT  ZMM_MMASTER_ALL.
DATA: HEADDATA LIKE  BAPIMATHEAD,
CLIENTDATA LIKE  BAPI_MARA,
CLIENTDATAX LIKE  BAPI_MARAX,
PLANTDATA LIKE  BAPI_MARC,
PLANTDATAX LIKE  BAPI_MARCX,
PLANNINGDATA LIKE  BAPI_MPGD,
PLANNINGDATAX LIKE  BAPI_MPGDX,
STORAGELOCATIONDATA LIKE  BAPI_MARD,
STORAGELOCATIONDATAX LIKE  BAPI_MARDX,
VALUATIONDATA LIKE  BAPI_MBEW,
VALUATIONDATAX LIKE  BAPI_MBEWX,
WAREHOUSENUMBERDATA LIKE  BAPI_MLGN,
WAREHOUSENUMBERDATAX LIKE  BAPI_MLGNX,
SALESDATA LIKE  BAPI_MVKE,
SALESDATAX LIKE  BAPI_MVKEX,
STORAGETYPEDATA LIKE  BAPI_MLGT,
STORAGETYPEDATAX LIKE  BAPI_MLGTX,
FLAG_ONLINE LIKE  BAPIE1GLOBAL_DATA-TESTRUN,
RETURN LIKE  BAPIRET2,
S_LOC TYPE T001L-LGORT,
ST_TYPE TYPE BAPI_MLGT-STGE_TYPE,
SLS_ORG TYPE BAPI_MVKE-SALES_ORG.
TABLES: BAPI_MAKT, BAPI_MLTX, BAPI_MLAN.
TABLES: T001L, T001W.
DATA: MATERIALDESCRIPTION TYPE TABLE OF BAPI_MAKT WITH HEADER LINE,
MATERIALLONGTEXT TYPE TABLE OF  BAPI_MLTX WITH HEADER LINE,
TAXCLASSIFICATIONS TYPE TABLE OF  BAPI_MLAN WITH HEADER LINE,
IT_T001L TYPE TABLE OF T001L WITH HEADER LINE,
IT_T001W TYPE TABLE OF T001W WITH HEADER LINE.

I am sure that by analyzing these lines of code you will learn many of the tables where the material master data is stored in SAP and the people there were nice enough to give them descriptive names in the BAPI so that you find your way out easily.

Next step internal table  that represents the migration structure and will be needed in the Excel file.

* --- INTERNAL TABLE
data:begin of itab occurs 0,
MATERIAL type BAPIMATHEAD-MATERIAL,
IND_SECTOR type c,
MATL_TYPE type bapimathead-MATL_TYPE,
OLD_MAT_NO type bapi_mara-old_mat_no,
EXTMATLGRP TYPE BAPI_MARA-EXTMATLGRP,
MATL_GROUP type bapi_mara-MATL_GROUP,
MATL_DESC TYPE BAPI_MAKT-MATL_DESC,
MATL_DESCMK TYPE BAPI_MAKT-MATL_DESC,
BASE_UOM TYPE BAPI_MARA-BASE_UOM,
TRANS_GRP TYPE BAPI_MARA-TRANS_GRP,
BATCH_MGMT TYPE BAPI_MARA-BATCH_MGMT,
LOADINGGRP TYPE BAPI_MARC-LOADINGGRP,
AVAILCHECK TYPE BAPI_MARC-AVAILCHECK,
SERNO_PROF TYPE BAPI_MARC-SERNO_PROF,
MRP_TYPE TYPE BAPI_MARC-MRP_TYPE,
LOTSIZEKEY TYPE BAPI_MARC-LOTSIZEKEY,
MRP_CTRLER TYPE BAPI_MARC-MRP_CTRLER,
SM_KEY TYPE BAPI_MARC-SM_KEY,
PLND_DELRY TYPE BAPI_MARC-PLND_DELRY,
ACCT_ASSGT TYPE BAPI_MVKE-ACCT_ASSGT,
ITEM_CAT TYPE BAPI_MVKE-ITEM_CAT,
PLACEMENT TYPE BAPI_MLGN-PLACEMENT,
WITHDRAWAL TYPE BAPI_MLGN-WITHDRAWAL,
*     STGE_TYPE TYPE BAPI_MLGT-STGE_TYPE,
STGE_BIN TYPE BAPI_MLGT-STGE_BIN,
PRICE_CTRL TYPE BAPI_MBEW-PRICE_CTRL,
VAL_CLASS TYPE BAPI_MBEW-VAL_CLASS,
STD_PRICE TYPE BAPI_MBEW-STD_PRICE,
end of itab,
IT_BAPI LIKE BAPI_MAKT OCCURS 0 WITH HEADER LINE.

This table should be prepared so that it suits your needs (or your client’s needs). And following is the code for uploading the file in the table.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.
PERFORM GET_FILE.
START-OF-SELECTION.
  PERFORM UPLOAD_FILE_ITAB.
PERFORM CALL_BAPI.
*&---------------------------------------------------------------------*
*& Form GET_FILE
*&---------------------------------------------------------------------*
FORM GET_FILE.
CALL FUNCTION 'F4_FILENAME'
EXPORTING
program_name  = syst-cprog
dynpro_number = syst-dynnr
IMPORTING
file_name     = p_file.
ENDFORM.   " GET_FILE
*&---------------------------------------------------------------------*
*& Form UPLOAD_FILE_ITAB
*&---------------------------------------------------------------------*
FORM UPLOAD_FILE_ITAB .
DATA:v_file TYPE string.
MOVE p_file TO v_file. CALL FUNCTION 'GUI_UPLOAD'
EXPORTING
filename = v_file
filetype = 'ASC'
has_field_separator = 'X'
TABLES
data_tab = itab .
ENDFORM. "UPLOAD_FILE_ITAB

OK let’s get to the most serious part of this migrating code. I hope you already analyzed the BAPI and realized that for every BAPI structure there is a BAPIX 🙂 These contain the same fields like the BAPI but they are all single chars and if they have the value ‘X’ it means update should be done with the value given in the BAPI structure, if it is space update shouldn’t be done. So let’s see the core of the code…

*&---------------------------------------------------------------------*
*& Form CALL_BAPI
*&---------------------------------------------------------------------*
form CALL_BAPI .
  LOOP AT ITAB.
*CODE FOR CREATION
    SELECT *
FROM T001W
INTO IT_T001W
WHERE BEDPL = 'X'.
APPEND IT_T001W.
ENDSELECT.
*LOOP PLANTS / SORgs(Plant(2)00) / WHouses (Plant(3)) / StorageType(SLoc(1)0SLoc(4)
LOOP AT IT_T001W.
CLEAR IT_T001L[].
SELECT *
FROM T001L
INTO IT_T001L
WHERE T001L~WERKS = IT_T001W-WERKS
AND T001L~XBLGO = 'X'.
APPEND IT_T001L.
ENDSELECT.
LOOP AT IT_T001L.
*LOOP SLocs / Storage type
        CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
EXPORTING
INPUT         = ITAB-MATERIAL
IMPORTING
OUTPUT        = HEADDATA-MATERIAL
.
       " HEADDATA-MATERIAL  = ITAB-MATERIAL. " matnr
HEADDATA-IND_SECTOR = ITAB-IND_SECTOR. " indsector
HEADDATA-MATL_TYPE = ITAB-MATL_TYPE. " mattype
HEADDATA-BASIC_VIEW = 'X'.
HEADDATA-SALES_VIEW  = 'X'.
HEADDATA-PURCHASE_VIEW  = 'X'.
HEADDATA-MRP_VIEW  = 'X'.
HEADDATA-STORAGE_VIEW  = 'X'.
HEADDATA-WAREHOUSE_VIEW  = 'X'.
HEADDATA-ACCOUNT_VIEW  = 'X'.
*CLIENT DATA + UPDATE FIELD
        CLIENTDATA-MATL_GROUP = ITAB-MATL_GROUP. "matgr
CLIENTDATA-BASE_UOM = ITAB-BASE_UOM. "unitmeasure
CLIENTDATA-OLD_MAT_NO = ITAB-OLD_MAT_NO.
CLIENTDATA-EXTMATLGRP = ITAB-EXTMATLGRP.
CLIENTDATA-TRANS_GRP = ITAB-TRANS_GRP.
CLIENTDATA-BATCH_MGMT = ITAB-BATCH_MGMT.
        CLIENTDATAX-MATL_GROUP = 'X'. "ITAB-MATL_GROUP'. "matgr
CLIENTDATAX-BASE_UOM = 'X'. " ITAB-BASE_UOM. "unitmeasure
CLIENTDATAX-OLD_MAT_NO = 'X'. "ITAB-OLD_MAT_NO.
CLIENTDATAX-EXTMATLGRP = 'X'. "ITAB-EXTMATLGRP.
CLIENTDATAX-TRANS_GRP = 'X'. "ITAB-TRANS_GRP.
CLIENTDATAX-BATCH_MGMT = 'X'. "ITAB-BATCH_MGMT.
        PLANTDATA-PLANT = IT_T001W-WERKS.
PLANTDATA-PUR_GROUP = '999'.
PLANTDATA-AVAILCHECK = ITAB-AVAILCHECK.
PLANTDATA-MRP_TYPE = ITAB-MRP_TYPE.
PLANTDATA-LOTSIZEKEY = ITAB-LOTSIZEKEY.
PLANTDATA-MRP_CTRLER = ITAB-MRP_CTRLER.
PLANTDATA-SM_KEY = ITAB-SM_KEY.
PLANTDATA-PLND_DELRY = ITAB-PLND_DELRY.
PLANTDATA-LOADINGGRP = ITAB-LOADINGGRP.
PLANTDATA-SERNO_PROF = ITAB-SERNO_PROF.
        PLANTDATAX-PLANT = IT_T001W-WERKS.
PLANTDATAX-PUR_GROUP = 'X'.
PLANTDATAX-AVAILCHECK = 'X'.
PLANTDATAX-MRP_TYPE = 'X'.
PLANTDATAX-LOTSIZEKEY = 'X'.
PLANTDATAX-MRP_CTRLER = 'X'.
PLANTDATAX-SM_KEY = 'X'.
PLANTDATAX-PLND_DELRY = 'X'.
PLANTDATAX-LOADINGGRP = 'X'.
PLANTDATA-SERNO_PROF = 'X'.
*
*PLANNINGDATA-PLANT = '1100'.
*PLANNINGDATA-PLNG_MATL = ''.
*PLANNINGDATA-PLNG_PLANT  = ''.
*
*PLANNINGDATAX-PLANT = 'X'.
*PLANNINGDATAX-PLNG_MATL = 'X'.
*PLANNINGDATAX-PLNG_PLANT  = 'X'.
        STORAGELOCATIONDATA-PLANT = IT_T001W-WERKS.
STORAGELOCATIONDATA-STGE_LOC = IT_T001L-LGORT.
STORAGELOCATIONDATA-STGE_BIN = ITAB-STGE_BIN.
        STORAGELOCATIONDATAX-PLANT = IT_T001W-WERKS.
STORAGELOCATIONDATAX-STGE_LOC = IT_T001L-LGORT.
STORAGELOCATIONDATAX-STGE_BIN = 'X'.
        VALUATIONDATA-VAL_AREA = IT_T001W-WERKS.
VALUATIONDATA-PRICE_CTRL = ITAB-PRICE_CTRL.
VALUATIONDATA-VAL_CLASS = ITAB-VAL_CLASS.
VALUATIONDATA-STD_PRICE = ITAB-STD_PRICE.
        VALUATIONDATAX-VAL_AREA = IT_T001W-WERKS.
VALUATIONDATAX-PRICE_CTRL = 'X'.
VALUATIONDATAX-VAL_CLASS = 'X'.
VALUATIONDATAX-STD_PRICE = 'X'.
*WAREHOUSENUMBERDATA
        WAREHOUSENUMBERDATA-WHSE_NO = IT_T001W-WERKS(3).
WAREHOUSENUMBERDATA-PLACEMENT = ITAB-PLACEMENT.
WAREHOUSENUMBERDATA-WITHDRAWAL = ITAB-WITHDRAWAL.
        WAREHOUSENUMBERDATAX-WHSE_NO = IT_T001W-WERKS(3).
WAREHOUSENUMBERDATAX-PLACEMENT = 'X'.
WAREHOUSENUMBERDATAX-WITHDRAWAL = 'X'.
*STORAGETYPEDATA
        CONCATENATE IT_T001L-LGORT(2) IT_T001L-LGORT+3(1) INTO ST_TYPE.
STORAGETYPEDATA-WHSE_NO = IT_T001W-WERKS(3).
STORAGETYPEDATA-STGE_TYPE = ST_TYPE.
STORAGETYPEDATA-STGE_BIN = ITAB-STGE_BIN.
        STORAGETYPEDATAX-WHSE_NO = IT_T001W-WERKS(3).
STORAGETYPEDATAX-STGE_TYPE = ST_TYPE.
STORAGETYPEDATAX-STGE_BIN = 'X'.
*SALESDATA
        CONCATENATE IT_T001W-WERKS(2) '00' INTO SLS_ORG.
SALESDATA-SALES_ORG = SLS_ORG.
SALESDATA-DISTR_CHAN = '99'.
SALESDATA-ITEM_CAT = ITAB-ITEM_CAT.
SALESDATA-ACCT_ASSGT = ITAB-ACCT_ASSGT.
        SALESDATAX-SALES_ORG = SLS_ORG.
SALESDATAX-DISTR_CHAN = '99'.
SALESDATAX-ITEM_CAT = 'X'.
SALESDATAX-ACCT_ASSGT = 'X'.
* MATERIAL DESCRIPTION - MK & EN
MATERIALDESCRIPTION-LANGU = '봋'.
MATERIALDESCRIPTION-LANGU_ISO  = 'MK'.
MATERIALDESCRIPTION-MATL_DESC = ITAB-MATL_DESCMK.
APPEND MATERIALDESCRIPTION.
        MATERIALDESCRIPTION-LANGU = 'E'.
MATERIALDESCRIPTION-LANGU_ISO  = 'EN'.
MATERIALDESCRIPTION-MATL_DESC = ITAB-MATL_DESC.
APPEND MATERIALDESCRIPTION.
*TAXCLASSIFICATIONS
*MK Macedonia MWST  Output Tax  1 Taxable
TAXCLASSIFICATIONS-DEPCOUNTRY = 'MK'.
TAXCLASSIFICATIONS-DEPCOUNTRY_ISO  ='MK'.
TAXCLASSIFICATIONS-TAX_TYPE_1  = 'MWST'.
TAXCLASSIFICATIONS-TAXCLASS_1  = '1'.
APPEND TAXCLASSIFICATIONS.
        CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING
HEADDATA                   = HEADDATA
CLIENTDATA                 = CLIENTDATA
CLIENTDATAX                = CLIENTDATAX
PLANTDATA                  = PLANTDATA
PLANTDATAX                 = PLANTDATAX
*   FORECASTPARAMETERS         =
*   FORECASTPARAMETERSX        =
*    PLANNINGDATA               = PLANNINGDATA
*    PLANNINGDATAX              = PLANNINGDATAX
STORAGELOCATIONDATA        = STORAGELOCATIONDATA
STORAGELOCATIONDATAX       = STORAGELOCATIONDATAX
VALUATIONDATA              = VALUATIONDATA
VALUATIONDATAX             = VALUATIONDATAX
WAREHOUSENUMBERDATA        = WAREHOUSENUMBERDATA
WAREHOUSENUMBERDATAX       = WAREHOUSENUMBERDATAX
SALESDATA                  = SALESDATA
SALESDATAX                 = SALESDATAX
STORAGETYPEDATA            = STORAGETYPEDATA
STORAGETYPEDATAX           = STORAGETYPEDATAX
*   FLAG_ONLINE                = ' '
*   FLAG_CAD_CALL              = ' '
*   NO_DEQUEUE                 = ' '
*   NO_ROLLBACK_WORK           = ' '
IMPORTING
RETURN                     = RETURN
TABLES
MATERIALDESCRIPTION        = MATERIALDESCRIPTION
*   UNITSOFMEASURE             =
*   UNITSOFMEASUREX            =
*   INTERNATIONALARTNOS        =
*    MATERIALLONGTEXT           = MATERIALLONGTEXT
TAXCLASSIFICATIONS         = TAXCLASSIFICATIONS
*   RETURNMESSAGES             =
*   PRTDATA                    =
*   PRTDATAX                   =
*   EXTENSIONIN                =
*   EXTENSIONINX               =
.
        CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' .
write: 'PLANT: ',IT_T001W-WERKS, ' STORAGE LOCATION: ', IT_T001L-LGORT, ' ',  RETURN-type , ' MESSAGE:', RETURN-MESSAGE, '/'.
WAIT UP TO 2 SECONDS.
ENDLOOP.
ENDLOOP.
ENDLOOP. " itab
ENDFORM.                    "call_bapi

Run the code, go make some coffee and check the results after it is done 😉

ABAP, XML, XSLT and something about acquisition of web services in SAP

Long time no written I know… The last couple of months were a bit busy so I guess I owe my blog some more attention… Just noticed that there are maybe three draft posts waiting for approval:  one for the Digra conference that I visited in September (more on: DIGRA2011  and I was a speaker there about the games that I have made for motivating physical activity: Me,  but the folks were even cooler than I could imagine so they really deserve a post), one kind of a review of my new smartphone HTC Desire S (the device: HTC Desire S) and one about ABAP & JSON project that will be the only one that will see the the light of the day as soon as I finish it 😀

I promise this 2012 I will write more often and I guess someone will find what they need in the posts.

As a first 2012 post I  chose to write something about SAP and web service acquisition. This is a really simple example with simple code but it can be further extended and improved in different cases.

I would suppose that the reader is familiar with XML and XSLT (if not please check the following basic tutorials: W3Schools ) and has experience in any programming language and thus will easily “read” and understand the ABAP lines of code. Maybe he/she has also tried some web service acquisition in some other language but is an ABAP beginner and wants to get some job done really quickly.

So.. we have some web-service  from the National Bank of Republic of Macedoniacurrency download – for getting the currency exchange rates on a given date. This is one of the main things that is implemented in any project since any company needs the exchange rates in many of the processes. Since I am not here to write about business processes I will stick to the technical details.

So what one has to do first is to see what are the input parameters and what is the result from the web service. Let’s take the method GetExchangeRates as an example the input parameters are the start and end date for which we need the rates and the result is a string. When the answer is encapsulated in a string it requires some testing to get the full information about the response. I use soapUI to test the methods and analyze the answers and for this method I got something like:

<GetExchangeRateResult><![CDATA[<dsKurs>
  <xs:schema id="dsKurs" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="dsKurs" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
      <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element name="KursZbir">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="RBr" type="xs:double" minOccurs="0" />
                <xs:element name="Datum" type="xs:dateTime" minOccurs="0" />
                <xs:element name="Valuta" type="xs:double" minOccurs="0" />
                <xs:element name="Oznaka" type="xs:string" minOccurs="0" />
                <xs:element name="Drzava" type="xs:string" minOccurs="0" />
                <xs:element name="Nomin" type="xs:double" minOccurs="0" />
                <xs:element name="Sreden" type="xs:double" minOccurs="0" />
                <xs:element name="DrzavaAng" type="xs:string" minOccurs="0" />
                <xs:element name="NazivMak" type="xs:string" minOccurs="0" />
                <xs:element name="NazivAng" type="xs:string" minOccurs="0" />
                <xs:element name="Datum_f" type="xs:dateTime" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>978</Valuta>
    <Oznaka>EUR</Oznaka>
    <Drzava>ЕМУ</Drzava>
    <Nomin>1</Nomin>
    <Sreden>61.5044</Sreden>
    <DrzavaAng>EMU</DrzavaAng>
    <NazivMak>евро</NazivMak>
    <NazivAng>Euro</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>840</Valuta>
    <Oznaka>USD</Oznaka>
    <Drzava>С А Д</Drzava>
    <Nomin>1</Nomin>
    <Sreden>48.2918</Sreden>
    <DrzavaAng>USA</DrzavaAng>
    <NazivMak>САД долар</NazivMak>
    <NazivAng>US dollar</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>826</Valuta>
    <Oznaka>GBP</Oznaka>
    <Drzava>В.Британија</Drzava>
    <Nomin>1</Nomin>
    <Sreden>74.0526</Sreden>
    <DrzavaAng>U.K.</DrzavaAng>
    <NazivMak>британска фунта</NazivMak>
    <NazivAng>British pound</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>756</Valuta>
    <Oznaka>CHF</Oznaka>
    <Drzava>Швајцарија</Drzava>
    <Nomin>1</Nomin>
    <Sreden>50.7797</Sreden>
    <DrzavaAng>Switzerland</DrzavaAng>
    <NazivMak>швајцарски франк</NazivMak>
    <NazivAng>Swiss franc</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>752</Valuta>
    <Oznaka>SEK</Oznaka>
    <Drzava>Шведска</Drzava>
    <Nomin>1</Nomin>
    <Sreden>6.9485</Sreden>
    <DrzavaAng>Sweden</DrzavaAng>
    <NazivMak>шведска круна</NazivMak>
    <NazivAng>Swedish krona</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>578</Valuta>
    <Oznaka>NOK</Oznaka>
    <Drzava>Норвешка</Drzava>
    <Nomin>1</Nomin>
    <Sreden>8.0115</Sreden>
    <DrzavaAng>Norway</DrzavaAng>
    <NazivMak>норвешка круна</NazivMak>
    <NazivAng>Norwegian krone</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>392</Valuta>
    <Oznaka>JPY</Oznaka>
    <Drzava>Јапонија</Drzava>
    <Nomin>100</Nomin>
    <Sreden>62.8109</Sreden>
    <DrzavaAng>Japan</DrzavaAng>
    <NazivMak>јапонски јен</NazivMak>
    <NazivAng>Japanese yen</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>208</Valuta>
    <Oznaka>DKK</Oznaka>
    <Drzava>Данска</Drzava>
    <Nomin>1</Nomin>
    <Sreden>8.2705</Sreden>
    <DrzavaAng>Denmark</DrzavaAng>
    <NazivMak>данска круна</NazivMak>
    <NazivAng>Danish krone</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>124</Valuta>
    <Oznaka>CAD</Oznaka>
    <Drzava>Канада</Drzava>
    <Nomin>1</Nomin>
    <Sreden>47.5084</Sreden>
    <DrzavaAng>Canada</DrzavaAng>
    <NazivMak>канадски долар</NazivMak>
    <NazivAng>Canadian dollar</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>36</Valuta>
    <Oznaka>AUD</Oznaka>
    <Drzava>Австралија</Drzava>
    <Nomin>1</Nomin>
    <Sreden>49.9914</Sreden>
    <DrzavaAng>Australia</DrzavaAng>
    <NazivMak>австралиски долар</NazivMak>
    <NazivAng>Australian dollar</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>

Aha there is the definition: KursZbir is the element containing all the data about the exchange rate of the currency according the Macedonian denar. And that is the content we need to put in an ABAP table and do what is necessary with it.

So before you think about which parsing technique should be used and all that I will tell you that the solution is just couple of clicks away. SAP is really sometimes nice to developers and sometimes it is better to search for a day or two first for some technique or a module instead of writing a bunch of code.

Next please go through the following tutorial about how to set up a proxy and test it on the following link. It contains detailed explanation with screens how to create a Proxy class, set up URL and create valid SOAP message and all with couple of clicks no line of code. 🙂 This is not the only one available on the internet you can find many more…

You see here that the method is really simple and the result is just square root of the value sent. The problem is when there is a more complex structure like the one we have to deal with now. Now we have to make a transformation. It is nothing too complicated just a way to tell one really great SAP function how to transform the “weird” XML data string into table data. It is done through the Object Navigator (SE80) . Open the menu: Workbench -> Edit Object. From the dialog select transformation and then create.

For this example the following XSL transformation was used: (i put it here so that you can easily copy and paste )

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sap="http://www.sap.com/sapxsl" version="1.0">
  <xsl:strip-space elements="*"/>
  <xsl:template match="/">
    <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
      <asx:values>
        <ALL_KURS>
          <xsl:apply-templates select="//KursZbir"/>
        </ALL_KURS>
      </asx:values>
    </asx:abap>
  </xsl:template>
  <xsl:template match="KursZbir">
  <ALLKURS>
    <RBR>
      <xsl:value-of select="RBr"/>
    </RBR>
    <DATUM>
      <xsl:value-of select="Datum"/>
    </DATUM>
    <VALUTA>
      <xsl:value-of select="Valuta"/>
    </VALUTA>
    <OZNAKA>
      <xsl:value-of select="Oznaka"/>
    </OZNAKA>
    <DRZAVA>
      <xsl:value-of select="Drzava"/>
    </DRZAVA>
    <NOMIN>
      <xsl:value-of select="Nomin"/>
    </NOMIN>
    <SREDEN>
      <xsl:value-of select="Sreden"/>
    </SREDEN>
    <DRZAVAANG>
      <xsl:value-of select="DrzavaAng"/>
    </DRZAVAANG>
    <NAZIVMAK>
      <xsl:value-of select="NazivMak"/>
    </NAZIVMAK>
    <NAZIVANG>
      <xsl:value-of select="NazivAng"/>
    </NAZIVANG>
    <DATUM_F>
      <xsl:value-of select="Datum_f"/>
    </DATUM_F>
 </ALLKURS>
  </xsl:template>
</xsl:transform>

Actually writing the transformation is the hard part. The ABAP code will actually be short and simple.

Here it goes:

*&---------------------------------------------------------------------*
*& Report  Z_KURS_DOWNLOAD
*&---------------------------------------------------------------------*
 REPORT  z_kurs_download.
 DATA: proxy TYPE REF TO zkurstestco_kurs_soap .
 DATA : st_data TYPE start_date ,
        en_data TYPE end_date.
 data: cr_date type string.
 concatenate sy-datum+6(2) '.' sy-datum+4(2) '.' sy-datum(4) into cr_date.

 DATA: zinput TYPE zkurstestget_exchange_rate_so1,
       zoutput TYPE zkurstestget_exchange_rate_soa.
 DATA: result TYPE string.

 TYPES : BEGIN OF kurszbir,
       rbr TYPE string,
       datum TYPE string,
       valuta TYPE string,
       oznaka TYPE string,
       drzava TYPE string,
       nomin TYPE string,
       sreden TYPE string,
       drzavaang TYPE string,
       nazivmak TYPE string,
       nazivang TYPE string,
       datum_f TYPE string ,
 END OF kurszbir.

 DATA itab TYPE kurszbir OCCURS 0.
 DATA lt_kurs TYPE TABLE OF kurszbir.
 DATA wa_kurs TYPE TABLE OF kurszbir WITH HEADER LINE.

 TRY.
     CREATE OBJECT proxy
       EXPORTING
         logical_port_name = 'ZKURSTESTCO_KURS_SOAP_PORT'.
   CATCH cx_ai_system_fault .
 ENDTRY.

 * ADD SYSTEM DATE IN FORMAT DD.MM.YYYY
 zinput-start_date = cr_date.
 zinput-end_date = cr_date. "cr_date .

 TRY.
     CALL METHOD proxy->get_exchange_rate
       EXPORTING
         input  = zinput
       IMPORTING
         output = zoutput.
   CATCH cx_ai_system_fault .
   CATCH cx_ai_application_fault .
 ENDTRY.

 result = zoutput-GET_EXCHANGE_RATE_RESULT.
 REPLACE ALL OCCURRENCES OF '##' IN result WITH ' ' .

 TRY.
     CALL TRANSFORMATION Z_KURS_XSLT
             SOURCE XML result
             RESULT     all_kurs = lt_kurs.
   CATCH cx_ai_system_fault.
 ENDTRY.

 MOVE lt_kurs TO wa_kurs[]. 

 LOOP AT wa_kurs .
   write: / wa_kurs-datum, ' ', wa_kurs-valuta, ' ',
  wa_kurs-nomin, ' ', wa_kurs-sreden, ' ', wa_kurs-drzavaang, ' ',    wa_kurs-nazivmak, ' '.
 ENDLOOP.

The output of the program is the following:

After that you can either schedule a job or set a transaction and the client can perform it on their own.

I guess someone will find this example useful and the good thing is that it is actually possible to test it because banks are generally reliable for this kind of data 🙂

Next time maybe something more about more complex XML structures.