ABAP - XCO Strings
In this article we look at the XCO class for generating and processing strings for ABAP Cloud and compare it with the classic statements.
Table of contents
The XCO classes are helper classes that provide various everyday functions bundled under a public API. You can find more information and an overview of the XCO libraries on the overview page.
Introduction
In this article we look at the collection of string functions and how you can use them. There are various string objects that you can get from the base XCO_CP. In the following sections we will look at the different objects and the differences to Classic, Modern and XCO.
STRING
Using XCO_CP=>STRING we get a string object with which we can process a single string. In the IF_XCO_STRING interface you will find the corresponding methods for use and in the CL_XCO_STRING class the implementation.
Lower/Upper
For converting a string to the lower and upper case, there are similar methods in all three versions. Here is an example of the upper case conversion.
" Classic
TRANSLATE ld_string TO UPPER CASE.
" Modern
ld_string = to_upper( c_string ).
" XCO
ld_string = xco_cp=>string( c_string )->to_upper_case( )->value.
Substring
Determining a substring from a text is usually called a substring. There are various commands and methods available in ABAP for this purpose.
" Classic
ld_string = c_string+11(3).
" Modern
ld_string = substring( val = c_string off = 11 len = 3 ).
" XCO
ld_string = xco_cp=>string( c_string )->from( 12 )->to( 3 )->value.
As you have probably noticed, the XCO class works a little differently here. With FROM we extract the substring from our string from the 12th character to the end. In the next step we extract the new substring up to the third character.
Split
Would you like to split a string at a separator and get a flexible number of new strings? The SPLIT command is used for this in ABAP. There is no modern version of this, only the classic version.
" Classic
SPLIT c_string AT c_seperator INTO TABLE DATA(lt_parts).
" Modern
" XCO
lt_parts = xco_cp=>string( c_string )->split( c_seperator )->value.
Append
There are various ways to append something to the beginning (PREPEND) or the end (APPEND) of a string in ABAP.
" Classic
CONCATENATE c_string c_part INTO ld_string.
" Modern
ld_string = c_string && c_part.
" XCO
ld_string = xco_cp=>string( c_string )->append( c_part )->value.
Validate part
If you want to check the start (STARTS_WITH) or the end (ENDS_WITH) of a string against a character string, in most cases you have to implement a bit more code. It is worth taking a look at the methods of the XCO class.
" Classic
DATA(ld_start) = strlen( c_string ) - strlen( c_point ) - 1.
DATA(ld_length) = strlen( c_point ).
IF c_string+ld_start(ld_length) = c_point.
ENDIF.
" Modern
IF substring( val = c_string
off = strlen( c_string ) - strlen( c_point ) - 1
len = strlen( c_point ) ) = c_point.
ENDIF.
" XCO
IF xco_cp=>string( c_string )->ends_with( c_point ).
ENDIF.
Regular expression
There are two methods for using regular expressions. With the MATCHES method, you can compare your string against a regular expression. With the GREP method, you can return all parts that match the search pattern.
" Classic
FIND ALL OCCURRENCES OF PCRE c_regex IN c_string RESULTS DATA(lt_found).
LOOP AT lt_found INTO DATA(ls_found).
INSERT c_string+ls_found-offset(ls_found-length) INTO TABLE lt_parts.
ENDLOOP.
CLEAR lt_parts.
" Modern
ld_start = 0.
DO.
DATA(ld_position) = find( val = c_string
pcre = c_regex
off = ld_start ).
IF ld_position = -1.
EXIT.
ENDIF.
DATA(ld_to) = find_end( val = c_string
pcre = c_regex
off = ld_start ).
INSERT substring( val = c_string
off = ld_position
len = ld_to - ld_position ) INTO TABLE lt_parts.
ld_start = ld_to.
ENDDO.
" XCO
lt_parts = xco_cp=>string( c_string )->grep( iv_regular_expression = c_regex
io_engine = xco_cp_regular_expression=>engine->pcre( )
)->value.
Hint: Currently, GREP does not seem to work with the same regular expression, we are currently working on this.
Decompose
For the special expression of the DECOMPOSE method, we only want to list the use, since resolving the string based on upper and lower case requires a lot of logic. You can also give the method various procedures (PascalCase and camelCase). After decomposition, we get a table with four entries.
lt_parts = xco_cp=>string( `MyNameIsPascal` )->decompose( xco_cp_string=>decomposition->pascal_case )->value.
You can find the counterpart in the STRINGS class with the COMPOSE method, in order to then link the individual components again logically.
Convert
There are currently various function modules, classes and other options for converting from string to XString. The AS_XSTRING method gives you the option of converting the string with the correct code page.
" Classic
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
text = c_string
IMPORTING
buffer = ld_xstring
EXCEPTIONS
failed = 1
OTHERS = 2.
IF sy-subrc <> 0.
ENDIF.
" Modern
ld_xstring = cl_abap_codepage=>convert_to( source = c_string ).
" XCO
ld_xstring = xco_cp=>string( c_string )->as_xstring( xco_cp_character=>code_page->utf_8 )->value.
Message
If you want to output a string as a message, you must create a corresponding mapping to fill the message fields. With the AS_MESSAGE method, you can create a message class from the text and use it further. This way you can have the message returned as a SYMSG structure. To do this, you only need to access the VALUE of the object.
DATA(ls_message) = xco_cp=>string( c_long_string )->as_message( xco_cp_message=>type->error )->value.
If we then look at the structure of LS_MESSAGE, we get the correct type for the message, a message class with placeholders and the mapping of the string.
As a second example, we can also create an exception class and have the message overwritten in the class.
DATA(lo_message) = NEW cx_abap_context_info_error( ).
xco_cp=>string( c_long_string )->as_message( )->write_to_t100_dyn_msg( lo_message ).
We can now view and validate the result in the "ABAP Exception (Debugger)" view.
STRINGS
Using XCO_CP=>STRINGS we get a String object with which we can edit a table of strings. If we use the SPLIT method in the String class, for example, we get an object like this. In the interface IF_XCO_STRINGS you will find the corresponding methods for use and in the class CL_XCO_STRINGS the implementation.
GET
Would you like to read a row from the string table? You can do this with the GET method to get a STRING object and continue working with it or read the contents.
" Classic
READ TABLE lt_strings INDEX 5 INTO ld_string.
" Modern
ld_string = lt_strings[ 5 ].
" XCO
ld_string = xco_cp=>strings( lt_strings )->get( 5 )->value.
Hint: In the last two cases, you have to take care of handling the exception CX_SY_ITAB_LINE_NOT_FOUND. If the line does not exist, the exception is triggered.
Range
With the FROM and TO methods, you can read a range from a table and extract parts. In the example, we want to extract a range from the middle.
" Classic
LOOP AT lt_strings INTO ld_string.
IF sy-tabix >= 2 AND sy-tabix <= 3.
APPEND ld_string TO lt_parts.
ENDIF.
ENDLOOP.
" Modern
LOOP AT lt_strings INTO ld_string FROM 2 TO 3.
INSERT ld_string INTO TABLE lt_parts.
ENDLOOP.
" XCO
lt_parts = xco_cp=>strings( lt_strings )->from( 2 )->to( 2 )->value.
The FROM method starts from TABIX 2 and reads the records to the end. The TO method then takes the result and reads from 1 to 2, we then receive this result in parts. In the example we have omitted the error handling because the XCO class also checks the ranges to see if there are enough rows.
Merge
The COMPOSE method merges a table of strings according to certain rules and is the counterpart to the DECOMPOSE method of the String class. Since the logic is a bit simpler, here is an example.
" Classic
LOOP AT lt_strings INTO ld_string.
DATA(ld_field) = ld_string+0(1).
TRANSLATE ld_field TO UPPER CASE.
CONCATENATE ld_result ld_field ld_string+1 INTO ld_result.
ENDLOOP.
" Modern
LOOP AT lt_strings INTO ld_string.
ld_result &&= to_upper( substring( val = ld_string
len = 1 ) ) && substring( val = ld_string
off = 1 ).
ENDLOOP.
" XCO
ld_result = xco_cp=>strings( lt_strings )->compose( xco_cp_string=>composition->pascal_case )->value.
Sort
Before we re-sort our table, we need the new order. To do this, we create an index table with the new rows and the order.
DATA(lt_index) = VALUE if_xco_strings=>tt_indices( ( 3 ) ( 4 ) ( 5 ) ( 2 ) ( 1 ) ).
Then we can perform the actual logic to create our new output table. To do this we use the REORDER method of the class.
" Classic
LOOP AT lt_index INTO ld_index.
READ TABLE lt_strings INTO ld_string INDEX ld_index.
APPEND ld_string TO lt_parts.
ENDLOOP.
CLEAR lt_parts.
" Modern
LOOP AT lt_index INTO ld_index.
INSERT lt_strings[ ld_index ] INTO TABLE lt_parts.
ENDLOOP.
" XCO
lt_parts = xco_cp=>strings( lt_strings )->reorder( lt_index )->value.
Hint: The logic is still missing error handling, otherwise the code would be a bit longer. The logic of the XCO class also checks that no rows are lost.
Reverse
The REVERSE method turns the entire table over once so that the last sentence becomes the first sentence. The following examples, which are also not very complex, show this.
" Classic
LOOP AT lt_strings INTO ld_string.
INSERT ld_string INTO lt_parts INDEX 1.
ENDLOOP.
" Modern
LOOP AT lt_strings INTO ld_string STEP -1.
INSERT ld_string INTO TABLE lt_parts.
ENDLOOP.
" XCO
lt_parts = xco_cp=>strings( lt_strings )->reverse( )->value.
XSTRING
We get an object for handling XStrings in the system via XCO_CP=>XSTRING. In the interface IF_XCO_XSTRING you will find the corresponding methods for use and in the class CL_XCO_XSTRING the implementation.
Conversion
Currently there is only one method for converting XString to String with a corresponding conversion. An example could therefore look like this.
" Classic
CALL FUNCTION 'ECATT_CONV_XSTRING_TO_STRING'
EXPORTING
im_xstring = c_xstring
IMPORTING
ex_string = ld_string.
" Modern
ld_string = cl_abap_codepage=>convert_from( source = c_xstring ).
" XCO
ld_string = xco_cp=>xstring( c_xstring )->as_string( xco_cp_character=>code_page->utf_8 )->value.
Performance
What is the performance of standard statements when we use the XCO classes? To do this, we will do a simple test with the SPLIT in ABAP. In our test, we create a table of random strings and process them line by line by breaking the texts down again. To do this, we first create a table of random strings.
DATA(lt_parts) = VALUE string_table( ( `Apple` )
( `Banana` )
( `Mango` )
( `Kiwi` )
( `Ananas` )
( `Orange` )
( `Citron` )
( `Lime` )
( `Cherry` )
( `Apricot` )
( `Plum` )
( `Pear` )
( `Blueberry` )
( `Grapes` )
( `Salmonberry` )
( `Raspberry` )
( `Honeydew` ) ).
DATA(lo_rand_strings) = NEW zcl_bs_demo_random( id_min = 1
id_max = lines( lt_parts ) ).
DATA(lo_rand_parts) = NEW zcl_bs_demo_random( id_min = 5
id_max = 10 ).
DO id_lines TIMES.
INSERT INITIAL LINE INTO TABLE rt_result REFERENCE INTO DATA(lr_line).
DO lo_rand_parts->rand( ) TIMES.
lr_line->* &&= | { lt_parts[ lo_rand_strings->rand( ) ] }|.
ENDDO.
ENDDO.
In the next step, we execute the logic with the classic split and break the string down into its components.
LOOP AT lt_strings REFERENCE INTO lr_string.
SPLIT lr_string->* AT c_seperator INTO TABLE lt_split.
ENDLOOP.
In the second step we use the SPLIT method of the XCO class to get the different components.
LOOP AT lt_strings REFERENCE INTO lr_string.
lt_split = xco_cp=>string( lr_string->* )->split( c_seperator )->value.
ENDLOOP.
In the first step, we take 5000 random rows and break down the table row by row. If we look at the runtime for the two variants, the difference becomes obvious. Managing the memory for the classes and instances is about 5 to 6 times more expensive than simply using the statement.
If performance is not so important to you at this point, you can also work with the XCO class, especially if further string operations are planned with the data.
Complete example
Here you can find the examples shown in an executable class for testing and debugging. For the performance test we use two classes that are not listed in the example. For more information, see the GitHub repository.
CLASS zcl_bs_demo_xco_string DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
CONSTANTS c_string TYPE string VALUE `This is my new string class.`.
CONSTANTS c_long_string TYPE string VALUE `If you want to have a longer string for validation and to test, you have to create it for youself.`.
CONSTANTS c_xstring TYPE xstring VALUE `54686973206973206D79206E657720737472696E6720636C6173732E`.
CONSTANTS c_part TYPE string VALUE `ABAP`.
CONSTANTS c_point TYPE string VALUE `.`.
CONSTANTS c_seperator TYPE string VALUE ` `.
CONSTANTS c_regex TYPE string VALUE `w*isw*`.
METHODS split_performance
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS get_random_strings
IMPORTING id_lines TYPE i
RETURNING VALUE(rt_result) TYPE string_table.
METHODS operations_with_string
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS operations_with_strings
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS operations_with_xstring
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_xco_string IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
operations_with_string( out ).
operations_with_strings( out ).
operations_with_xstring( out ).
split_performance( out ).
ENDMETHOD.
METHOD operations_with_string.
DATA ld_xstring TYPE xstring.
DATA(ld_string) = c_string.
" Classic
TRANSLATE ld_string TO UPPER CASE.
" Modern
ld_string = to_upper( c_string ).
" XCO
ld_string = xco_cp=>string( c_string )->to_upper_case( )->value.
" Classic
ld_string = c_string.
TRANSLATE ld_string TO LOWER CASE.
" Modern
ld_string = to_lower( c_string ).
" XCO
ld_string = xco_cp=>string( c_string )->to_lower_case( )->value.
" Classic
ld_string = c_string+11(3).
" Modern
ld_string = substring( val = c_string
off = 11
len = 3 ).
" XCO
ld_string = xco_cp=>string( c_string )->from( 11 + 1 )->to( 3 )->value.
" Classic
" TODO: variable is assigned but never used (ABAP cleaner)
SPLIT c_string AT c_seperator INTO TABLE DATA(lt_parts).
" Modern
" XCO
lt_parts = xco_cp=>string( c_string )->split( c_seperator )->value.
" Classic
CONCATENATE c_string c_part INTO ld_string.
" Modern
ld_string = c_string && c_part.
" XCO
ld_string = xco_cp=>string( c_string )->append( c_part )->value.
" Classic
DATA(ld_start) = strlen( c_string ) - strlen( c_point ) - 1.
DATA(ld_length) = strlen( c_point ).
IF c_string+ld_start(ld_length) = c_point.
ENDIF.
" Modern
IF substring( val = c_string
off = strlen( c_string ) - strlen( c_point ) - 1
len = strlen( c_point ) ) = c_point.
ENDIF.
" XCO
IF xco_cp=>string( c_string )->ends_with( c_point ).
ENDIF.
CLEAR lt_parts.
" Classic
FIND ALL OCCURRENCES OF PCRE c_regex IN c_string RESULTS DATA(lt_found).
LOOP AT lt_found INTO DATA(ls_found).
INSERT c_string+ls_found-offset(ls_found-length) INTO TABLE lt_parts.
ENDLOOP.
CLEAR lt_parts.
" Modern
ld_start = 0.
DO.
DATA(ld_position) = find( val = c_string
pcre = c_regex
off = ld_start ).
IF ld_position = -1.
EXIT.
ENDIF.
DATA(ld_to) = find_end( val = c_string
pcre = c_regex
off = ld_start ).
INSERT substring( val = c_string
off = ld_position
len = ld_to - ld_position ) INTO TABLE lt_parts.
ld_start = ld_to.
ENDDO.
" XCO
lt_parts = xco_cp=>string( c_string )->grep( iv_regular_expression = c_regex
io_engine = xco_cp_regular_expression=>engine->pcre( )
)->value.
lt_parts = xco_cp=>string( `MyNameIsPascal` )->decompose( xco_cp_string=>decomposition->pascal_case )->value.
" Classic
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
text = c_string
IMPORTING
buffer = ld_xstring
EXCEPTIONS
failed = 1
OTHERS = 2.
IF sy-subrc <> 0.
ENDIF.
" Modern
ld_xstring = cl_abap_codepage=>convert_to( source = c_string ).
" XCO
ld_xstring = xco_cp=>string( c_string )->as_xstring( xco_cp_character=>code_page->utf_8 )->value.
" XCO
DATA(ls_message) = xco_cp=>string( c_long_string )->as_message( xco_cp_message=>type->error )->value.
" XCO
DATA(lo_message) = NEW cx_abap_context_info_error( ).
xco_cp=>string( c_long_string )->as_message( )->write_to_t100_dyn_msg( lo_message ).
ENDMETHOD.
METHOD operations_with_strings.
DATA ld_string TYPE string.
DATA lt_parts TYPE string_table.
DATA ld_result TYPE string.
DATA ld_index TYPE if_xco_strings=>tv_index.
DATA(lt_strings) = VALUE string_table( ( `This` )
( `is` )
( `the` )
( `strings` )
( `class` ) ).
" Classic
READ TABLE lt_strings INDEX 5 INTO ld_string.
" Modern
ld_string = lt_strings[ 5 ].
" XCO
ld_string = xco_cp=>strings( lt_strings )->get( 5 )->value.
" Classic
LOOP AT lt_strings INTO ld_string.
IF sy-tabix >= 2 AND sy-tabix <= 3.
APPEND ld_string TO lt_parts.
ENDIF.
ENDLOOP.
CLEAR lt_parts.
" Modern
LOOP AT lt_strings INTO ld_string FROM 2 TO 3.
INSERT ld_string INTO TABLE lt_parts.
ENDLOOP.
" XCO
lt_parts = xco_cp=>strings( lt_strings )->from( 2 )->to( 2 )->value.
CLEAR ld_result.
" Classic
LOOP AT lt_strings INTO ld_string.
DATA(ld_field) = ld_string+0(1).
TRANSLATE ld_field TO UPPER CASE.
CONCATENATE ld_result ld_field ld_string+1 INTO ld_result.
ENDLOOP.
CLEAR ld_result.
" Modern
LOOP AT lt_strings INTO ld_string.
ld_result &&= to_upper( substring( val = ld_string
len = 1 ) ) && substring( val = ld_string
off = 1 ).
ENDLOOP.
" XCO
ld_result = xco_cp=>strings( lt_strings )->compose( xco_cp_string=>composition->pascal_case )->value.
DATA(lt_index) = VALUE if_xco_strings=>tt_indices( ( 3 ) ( 4 ) ( 5 ) ( 2 ) ( 1 ) ).
CLEAR lt_parts.
" Classic
LOOP AT lt_index INTO ld_index.
READ TABLE lt_strings INTO ld_string INDEX ld_index.
APPEND ld_string TO lt_parts.
ENDLOOP.
CLEAR lt_parts.
" Modern
LOOP AT lt_index INTO ld_index.
INSERT lt_strings[ ld_index ] INTO TABLE lt_parts.
ENDLOOP.
" XCO
lt_parts = xco_cp=>strings( lt_strings )->reorder( lt_index )->value.
CLEAR lt_parts.
" Classic
LOOP AT lt_strings INTO ld_string.
INSERT ld_string INTO lt_parts INDEX 1.
ENDLOOP.
CLEAR lt_parts.
" Modern
LOOP AT lt_strings INTO ld_string STEP -1.
INSERT ld_string INTO TABLE lt_parts.
ENDLOOP.
" XCO
lt_parts = xco_cp=>strings( lt_strings )->reverse( )->value.
ENDMETHOD.
METHOD operations_with_xstring.
DATA ld_string TYPE string.
" Classic
CALL FUNCTION 'ECATT_CONV_XSTRING_TO_STRING'
EXPORTING
im_xstring = c_xstring
IMPORTING
ex_string = ld_string.
" Modern
ld_string = cl_abap_codepage=>convert_from( source = c_xstring ).
" XCO
ld_string = xco_cp=>xstring( c_xstring )->as_string( xco_cp_character=>code_page->utf_8 )->value.
ENDMETHOD.
METHOD split_performance.
DATA lo_run TYPE REF TO zcl_bs_demo_runtime.
DATA lr_string TYPE REF TO string.
DATA lt_split TYPE string_table.
DATA(lt_strings) = get_random_strings( 5000 ).
lo_run = NEW zcl_bs_demo_runtime( ).
LOOP AT lt_strings REFERENCE INTO lr_string.
SPLIT lr_string->* AT c_seperator INTO TABLE lt_split.
ENDLOOP.
io_out->write( |Native SPLIT : { lo_run->get_diff( ) }| ).
lo_run = NEW zcl_bs_demo_runtime( ).
LOOP AT lt_strings REFERENCE INTO lr_string.
lt_split = xco_cp=>string( lr_string->* )->split( c_seperator )->value.
ENDLOOP.
io_out->write( |XCO String Split : { lo_run->get_diff( ) }| ).
ENDMETHOD.
METHOD get_random_strings.
DATA(lt_parts) = VALUE string_table( ( `Apple` )
( `Banana` )
( `Mango` )
( `Kiwi` )
( `Ananas` )
( `Orange` )
( `Citron` )
( `Lime` )
( `Cherry` )
( `Apricot` )
( `Plum` )
( `Pear` )
( `Blueberry` )
( `Grapes` )
( `Salmonberry` )
( `Raspberry` )
( `Honeydew` ) ).
DATA(lo_rand_strings) = NEW zcl_bs_demo_random( id_min = 1
id_max = lines( lt_parts ) ).
DATA(lo_rand_parts) = NEW zcl_bs_demo_random( id_min = 5
id_max = 10 ).
DO id_lines TIMES.
INSERT INITIAL LINE INTO TABLE rt_result REFERENCE INTO DATA(lr_line).
DO lo_rand_parts->rand( ) TIMES.
lr_line->* &&= | { lt_parts[ lo_rand_strings->rand( ) ] }|.
ENDDO.
ENDDO.
ENDMETHOD.
ENDCLASS.
Conclusion
The XCO classes can save code in some places, especially when it comes to more complex actions. Developing and testing logic anew every time makes no sense. Therefore, a clear recommendation to use the class if it is not a matter of standard operations.
Source:
SAP Help - XCO String