Check structure against another structure by value - data-structures

I have this piece of dynamic coding. Where I need to check if a structure of keys is in another structure. Without checking the value of each field (if possible).
Logically it should be something like this:
IF ls_keys IN ls_data_struc.
"do stuff
ENDIF.
yes I know that coding snippet doesn't work, but I think that shows best what I am asking.
TYPES: BEGIN OF tys_keys,
matnr TYPE matnr,
vkorg TYPE vkorg,
END OF tys_keys.
TYPES: BEGIN OF tys_data,
matnr TYPE matnr,
vkorg TYPE vkorg,
mtpos TYPE mtpos,
END OF tys_data.
DATA: ls_keys TYPE tys_keys,
ls_data_struc TYPE tys_data.
Example that should work:
ls_keys-matnr = '009988776655443322'.
ls_keys-vkorg = '0001'.
ls_data_struc-matnr = '009988776655443322'.
ls_data_struc-vkorg = '0001'.
ls_data_struc-mtpos = 'ALEN'.
IF ls_keys IN ls_data_struc.
"do something
ENDIF.
Example that shouldn't work:
ls_keys-matnr = '112233445566778899'.
ls_keys-vkorg = '3145'.
ls_data_struc-matnr = '009988776655443322'.
ls_data_struc-vkorg = '0001'.
ls_data_struc-mtpos = 'ALEN'.
IF ls_keys IN ls_data_struc.
"do something
ENDIF.

To only compare the component values of both structures, the simplest solution is to use the constructor operator CORRESPONDING:
IF ls_keys = CORRESPONDING tys_keys( ls_data_struc ).
"do something
ENDIF.

If the fields are actually laid out similar to the snippet in your question,
If ls_keys = ls_data_struc(22).
“Do something
Endif.
Should also work. It would probably also be faster than using the corresponding operator, but will have more limited applicability.

Related

Fetch value from XML using dynamic tag in ESQL

I have an xml
<family>
<child_one>ROY</child_one>
<child_two>VIC</child_two>
</family>
I want to fetch the value from the XML based on the dynamic tag in ESQL. I have tried like this
SET dynamicTag = 'child_'||num;
SET value = InputRoot.XMLNSC.parent.(XML.Element)dynamicTag;
Here num is the value received from the input it can be one or two. The result should be value = ROY if num is one and value is VIC if num is two.
The chapter ESQL field reference overview describes this use case:
Because the names of the fields appear in the ESQL program, they must be known when the program is written. This limitation can be avoided by using the alternative syntax that uses braces ( { ... } ).
So can change your code like this:
SET value = InputRoot.XMLNSC.parent.(XMLNSC.Element){dynamicTag};
Notice the change of the element type as well, see comment of #kimbert.

Get components of DDIC structure with includes

I am using cl_abap_structdescr->get_components to get a list of fields in a structure. It works fine when I use it on locally declared structure types, but when I use it on DDIC structures, it doesn't give me the results I expect.
Reproducable example:
TYPES: BEGIN OF gty_outtab,
infty TYPE infty,
uname TYPE uname,
bdate TYPE datum,
btime TYPE uzeit,
pernr TYPE pernr_d,
opera TYPE hr_opera,
begda TYPE begda,
endda TYPE endda,
END OF gty_outtab.
DATA: lr_infty_structdescr TYPE REF TO cl_abap_structdescr,
lr_outtab_structdescr TYPE REF TO cl_abap_structdescr,
lt_outtab_components TYPE STANDARD TABLE OF abap_componentdescr,
lt_infty_components TYPE STANDARD TABLE OF abap_componentdescr.
" works as expected
lr_outtab_structdescr ?= cl_abap_structdescr=>describe_by_name( 'GTY_OUTTAB' ).
lt_outtab_components = lr_outtab_structdescr->get_components( ).
" doesn't work as expected
lr_infty_structdescr ?= cl_abap_structdescr=>describe_by_name( 'P0008' ).
lt_infty_components = lr_infty_structdescr->get_components( ).
BREAK-POINT.
Results:
That's okay for GTY_OUTTAB:
There are only two fields for P0008 although it contains many more fields (see below):
I already tried using cl_abap_typedescr instead and googled, but every code I find online looks just like mine?
Here is the definition of P0008 which contains many fields as you can see:
Of course after posting this, I found the reason why (german thread). Apparently, if the given structure contains included structures, then get_components won't break them up. Three solutions have been suggested in the thread and they all work great for me. Since I only need the structures' fieldnames, I will use option 1.
DATA: lt_infty_complist1 TYPE abap_compdescr_tab,
lt_infty_complist2 TYPE STANDARD TABLE OF fieldname,
lt_infty_complist3 TYPE abap_component_tab.
" 1 - get full fieldname list, but with barely any typedescription
lt_infty_complist1 = lr_infty_structdescr->components.
" 2 - get full fieldname list of DDIC structures, but without typedescription
SELECT fieldname
FROM dd03l
INTO TABLE lt_infty_complist2
WHERE tabname = 'P0008'.
DELETE lt_infty_complist2 WHERE table_line = '.INCLU--AP'
OR table_line = '.INCLUDE'.
" 3 - get full component list
" function code from: https://www.abapforum.com/forum/viewtopic.php?f=18&p=59840)
PERFORM return_components USING lr_infty_structdescr CHANGING lt_infty_complist3.
There is a method on cl_abap_structdescr called get_included_view( ) that will expand the included structures
Method GET_RTTS_FOR_LOCAL_TABLE of helper-class CL_CACS_RTTS_HELPER seems to do exactly what you want and what lacks your option 1
CALL METHOD cl_cacs_rtts_helper=>get_rtts_for_local_structure
EXPORTING
id_tabname = 'P0008'
receiving
ro_data = DATA(ro_struct)
.
It fetches all components of a structure into reference data object and also includes absolute types:
In the same class there is get field list method, which may be sufficient.
data: lo_incl_stru TYPE REF TO cl_abap_structdescr,
lt_field_list TYPE ddfields.
lt_field_list = lo_incl_stru->get_ddic_field_list( p_including_substructres = abap_true ).
If that isnt enough.... try
METHODS recursive_get_components
IMPORTING
io_structdescr TYPE REF TO cl_abap_structdescr
RETURNING
VALUE(rt_components) TYPE abap_component_tab.
METHOD recursive_get_components.
DATA:
lo_incl_stru TYPE REF TO cl_abap_structdescr,
lt_incl_comp TYPE abap_component_tab,
l_curr_index TYPE i.
FIELD-SYMBOLS: <comp> LIKE LINE OF rt_components,
<incl_comp> LIKE LINE OF lt_incl_comp.
rt_components = io_structdescr->get_components( ).
LOOP AT rt_components ASSIGNING <comp>.
IF <comp>-as_include = 'X'.
lo_incl_stru ?= <comp>-type. " not the include struc type
l_curr_index = sy-tabix. " and the index it is to be included
DELETE rt_components INDEX l_curr_index.
lt_incl_comp = recursive_get_components( io_structdescr = lo_incl_stru ).
LOOP AT lt_incl_comp ASSIGNING <incl_comp>.
INSERT <incl_comp> INTO rt_components INDEX l_curr_index.
l_curr_index = l_curr_index + 1.
ENDLOOP.
ENDIF.
ENDLOOP.
ENDMETHOD.

Using one variable for multiple items data in descriptive programming

I know that with Descriptive programming you can do something like this:
Browser("StackOverflow").Page("StackOverflow").Link("text:=Go To Next Page ", "html tag:=A").Click
But is it possible to create some kind of string so I can assign more than one data value and pass it as single variable? I've tried many combinations using escape characters and I always get error.
For example in the case above, let's say I have more properties in the Page object, so I'd normally have to do something like this:
Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")...etc...
But I'd like to pass "name:=StackOverflow", "html id:=PageID" as a single variable, so when writing many objects I'd only have to write:
Browser(BrowserString).Page(PageString).WebEdit("name:=asdfgh")
And the first part would remain static, so if the parents' data needs to be modified I'd only have to modify two variables and not all the objects created in all libraries.
Is it possible?
If I was not clear enough please let me know.
Thank you in advance!
I think what you're looking for is UFT's Description object
This allows you finer grained control on the description since in descriptive programming all values are regular expressions but with Description you can turn the regular expression functionality off for a specific property.
Set desc = Description.Create()
desc("html tag").Value = "A"
desc("innertext").Value = "More information..."
desc("innertext").RegularExpression = False
Browser("Example Domain").Navigate "www.example.com"
Browser("Example Domain").Page("Example Domain").WebElement(desc).Click
If you want to represent this with plain string then it's a bit more of a problem, you can write a helper function but I'm not sure I would recommend it.
Function Desc(descString)
Set ret = Description.Create()
values = Split(descString, "::")
For Each value In values
keyVal = Split(value, ":=")
ret(keyVal(0)).Value = keyVal(1)
Next
Set Desc = ret
End Function
' Usage
Browser("StackOverflow").Page("StackOverflow").WebElement(Desc("html tag:=H2::innertext:=some text")).Click
Further reading about descriptive programming.
As an alternative to Motti's excellent answer, you could also Set a variable to match your initial descriptive object and then extend it as required:
Set myPage = Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")
after which you can then use
myPage.WebEdit("name:=asdfgh")
throughout the rest of the code, so long as the myPage object stays in scope...

Better ternary condition

Method find_something may return nil. In the following code,
something = find_something(id) ? find_something(id) : create_something(foo)
find_something(id) is called twice. This is a smell that I want to avoid. Is there a way to avoid redundancy in this expression?
Anything like this?
something = find_something(id) || create_something(foo)
There's not quite enough detail given to say this with confidence, though it might be this is a case for find_or_create_by.
If this does suit, you would just do:
something = YourModel.find_or_create_by(id: id)
You can also provide a block to this, which is passed to the create method if no record is found. For example:
something = YourModel.find_or_create_by(id: id) do |instance|
# this block only gets executed on create
instance.some_new_attribute = 'goes here'
end
Hope that's useful - let me know if it suits your use case.

How to WRITE a structure?

How can I do the following:
data: ls_header type BAPIMEPOHEADER.
" fill it
write ls_header.
currently I'm getting an error because write can not parse the complex type to a string. Is there a simple way to get this code running in abap?
You could use something like:
DATA: g_struct TYPE bapimepoheader.
DO.
ASSIGN COMPONENT sy-index OF STRUCTURE g_struct TO FIELD-SYMBOL(<f>).
IF sy-subrc NE 0.
EXIT.
ENDIF.
WRITE: / <f>.
ENDDO.
Perhaps not exactly the answer you expect: If you list each field.
This can be done quite easy via the Pattern-mask in SE38:
Select the Write-pattern:
Enter the structure you want:
Select the fields
Confirm with "Copy"
Confirm and you get
WRITE: bapimepoheader-po_number,
bapimepoheader-comp_code,
bapimepoheader-doc_type,
bapimepoheader-delete_ind,
bapimepoheader-status,
bapimepoheader-creat_date,
bapimepoheader-created_by,
bapimepoheader-item_intvl,
bapimepoheader-vendor,
bapimepoheader-langu,
bapimepoheader-langu_iso,
bapimepoheader-pmnttrms,
bapimepoheader-dscnt1_to,
bapimepoheader-dscnt2_to,
bapimepoheader-dscnt3_to,
bapimepoheader-dsct_pct1,
bapimepoheader-dsct_pct2,
bapimepoheader-purch_org,
bapimepoheader-pur_group,
bapimepoheader-currency,
bapimepoheader-currency_iso,
bapimepoheader-exch_rate,
bapimepoheader-ex_rate_fx,
bapimepoheader-doc_date,
bapimepoheader-vper_start,
bapimepoheader-vper_end,
bapimepoheader-warranty,
bapimepoheader-quotation,
bapimepoheader-quot_date,
bapimepoheader-ref_1,
bapimepoheader-sales_pers,
bapimepoheader-telephone,
bapimepoheader-suppl_vend,
bapimepoheader-customer,
bapimepoheader-agreement,
bapimepoheader-gr_message,
bapimepoheader-suppl_plnt,
bapimepoheader-incoterms1,
bapimepoheader-incoterms2,
bapimepoheader-collect_no,
bapimepoheader-diff_inv,
bapimepoheader-our_ref,
bapimepoheader-logsystem,
bapimepoheader-subitemint,
bapimepoheader-po_rel_ind,
bapimepoheader-rel_status,
bapimepoheader-vat_cntry,
bapimepoheader-vat_cntry_iso,
bapimepoheader-reason_cancel,
bapimepoheader-reason_code,
bapimepoheader-retention_type,
bapimepoheader-retention_percentage,
bapimepoheader-downpay_type,
bapimepoheader-downpay_amount,
bapimepoheader-downpay_percent,
bapimepoheader-downpay_duedate,
bapimepoheader-memory,
bapimepoheader-memorytype,
bapimepoheader-shiptype,
bapimepoheader-handoverloc,
bapimepoheader-shipcond,
bapimepoheader-incotermsv,
bapimepoheader-incoterms2l,
bapimepoheader-incoterms3l.
Now you can make a simple replacement of bapimepoheader with ls_header and you have an output of all fields of the structure.
Maybe this is not elegant and you must adapt your report, if the structure changes. But I like this way, because often I don't need all fields and I can select the fields in an easy way.
I know two ways, one is procedural, the other is oop.
Here is the procedural approach.
Select the structure's fields (or whatever else You might need ) from the data-dictionary table DD03L into a local internal table.
Loop over the table into a work-area
Check, whether current field is a flat single datatype, and if so,
Assign component workarea-fieldname of structure ls_header into anyfieldsymbol
Write anyfieldsymbol
Do You need the code ?
Class CL_ABAP_CONTAINER_UTILITIES was specially introduced for that by SAP.
Use FILL_CONTAINER_C method for output the structure in a WRITE manner:
DATA: ls_header type BAPIMEPOHEADER.
CALL METHOD CL_ABAP_CONTAINER_UTILITIES=>FILL_CONTAINER_C
EXPORTING
IM_VALUE = ls_header
IMPORTING
EX_CONTAINER = DATA(container)
EXCEPTIONS
ILLEGAL_PARAMETER_TYPE = 1
others = 2.
WRITE container.
You can write your structure to a string and then output the string. Same method idoc segments are created.

Resources