Skip to Content
Author's profile photo Andre Fischer

How to split a string into 4 strings of equal length in ABAP?

This question sounds trivial, doesn’t it?

I ran into this when developing the new tasklist for the mass maintenance of OData services (see my blog Custom tasklist for OData service mass maintenance).

Since I am calling API’s that do not return the root cause of an error I wrote some code that retrieved the long text of the original error.

The problem was then that I had to call a method to add a T100 message to a log file that required 4 parameters of type CHAR50.

So I had to cut the string into 4 pieces each having the length of 50 characters.

Let’s have a look what would happen if the incoming string has a length of 102 characters.

First approach: CALL FUNCTION ‘SWA_STRING_SPLIT’

First I tried the function module ‘SWA_STRING_SPLIT’ that takes a string and the desired length of the substring as an Input parameter. The split string is returned via the table lt_string_comp.

CALL FUNCTION 'SWA_STRING_SPLIT'
  EXPORTING
     input_string         = lv_longtext     "string to be split
     max_component_length = 50 
   TABLES
     string_components    = lt_string_comp
                .

You then still have the problem to find out the number of table entries and you cannot access the fourth table entry lt_string_comp[ 4 ] if the table has only three entries if the string has a length of 102 characters.

Second Approach: Use substring

The built in function substring has the disadvantage that it throws an error of type cx_sy_range_out_of_bounds if it would reach the statement

DATA(lv3) = substring( val = lv_longtext off = 100 len = 50 ).

in case the string contained in lv_longtext has a length of 102 characters.

DATA: lv_longtext TYPE char200.
...
CATCH /iwfnd/cx_cof INTO DATA(lx_cof).
lv_longtext = lx_cof->get_longtext( ).
TRY.
    DATA(lv1) = substring( val = lv_longtext off = 0 len = 50 ).
    DATA(lv2) = substring( val = lv_longtext off = 50 len = 50 ).
    DATA(lv3) = substring( val = lv_longtext off = 100 len = 50 ).
    DATA(lv4) = substring( val = lv_longtext off = 150 len = 50 ).
  CATCH cx_sy_range_out_of_bounds INTO DATA(lx_too_long).
    WRITE : / lx_too_long->get_text( ).
ENDTRY.

 

Third Approach: Use old-fashioned ABAP syntax

Though it is not recommended the following code works like a charm. If the string has a length of 102 characters lv7 would contain 2 characters and lv8 would simply be intial.

DATA(lv5) = lv_longtext(50).
DATA(lv6) = lv_longtext+50(50).
DATA(lv7) = lv_longtext+100(50).
DATA(lv8) = lv_longtext+150(50).

Fourth approach: Use cl_message_helper=>set_msg_vars_for_clike

Another option was mentioned by Matthew Billingham as a comment to this blog. By calling the method set_msg_vars_for_clike of class cl_message_helper you get four strings of length 50 characters as well.

This method under the hood uses the old fashioned ABAP syntax

MOVE c200+offset(50) TO sy-msgv2.

as well.

It in addition offers lots of other useful methods as well.

Final approach: Use a structure

The most flexible approach in my opinion (kudos go to my colleagues from the SAP Gateway runtime development team) is using a structure

In the following I move the long text to a structure with 4 fields of length 50 characters each.

As a result error messages longer than 200 characters are truncated but no exception is thrown opposed to using substring.

And it does not make use of out of date ABAP coding style.

Moreover you would be able to define a structure with fields that have different lengths, say 10, 24, 40 and 5 characters. A string moved to this structure would be split approriately without having to look for exceptions whose handling is time consuming and not needed in this case.

TYPES: BEGIN OF ty_longtext,
         msgv1(50),
         msgv2(50),
         msgv3(50),
         msgv4(50),
END OF ty_longtext.
DATA: ls_longtext      TYPE ty_longtext.
...         
CATCH /iwfnd/cx_cof INTO DATA(lx_cof).

ls_longtext = lx_cof->get_longtext( ).

"add a t100 message
if_stctm_task~pr_log->add_t100(
        EXPORTING
            iv_msgty      = 'E'
            iv_msgid      = '/IWFND/COD'
            iv_msgno      = '312'
            iv_msgv1      = CONV #( ls_longtext-msgv1 )
            iv_msgv2      = CONV #( ls_longtext-msgv2 )
            iv_msgv3      = CONV #( ls_longtext-msgv3 )
            iv_msgv4      = CONV #( ls_longtext-msgv4 )
).

Simple test report

If you want to play around I have created the following test report that shows four of the five approaches described above.

*&---------------------------------------------------------------------*
*& Report z_split_string
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z_split_string.

DATA: lv_teststring TYPE string.
DATA: lv_longtext TYPE char200.

TYPES: BEGIN OF ty_longtext,
         msgv1(50),
         msgv2(50),
         msgv3(50),
         msgv4(50),
       END OF ty_longtext.
DATA: ls_longtext TYPE ty_longtext.

lv_teststring = '0123456789'. "10 characters
lv_teststring = lv_teststring && lv_teststring. "20 characters
lv_teststring = lv_teststring && lv_teststring. "40 characters
lv_teststring = lv_teststring && lv_teststring. "80 characters
lv_teststring = lv_teststring && '0123456789' && '012345678901' . "102 characters

DATA(length) = strlen( lv_teststring ).

lv_longtext = lv_teststring.
ls_longtext = lv_teststring.

TRY.
    DATA(lv1) = substring( val = lv_longtext off = 0 len = 50 ).
    DATA(lv2) = substring( val = lv_longtext off = 50 len = 50 ).
    DATA(lv3) = substring( val = lv_longtext off = 100 len = 50 ).
    DATA(lv4) = substring( val = lv_longtext off = 150 len = 50 ).
  CATCH cx_sy_range_out_of_bounds INTO DATA(lx_too_long).
    data(lv_error_lx_too_long) = lx_too_long->get_text( ).
ENDTRY.

DATA(lv5) = lv_longtext(50).
DATA(lv6) = lv_longtext+50(50).
DATA(lv7) = lv_longtext+100(50).
DATA(lv8) = lv_longtext+150(50).

cl_message_helper=>set_msg_vars_for_clike( lv_longtext ).




data(out) = cl_demo_output=>new( ).

out->write( : data = lv_error_lx_too_long ),
              data = '********************************' ),
              data = 'use of substring' ),
              data = lv1 ),
              data = lv2 ),
              data = lv3 ),
              data = lv4 ),
              data = 'old ABAP syntax' ),
              data = lv5 ),
              data = lv6 ),
              data = lv7 ),
              data = lv8 ),
              data = 'string moved to structure' ),
              data = ls_longtext-msgv1 ),
              data = ls_longtext-msgv2 ),
              data = ls_longtext-msgv3 ),
              data = ls_longtext-msgv4 ),
              data = 'use cl_message_helper' ),
              data = sy-msgv1 ),
              data = sy-msgv2 ),
              data = sy-msgv3 ),
              data = sy-msgv4
              )->display( ).

 

This Report generates the following Output:

Substring access (offset = 100, length = 50) to a data object of the size 102 exceeds valid boundaries.

********************************

use of substring

01234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789
Field
 

Field
 


old ABAP syntax

01234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789

01
Field
 


string moved to structure

01234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789

01
Field
 


use cl_message_helper

01234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789

01
Field
 

 

Assigned Tags

      22 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Matthew Billingham
      Matthew Billingham

       

      DATA(my_text) = 'Text to put into upto 4 char50 variables'.
      
      cl_message_helper=>set_msg_vars_for_clike( my_text ).
      WRITE / : sy-msgv1, sy-msgv2, sy-msgv3, sy-msgv4.

       

      Author's profile photo Andre Fischer
      Andre Fischer
      Blog Post Author

      Hi Matthew,

      thanks for pointing me to this option. I therefore changed the description for the approach using a structure from ""the best..." to "the most flexible approach". ;-).

      Best Regards,

      Andre

      Author's profile photo Matthew Billingham
      Matthew Billingham

      It’s more flexible if you’re wishing to do a quick concatenation, and the structure won’t change. This approach was used quite often pre-unicode. The problem that can arise is if a structure were to change so that it incorporated a non-clike field. It just doesn’t work then.

      Looking at your problem as stated (and without knowing the full details obviously), if I’d encountered it, I’d have looked here:

      The problem was then that I had to call a method to add a T100 message to a log file that required 4 parameters of type CHAR50.

      The solution (if possible) would be to add a method so a string can be added to the log file. By doing this you have simpler calling code, and you’ve made your logging class more powerful.

      Another hint -> as Horst Keller explains the ABAP documentation uses the class CL_DEMO_OUTPUT for sample code. You might like to look at that.

      btw, SAP’s own ABAP Guidelines and DSAG’s latest best practice document, speak against using prefixes as type indicators such as l, s etc. Especially as not one of the variables in your example program is, in fact, local…

      Author's profile photo Andre Fischer
      Andre Fischer
      Blog Post Author

      Hi Matthew,

      the class CL_DEMO_OUTPUT is nice but I am not allowed to use it in SAP production code since it has been released for demo purposes only.

      Unfortunately I am not the owner of the logging class, but I will ask the colleagues what they can do.

      Regards,

      Andre

       

       

      Author's profile photo Matthew Billingham
      Matthew Billingham

      I meant instead of using the WRITE statements in your simple test report.  I kind of assumed that wasn't intended for production! 😀

      Author's profile photo Andre Fischer
      Andre Fischer
      Blog Post Author

      done

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      TBH personally I find the use of CL_DEMO_OUTPUT in the examples annoying. If neither is suitable to be copy-pasted for production use 🙂 then might as well just use WRITE. Simpler and shorter.

      Even if I can understand rationale behind this it's still irritating and for some reason looks rather pompous. Can't help it. 🙂

      Author's profile photo Horst Keller
      Horst Keller

      Well, then show me how you translate CL_DEMO_OUTPUT=>DISPLAY( itab ) to a WRITE statement ...

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Touche. 🙂 But in the simplest cases it's still feels like "ugh".

       

      Author's profile photo Horst Keller
      Horst Keller

      But only this "pompous ugh" makes the examples directly executable in the web version of the documentation, notably in ADT!

      Author's profile photo Shai Sinai
      Shai Sinai

      For Unicode support (with non C-Like types) you may use CL_ABAP_CONTAINER_UTILITIES->READ_CONTAINER_C.

      Author's profile photo George Manousaridis
      George Manousaridis

      Hi Matthew,

      Great approach! I tried it and a problem I saw, was that the method splits the text in exactly 50 chars, so it breaks the words in that position.

      Other than that, which prevented me to use it in my case, it works like a charm!

      Cheers,

      George

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Thanks for sharing! I'm wondering if you also thought about a more generic option. E.g. in this case we're splitting into 50 characters and pre-define 4 variables. But what if it's not 50 but 10 or 20 characters? How would you approach that?

      Author's profile photo Andre Fischer
      Andre Fischer
      Blog Post Author

      Hi Jelena,

      I mentioned this in my blog.

      I would just create an appropriate structure (here with 10, 20, 30 and 40 characters).

      TYPES: BEGIN OF ty_longtext,
               msgv1(10),
               msgv2(20),
               msgv3(30),
               msgv4(40),
             END OF ty_longtext.
      DATA: ls_longtext TYPE ty_longtext.

      The output then looks like follows:

      string moved to structure
      
      0123456789
      
      01234567890123456789
      
      012345678901234567890123456789
      
      0123456789012345678901234567890123456789
      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Right but this is just another sort of hard-coded variation. What about, say, using a number N as a parameter and a more generic code to split into however many pieces needed (each of N length) within some reasonable limit (to make it easier)? Don't have a specific use case in mind, just idle curiosity. 🙂

       

      Author's profile photo Matthew Billingham
      Matthew Billingham

      Probably a variant of cl_message_helper=>set_msg_vars_for_clike( my_text ) would do the trick.

      Author's profile photo Phillip Eisenhauer
      Phillip Eisenhauer

      In my opinion a quick "while construct" cutting the pieces out of the string would do the trick. Reusing some code I already wrote for a BAPI_MATERIAL_SAVEDATA call (extensionin..) I think of the following:

      DATA: collected_strings TYPE standard table of string with default key.
      DATA(string) = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtVvWwXxYyZzÄäÖöÜü1!2"3§4$5%6&7/8(9)0=ß?´`+#-.,<>|^°'.
      DATA(single_unit_len) = 10.
      break-point. " to modify string and single_unit_len
      
      * get length of string that will be processed
      DATA(remaining_strlen) = strlen( string ).
      
      * while string is not processed fully, loop across this algorithm.
      WHILE remaining_strlen <> 0.
      * the length to be processed at once can have 2 different values. The full single_unit_len length or a smaller remaining part.
        DATA(unit_len) = COND #(
                            WHEN remaining_strlen > single_unit_len THEN single_unit_len
                            ELSE remaining_strlen
                         ).
      * copy a single unit out of the string.
        DATA(single_unit) = substring(
                                val = string
                                len = unit_len
                            ).
      * "cut" the copied out unit out of the string
        string = substring( val = string off = unit_len len = remaining_strlen - unit_len  ).
      * get the new remaining length.
        remaining_strlen = strlen( string ).
      * append the single unit to a string table.
        collected_strings = VALUE #( BASE collected_strings ( single_unit ) ).
      ENDWHILE.
      
      cl_demo_output=>display( collected_strings ).
      Author's profile photo Andre Adam
      Andre Adam

      You can also use function module
      SWA_STRING_SPLIT and after that:

      lv_value4 = value #( lt_string_comp[ 4 ] OPTIONAL )

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Not that it ever stopped anyone but FYI this function is not released.

       

      Author's profile photo Weikang Peng
      Weikang Peng

      nice blog Sir, can I translate and  post a new blog in China CSDN site with a reference to yours?

      Author's profile photo Andre Fischer
      Andre Fischer
      Blog Post Author

      Sure.

      I would be happy that you help me to spread the news.

      Best regards,

      Andre

       

      Author's profile photo George Manousaridis
      George Manousaridis

      Hello Andre,

      Thanks for spreading the wisdom.

      Any thoughts about the following approach?

      I know it contains a loop, but still i tried to make it as dynamic as possible

          DATA: lv_msgv1 TYPE symsgv,
                lv_msgv2 TYPE symsgv,
                lv_msgv3 TYPE symsgv,
                lv_msgv4 TYPE symsgv.
      
          FIELD-SYMBOLS <lv_msgv> TYPE symsgv.
      
          SPLIT iv_msgtxt AT space INTO TABLE DATA(lt_msg_words).
      
          DATA(lv_counter) = 2.
          ASSIGN lv_msgv1 TO <lv_msgv>.
          LOOP AT lt_msg_words ASSIGNING FIELD-SYMBOL(<lv_word>).
            IF strlen( <lv_msgv> && <lv_word> ) > 50.
              DATA(lv_msg_var) = |lv_msgv{ lv_counter }|.
              ASSIGN (lv_msg_var) TO <lv_msgv>.
              ADD 1 TO lv_counter.
            ENDIF.
      
            <lv_msgv> = COND #( WHEN <lv_msgv> IS INITIAL THEN <lv_word> ELSE |{ <lv_msgv> } { <lv_word> }| ).
      
            IF lv_counter > 4.
              EXIT.
            ENDIF.
          ENDLOOP.

       

      Thanks for your time.

       

      Cheers,

      George