ABAP in Practice - String Processing
In this practical example we look at the string processing to determine the CDS names in CamelCase and how you can implement this with ABAP.
Table of contents
In the following sections, we will discuss the simple and efficient processing of strings. We will use Modern ABAP to get the names of the Core Data Services.
Introduction
In some situations, we lack the right interfaces to get direct information. For example, we had not found a class or function module to get the CamelCase names of the Core Data Services. These are stored in tables in the classic way and the information is converted to uppercase. Unfortunately, this means that they are not so easy for us to use.
In this blog, we break down the source code of the Core Data Services into processable components and determine the name with upper and lower case as defined in the source code.
Preparation
Before you can start with the actual task, we need a basis that you can develop further. To do this, we create an executable class in the system. We are working outside of ABAP Cloud because we want to access a table that is not released. To do this, we create three types that we need for processing: a range for selection, a structure for mapping and a table type for returning the data.
TYPES tt_r_name TYPE RANGE OF ddddlsrc-ddlname.
TYPES: BEGIN OF ts_mapping,
ddlname TYPE ddddlsrc-ddlname,
cds_name TYPE string,
END OF ts_mapping.
TYPES tt_mapping TYPE SORTED TABLE OF ts_mapping WITH UNIQUE KEY ddlname.
The next step is to create a method that will perform the determination. We want to make it reusable and pass the CDS views for which we want to determine names so that we don't have to read all the objects.
METHODS extract_cds_name
IMPORTING it_r_name TYPE tt_r_name
RETURNING VALUE(rt_result) TYPE tt_mapping.
We now fill the method with some data in order to quickly obtain a result from the logic. When filling it, we choose an inline declaration and fill the range table. Since SIGN and OPTION only have to be set once, we leave them outside the data records and save ourselves the further enumeration.
DATA(lt_r_names) = VALUE tt_r_name( sign = 'I'
option = 'EQ'
( low = 'I_COMPANYCODE' )
( low = '/1BS/SADL_CDS_EXP' )
( low = 'A_CHANGEMASTEROBJECTTYPETEXT' )
( low = 'C_BUDGETPERIODCHILDGROUP' )
( low = 'I_ABOPCHECKINGRULE' )
( low = 'I_JOBSTATUS' )
( low = 'SADL_CDS_RS_SO_ROOT_W_DB_HINT' )
( low = 'SADL_GW_V_AUNIT_V2_VH_WRONG_AN' )
( low = 'SEPM_SDDL_EXTENSIONS' ) ).
Finally, we read the selected Core Data Services and the stored source code from the DDDDLSRC table.
SELECT FROM ddddlsrc
FIELDS ddlname, source
WHERE ddlname IN @it_r_name
INTO TABLE @DATA(lt_views).
This allows you to use the following class for the implementation. If you want to go even further, you can implement it using Test Driven Development and create the test class before implementation.
CLASS zcl_bs_demo_cds_names DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
TYPES tt_r_name TYPE RANGE OF ddddlsrc-ddlname.
TYPES: BEGIN OF ts_mapping,
ddlname TYPE ddddlsrc-ddlname,
cds_name TYPE string,
END OF ts_mapping.
TYPES tt_mapping TYPE SORTED TABLE OF ts_mapping WITH UNIQUE KEY ddlname.
METHODS extract_cds_name
IMPORTING it_r_name TYPE tt_r_name
RETURNING VALUE(rt_result) TYPE tt_mapping.
ENDCLASS.
CLASS zcl_bs_demo_cds_names IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_r_names) = VALUE tt_r_name( sign = 'I'
option = 'EQ'
( low = 'I_COMPANYCODE' )
( low = '/1BS/SADL_CDS_EXP' )
( low = 'A_CHANGEMASTEROBJECTTYPETEXT' )
( low = 'C_BUDGETPERIODCHILDGROUP' )
( low = 'I_ABOPCHECKINGRULE' )
( low = 'I_JOBSTATUS' )
( low = 'SADL_CDS_RS_SO_ROOT_W_DB_HINT' )
( low = 'SADL_GW_V_AUNIT_V2_VH_WRONG_AN' )
( low = 'SEPM_SDDL_EXTENSIONS' ) ).
out->write( extract_cds_name( lt_r_names ) ).
ENDMETHOD.
METHOD extract_cds_name.
SELECT FROM ddddlsrc
FIELDS ddlname, source
WHERE ddlname IN @it_r_name
INTO TABLE @DATA(lt_views).
" Implement here
ENDMETHOD.
ENDCLASS.
Task
The task now is to fill the return table and derive the real CamelCase names from the code. In the internal table LT_VIEWS you will find the name of the view and the coding:
Hint: In the next section we will go into the solution, if you want to do the task on your own, you should pause here.
Solution
In this section we will look at the different steps for setting up the table.
Loop
In the first step we want to process the different views, for this we create a loop and work with a reference. You can find more information about how to work with it in an older article from us. In the second step, we add a new row to our results table and fill in the view name. We assign the generated row to a new reference in order to add the name later.
LOOP AT lt_views REFERENCE INTO DATA(lr_view).
INSERT VALUE #( ddlname = lr_view->ddlname ) INTO TABLE rt_result REFERENCE INTO DATA(lr_result).
ENDLOOP.
We take the name of the view directly, since it is a sorted table and we have to fill in the key, otherwise we will get an error. Since we have no exclusion criteria that exclude individual rows, but we include everything in the result, this step also makes sense.
Division
Next, we want to split the individual statements of the source. To do this, we use a classic SPLIT command and get a string table with the individual elements from the source code.
SPLIT lr_view->source AT ` ` INTO TABLE DATA(lt_split).
Search
We can now start processing the individual statements. The table would currently look like this after the split:
Now we just need to search the table with elements and filter for the CDS view we are looking for. Once we have found the view, we accept it and exit the routine. To make the values comparable, we convert the CamelCase name using TO_UPPER and compare the result. Since we are working with a reference here, we need to use "->*" for access.
LOOP AT lt_split REFERENCE INTO DATA(lr_split).
IF to_upper( lr_split->* ) = lr_view->ddlname.
lr_result->cds_name = lr_split->*.
EXIT.
ENDIF.
ENDLOOP.
Finally, we leave the logic with EXIT and go to the next Core Data Service.
Performance
To improve performance a little, we don't always have to start with the first line, especially if there are a lot of annotations at the beginning. Basically, we can search for the keyword DEFINE, as this introduces and defines the view. To do this, we search for "define" in the table, but since this has no fields, we use access via TABLE_LINE. We use the LINE_INDEX function to return the current line. We then add the FROM addition to the loop to start from this position.
DATA(ld_start) = line_index( lt_split[ table_line = `define` ] ).
LOOP AT lt_split REFERENCE INTO DATA(lr_split) FROM ld_start.
ENDLOOP.
If LINE_INDEX does not find a statement, the position is set to 0 and can then continue to be used for the LOOP to start with the first position.
Line breaks
In some cases it can happen that "define" cannot be found. If we take a closer look at these cases, we will see that the statement was not separated cleanly.
The backslash & "n" is a line break, which is also stored in the table. In such a case, we should replace all line breaks in the source code before the SPLIT so that we get a clean result. To do this, we use the Replace function and replace all occurrences of the line break with a space.
lr_view->source = replace( val = lr_view->source
sub = cl_abap_char_utilities=>cr_lf
with = ` `
occ = 0 ).
If we now look at the table after the SPLIT, the statements are cleanly separated so that we can then search for the beginning.
Names
In our examples there is also a case that returns an empty Core Data Service. If we look at the mapping in detail here, we will see that the name in the source code is not the same as the name of the object.
In the simplest case, we then take the name of the Core Data Service we are looking for as the result. There is no CamelCase name for the object here.
IF lr_result->cds_name IS INITIAL.
lr_result->cds_name = lr_view->ddlname.
ENDIF.
Complete example
In this section you will find the complete class with the complete implementation so that you can recreate the example. In principle, you can also remove the restriction of the individual views in order to get all views as a result. However, this will significantly increase the runtime. You can find the resource in our GitHub repository for extracting the CDS information.
Conclusion
In this practical example, we wanted to give you a better idea of how to work with strings and how you can use the different techniques to get the result. If you have an interesting solution to the problem, please post it in the comments.