Skip to Content
Author's profile photo Vitalij -

Tutorial Excel report with Transformation XSLT

XSLT – is a language for transforming XML. In this article we take XML document with empty table then fill it and pass it to EXCEL. Technically, it is serialization.

Advantages: speed, flexibility.

XML – put it simply, is a syntax, which we use to mark data. In our case tables for Excel marked with Excel XML processor.

As it says in Wikipeida:

XSL (Extensible Stylesheet Language) is a family of languages used to transform and render XML documents, split into: XSLT (XSL Transformations), XSL-FO (XSL Formatting Objects), XPath (XML Path Language).

XPath (XML Path Language), a non-XML language for addressing the components (elements, attributes, and so on) of an XML document. XPath is widely used in other core-XML specifications and in programming libraries for accessing XML-encoded data.

Simple transformations, or ST, is a proprietary SAP programming language that describes the transformation of ABAP data to XML (serialization) and from XML to ABAP data (deserialization).

Comparision: !=, <=, <, =, >, >=. Logical operators: not(), or, and.



1 Basic
2 Nested loops
3 Set row position
4 Dynamic columns
5 Rows merging
7 Formulas
8 External method call
9 Templates
10 Two tables associated by the key.
11 “select” with filter for structure
12 Advices
13 Annex 1. Classic Dynpro output
14 Annex 2. WebDynpro output
15 links



1) Lets create table in Excel:


Save it like “Tables XML 2003 (*.xml)“. it is important.

We open this file and copy everything except first lines:

<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>

2) In SAP GUI we create XSLT: right mouse click at package


<xsl:transform version="1.0"

  <xsl:strip-space elements="*"/>

  <xsl:template match="/">


3)  Find this:

<Worksheet ss:Name="Лист1"> 
   <Table ss:ExpandedRowCount="2" >

In Table tag just remove rows limit attribute: ss:ExpandedRowCount=”2″

4) Find required ROW and place it inside loop:

<xsl:for-each select="//TAB1/*">   - TAB1 is a SOURCE parameter for CALL TRANSFORMATION
   here <ROW></ROW> which we want to repeat after header.

We pass DATA as an internal table from ABAP:

DATA: lt_t TYPE TABLE OF ls_t.
          tab1 = lt_t
        RESULT XML

Note: Dictionary objects is not required.

5) In our ROW we insert CELL with DATA tag and out tab1 – abap table:

<Data ss:Type="Number">
  <xsl:value-of select="INDEX"/>     - index is a field in out TAB1 internal table

Note: field names must be in UPPER CASE.

6) See Output of XSLT in Annex 1,2.


Nested loops

For example: we have two itabs:  T1 and nested T2. In nested T2 we must not use “//”.

         <xsl:for-each select="//T1/*">
            <xsl:for-each select="T2/*"> - here without // becouse of T1/T2
                  <Data ss:Type="Number"><xsl:value-of select="INDEX"/></Data>

Set row position

<xsl:for-each select="//DATA/*">
   <xsl:variable name="ops_row" select="2 + position()"/>    - that is how we create varibles
   <Row ss:Index="{$ops_row}">     <!-- {} - attribute value template -->

Dynamic columns

<Table>        <!-- without ss:ExpandedColumnCount -->
  <Column/>    <!-- Column tag set Style for columns, we loopt it first -->
  <xsl:for-each select="//T2/*">
<Row>                      <!-- one row with T2 columns -->
  <xsl:for-each select="//T2/*">
       <!-- dynamic rows and dynamic columns -->
<xsl:for-each select="//T1/*">  <!-- loop at rows -->
    <xsl:for-each select="//T2/*"> <!-- loop at columns -->
      <Cell ss:Index="{1 + position()}">  </Cell>

Rows merging

We will look at the case with two levels merging. We have itab T1(num) <- nested T2(org) <- nested T3(plans).

<xsl:for-each select="//T1/*">
  <xsl:variable name="t1_num" select="NUM"/>  <!-- inner loop T2 hide fields of T1 -->
    <xsl:variable name="t1_count" select="COUNT"/>
  <xsl:for-each select="T2/*">
    <xsl:variable name="t2_count" select="COUNT"/>
    <xsl:variable name="t2_pos" select="position()"/>
    <xsl:variable name="t2_org" select="ORG"/>
  <xsl:for-each select="T3/*">
      <xsl:if test="($t2_pos=1) and (position()=1)"> <!-- top cell with merge and skip below ones-->
        <Cell ss:MergeDown="{$t1_count - 1}"><Data ss:Type="String"><xsl:value-of select="$t1_num"/></Data></Cell>
      <xsl:if test="position()=1"> <!-- for cell after skipped ones we must specify ss:Index-->
        <Cell ss:MergeDown="{$t2_count - 1}" ss:Index="2"><Data ss:Type="String"><xsl:value-of select="$t2_org"/></Data></Cell>
    <Cell ss:Index="3"><xsl:value-of select="PLANS"/></Cell>

In XSLT it is impossible to accumulate or change variable, that is why we need to calculate rows number  in advance with ABAP like this:

      LOOP AT <T1>-T2 ASSIGNING <T2>.
        <T2>-count = lines( <T2>-T3 ).    "for T2 just all nested lines
        IF <T2>-count <> 0.
          <T1>-count = <T1>-count + <T2>-count.   "for T1 we sum all T2 tables, empty = 1 line.
          <T1>-count = <T1>-count + 1.

Note: helpful function count(//T1/*) – return rows count for T1 table.


If field has numeric value 1. If-then here:

<xsl:if test="INDEX=1">


          <xsl:when test="SYMB='+'">
            <Cell ss:StyleID="s26"><Data ss:Type="String"><xsl:value-of select="SYMB"/></Data></Cell>
            <Cell ss:StyleID="s18"><Data ss:Type="String"></Data></Cell>


<Cell ss:StyleID="s133" ss:Formula="=SUM(R[-{$t1_count}]C:R[-1]C)">
   <Data ss:Type="Number"></Data></Cell>

Here we sym sells up at a distance of $t1_count to curret cell. [] is a Relative Notation for Excel cells.

R for Rows, and C for Columns.

RC[-1] refers to the cell one column to the left: R without a number means same row.

External method call

Static method of a global class call:

<sap:call-external class="ZMYCLASS" method="GET_XML">
                  <sap:callvalue param="IV_STR" select="string(position())"/>  <!-- export parameter -->
                  <sap:callvariable name="fromclass" param="RET_VAL"/>         <!-- return $fromclass
<xsl:value-of select="$fromclass"/>

To call method of object instance, it is required to get instance from one of the static class method.

method SIMP.
<sap:call-external class=”ZCAL” method=”SIMP”>    “calling static method
  <sap:callvariable param=”EX_OBJ” name=”plant”/>  ” Hold the objec

<sap:call-external name=”plant” method=”ZPERIOD”> “calling instacne method
  <sap:callvalue param=”IM_MHDHB”  select=”string(Order/MHDHB)”/>
  <sap:callvalue param=”IM_IPRKZ”  select=”string(Order/IPRKZ)”/>
  <sap:callvariable param=”EX_MHDHB” name=”period”/>


Element xsl:apply-templates is an alternative way for doing xsl:for-each. This element useful if the structure of a subnode is not defined, appropriate template selected at runtime. In example we have node-set PLANETS, for wich we want to make loop.

<xsl:template match="PLANETS">

<xsl:template match="PLANET">
      <xsl:value-of select="NAME"/>

Two tables associated by the key

If we have  two tables T1 (key LV_ID), T2 (key LV_ID_F) for output, then we can use followind approach based on filter:

<xsl:for-each select="//T1/*">
  <xsl:variable name="id" select="LV_ID"/>
  <xsl:for-each select="//T2/*[LV_ID_F=$id]">

“select” with filter for structure

Let us say we have table T_LGART with fields PERNR, ID_FK, BETRG. We need to select BETRG from line with PERNR=$pernr and ID_FK=$id.

<xsl:value-of select="//T_LGART/*[PERNR=$pernr and ID_FK=$id]/*[name()='BETRG']"/>


  1. Keep track for changes in XSLT if you forget your last change and meet failure it will be really fail.
  2. Pretty Printer work bad, it is better to structure yourself.
  3. When you have many types of rows in template it will be good to cut them all and past in xslt one by one.
  4. Overall steps: we create two xml template minimal and rich; it will be good to have comment with every column; one row with dynamic columns; one by one rows surround by loops; surround rows by loops one by one; cells fillings; cell merging; formulas; separate cell filings.

Annex 1. Classic Dynpro output

 DATA lt_data_xml      TYPE swxmlcont.
    DATA l_ostream        TYPE REF TO if_ixml_ostream.
    DATA(l_xml) = cl_ixml=>create( ).
    DATA(l_stream_factory) = l_xml->create_stream_factory( ).
    l_ostream = l_stream_factory->create_ostream_itable( table = lt_data_xml[] ).

          data1 = lt_t "internal table lt_t transfered by name data1. Be careful with this names.
        RESULT XML

    DATA workdir  TYPE string.
    cl_gui_frontend_services=>get_sapgui_workdir( CHANGING sapworkdir = workdir ).

    cl_gui_cfw=>flush( ).
    CONCATENATE workdir '\' 'ZXSLT_TRANSFORMATION' sy-datum sy-uzeit '.xml' INTO DATA(filename).
*  "remove special characters
    REPLACE ALL OCCURRENCES OF '*' IN filename WITH space.

    CALL METHOD cl_gui_frontend_services=>gui_download
        filename         = filename
        filetype         = 'BIN'
        data_tab         = lt_data_xml[]
        file_write_error = 1
        OTHERS           = 99.

    CONCATENATE '"' filename '"' INTO filename.

    CALL METHOD cl_gui_frontend_services=>execute
        application = 'Excel'
        parameter   = filename
        operation   = 'OPEN'
        OTHERS = 1.

Annex 2. WebDynpro output

DATA lv_result  TYPE xstring.
        data      = lt_t
      RESULT XML lv_result


  CALL METHOD cl_wd_runtime_services=>attach_file_to_response
      i_filename  = 'a.xml'
      i_content   = lv_result
      i_mime_type = 'application/msexcel'
      i_inplace   = abap_true.

URL sources:


Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Sreehari V Pillai
      Sreehari V Pillai

      Nice blog mate.. Its in my bookmark.



      Author's profile photo Sandra Rossi
      Sandra Rossi

      Hi Vitalij, thanks for the post. Be careful in title and in the whole post, the acronym XLST is incorrect, it's XSLT instead. Note also that the "speed" advantage can be discussed a lot. My rule-of-thumb is that XSLT is 10 times slower than Simple Transformations.



      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      +1 to Sandra, it got me confused for a moment. This needs to be corrected throughout.

      Also in p.2 "In SAP GUI we create XLST: right mouse click at package" is not quite accurate. It's not in "SAP GUI", it's in ABAP Editor (SE80). Not sure if this can be done in Eclipse as well but "ABAP Editor" is neutral enough and should be clear no matter what specific environment is used by the readers.

      There is no "XLST" (or "XSLT") in the menu though, as the instructions seem to suggest. The menu Transformation opens this pop-up, at least in EHP6 ABAP 7.31:

      I feel that attention to detail is very important in such instructional blogs. I hope you can fix the typo and consider these suggestions, going forward.

      Thanks for sharing!

      Author's profile photo Irina Ryzhkova
      Irina Ryzhkova


      Hi, Vitalij, thank you for this post. You specified how to pass internal table data into this XSLT. Please, could you write how to pass a variable into this XSLT?


      Author's profile photo Shiva Krishna
      Shiva Krishna

      Hello Vitalji,

      Could you please let me know how to insert a new line character in XSLT transformation , my requirement is as below

      If the value in the column 'A' is common then i have to group the column 'A' and display multiple lines of column B values in the single cell.

      Picture 1 : AS-IS


      Picture 2 : TO-BE


      The transformation is generated through an internal table . So i have to concatenate the clumn B values with a newline chraacter so that it can display in multiple lines in a single cell.


      Thanks and regards,

      Shiva krishna