diff --git a/run/input/UPSTREAM_COMMIT b/run/input/UPSTREAM_COMMIT index fa3610e..8ba5de4 100644 --- a/run/input/UPSTREAM_COMMIT +++ b/run/input/UPSTREAM_COMMIT @@ -1 +1 @@ -783d6f9090b944dcf42149b64fdde3074e9fe558 +95e33a1a023002e3ded6a48b0aba15e20297e27c diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_011.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_011.js index 33481d3..320a10d 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_011.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_011.js @@ -67,7 +67,7 @@ class z2ui5_cl_demo_app_011 extends z2ui5_if_app { for (let _i = this.t_tab.length - 1; _i >= 0; _i--) { const row = this.t_tab[_i]; if ((row.selkz === true || row.selkz === `X`)) this.t_tab.splice(_i, 1); } client.view_model_update(); } else if (client.check_on_event(`BUTTON_ADD`)) { - this.t_tab.push({}); + this.t_tab.push(z2ui5_cl_util.abap_copy({})); client.view_model_update(); } } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_028.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_028.js index aee9600..f05019e 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_028.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_028.js @@ -26,7 +26,7 @@ class z2ui5_cl_demo_app_028 extends z2ui5_if_app { on_event() { this.counter = this.counter + 1; - this.t_tab.push({ title: `entry${this.counter}`, info: `completed`, descr: `this is a description`, icon: `sap-icon://account`, value: ``, checkbox: false }); + this.t_tab.push(z2ui5_cl_util.abap_copy({ title: `entry${this.counter}`, info: `completed`, descr: `this is a description`, icon: `sap-icon://account`, value: ``, checkbox: false })); if (this.counter < 3) { this.start_timer(); } else { diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_045.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_045.js index e14c7ee..3d25f94 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_045.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_045.js @@ -1,3 +1,4 @@ +const z2ui5_cl_util = require("abap2UI5/z2ui5_cl_util"); const z2ui5_cl_xml_view = require("abap2UI5/z2ui5_cl_xml_view"); const z2ui5_if_app = require("abap2UI5/z2ui5_if_app"); @@ -9,7 +10,7 @@ class z2ui5_cl_demo_app_045 extends z2ui5_if_app { let ls_row; for (let sy_index = 1; sy_index <= 1000; sy_index++) { ls_row = { count: sy_index, value: `red`, info: (sy_index < 50 ? `completed` : `uncompleted`), descr: `this is a description`, checkbox: true }; - this.t_tab.push(ls_row); + this.t_tab.push(z2ui5_cl_util.abap_copy(ls_row)); } } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_053.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_053.js index 7ffc92a..1d4f2ef 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_053.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_053.js @@ -59,7 +59,9 @@ class z2ui5_cl_demo_app_053 extends z2ui5_if_app { set_search() { if (this.mv_search_value) { - z2ui5_cl_sample_context.itab_filter_by_val({ val: this.mv_search_value, tab: this.mt_table }); + const _out0 = { val: this.mv_search_value, tab: this.mt_table }; + z2ui5_cl_sample_context.itab_filter_by_val(_out0); + if ("tab" in _out0) this.mt_table = _out0.tab; } } } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_059.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_059.js index 3f33b51..3dd7edb 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_059.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_059.js @@ -21,7 +21,9 @@ class z2ui5_cl_demo_app_059 extends z2ui5_if_app { this.client = z2ui5_cl_util.abap_copy(this.client); if (this.client.check_on_event(`BUTTON_SEARCH`)) { this.set_data(); - z2ui5_cl_sample_context.itab_filter_by_val({ val: this.client.get_event_arg(), tab: this.mt_table }); + const _out0 = { val: this.client.get_event_arg(), tab: this.mt_table }; + z2ui5_cl_sample_context.itab_filter_by_val(_out0); + if ("tab" in _out0) this.mt_table = _out0.tab; this.client.view_model_update(); } } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_061.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_061.js index 60fc115..63ce4cb 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_061.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_061.js @@ -49,9 +49,9 @@ class z2ui5_cl_demo_app_061 extends z2ui5_if_app { if (client.check_on_init()) { // TODO(abap2js): CREATE DATA t_tab TYPE STANDARD TABLE OF (`Z2UI5_T_01`). // TODO(abap2js): ASSIGN t_tab->* TO . - fs_tab.push({ id: `this is an uuid`, timestampl: `2023234243`, id_prev: `previous` }); - fs_tab.push({ id: `this is an uuid`, timestampl: `2023234243`, id_prev: `previous` }); - fs_tab.push({ id: `this is an uuid`, timestampl: `2023234243`, id_prev: `previous` }); + fs_tab.push(z2ui5_cl_util.abap_copy({ id: `this is an uuid`, timestampl: `2023234243`, id_prev: `previous` })); + fs_tab.push(z2ui5_cl_util.abap_copy({ id: `this is an uuid`, timestampl: `2023234243`, id_prev: `previous` })); + fs_tab.push(z2ui5_cl_util.abap_copy({ id: `this is an uuid`, timestampl: `2023234243`, id_prev: `previous` })); } this.set_view(); } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_071.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_071.js index 301cf29..d6e7eff 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_071.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_071.js @@ -1,3 +1,4 @@ +const z2ui5_cl_util = require("abap2UI5/z2ui5_cl_util"); const z2ui5_cl_xml_view = require("abap2UI5/z2ui5_cl_xml_view"); const z2ui5_if_app = require("abap2UI5/z2ui5_if_app"); @@ -16,7 +17,7 @@ class z2ui5_cl_demo_app_071 extends z2ui5_if_app { case `UPDATE_MODEL`: this.t_combo = {}; for (let sy_index = 1; sy_index <= this.mv_combo_number; sy_index++) { - this.t_combo.push({ key: sy_index, text: sy_index }); + this.t_combo.push(z2ui5_cl_util.abap_copy({ key: sy_index, text: sy_index })); } client.message_toast_display(`update number of entries`); client.view_model_update(); @@ -25,7 +26,7 @@ class z2ui5_cl_demo_app_071 extends z2ui5_if_app { } this.mv_combo_number = 105; for (let sy_index = 1; sy_index <= this.mv_combo_number; sy_index++) { - this.t_combo.push({ key: sy_index, text: sy_index }); + this.t_combo.push(z2ui5_cl_util.abap_copy({ key: sy_index, text: sy_index })); } const view = z2ui5_cl_xml_view.factory(); client.view_display(view.shell().page({ title: `abap2UI5 - First Example`, navbuttonpress: client._event_nav_app_leave(), shownavbutton: client.check_app_prev_stack() }).simple_form({ title: `Form Title`, editable: true }).content(`form`).title(`Input`).label(`Link`).label(`setSizeLimit`).input({ value: client._bind_edit(this.mv_set_size_limit) }).button({ text: `update size limit`, press: client._event(`UPDATE`) }).label(`Number of Entries`).input({ value: client._bind_edit(this.mv_combo_number) }).button({ text: `update number entries`, press: client._event(`UPDATE_MODEL`) }).label(`demo`).combobox({ items: client._bind(this.t_combo) }).item({ key: `{KEY}`, text: `{TEXT}` }).get_parent().get_parent().stringify()); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_078.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_078.js index 3ebd1a3..665a21f 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_078.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_078.js @@ -1,3 +1,4 @@ +const z2ui5_cl_util = require("abap2UI5/z2ui5_cl_util"); const z2ui5_cl_xml_view = require("abap2UI5/z2ui5_cl_xml_view"); const z2ui5_if_app = require("abap2UI5/z2ui5_if_app"); @@ -39,7 +40,7 @@ class z2ui5_cl_demo_app_078 extends z2ui5_if_app { sy_tabix = 0; for (const ls_token of this.mt_tokens_added) { sy_tabix++; - this.mt_token.push({ key: ls_token.key, text: ls_token.text, visible: true, editable: true, selkz: false }); + this.mt_token.push(z2ui5_cl_util.abap_copy({ key: ls_token.key, text: ls_token.text, visible: true, editable: true, selkz: false })); } this.mt_tokens_removed = {}; this.mt_tokens_added = {}; diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_097.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_097.js index 158bad7..6e7333e 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_097.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_097.js @@ -71,7 +71,7 @@ class z2ui5_cl_demo_app_097 extends z2ui5_if_app { if (sy_subrc === 0) ls_sel = _t[_i]; } ls_sel.uuid = z2ui5_cl_sample_context.uuid_get_c32(); - this.t_tab2.push(ls_sel); + this.t_tab2.push(z2ui5_cl_util.abap_copy(ls_sel)); this.mv_layout = `TwoColumnsMidExpanded`; client.nest_view_model_update(); client.view_model_update(); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_098.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_098.js index b182c66..488fa12 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_098.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_098.js @@ -87,7 +87,7 @@ class z2ui5_cl_demo_app_098 extends z2ui5_if_app { if (sy_subrc === 0) ls_sel = _t[_i]; } if (!this.t_tab2.some((row) => row.title === ls_sel.title)) { - this.t_tab2.push(ls_sel); + this.t_tab2.push(z2ui5_cl_util.abap_copy(ls_sel)); } this.mv_layout = `TwoColumnsMidExpanded`; client.nest_view_model_update(); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_101.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_101.js index 03249be..99f8800 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_101.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_101.js @@ -30,7 +30,7 @@ class z2ui5_cl_demo_app_101 extends z2ui5_if_app { if (!this.value) { return; } - this.t_feed.splice((1) - 1, 0, { author: `Alexandrina Victoria`, authorpic: `http://upload.wikimedia.org/wikipedia/commons/a/aa/Dronning_victoria.jpg`, type: `Reply`, date: `${sy_datum} ${sy_uzeit}`, text: this.value }); + this.t_feed.splice((1) - 1, 0, z2ui5_cl_util.abap_copy({ author: `Alexandrina Victoria`, authorpic: `http://upload.wikimedia.org/wikipedia/commons/a/aa/Dronning_victoria.jpg`, type: `Reply`, date: `${sy_datum} ${sy_uzeit}`, text: this.value })); this.value = {}; this.client.view_model_update(); } else if (this.client.check_on_event(`SENDER_PRESS`)) { @@ -41,7 +41,7 @@ class z2ui5_cl_demo_app_101 extends z2ui5_if_app { } view_display() { - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com/`; const view = z2ui5_cl_xml_view.factory(); const page = view.shell() .page({ title: `abap2UI5 - Sample: Feed`, navbuttonpress: this.client._event_nav_app_leave(), shownavbutton: this.client.check_app_prev_stack() }); @@ -54,7 +54,7 @@ class z2ui5_cl_demo_app_101 extends z2ui5_if_app { } set_data() { - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com/`; this.t_feed = [{ author: `Alexandrina Victoria`, authorpic: base_url + `test-resources/sap/m/images/dronning_victoria.jpg`, type: `Request`, date: `March 03 2013`, text: `Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.` + `Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.` + ` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, seddiamnonumyeirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.` + ` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.` + ` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.` + ` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.` }, { author: `George Washington`, authorpic: base_url + `test-resources/sap/m/images/george_washington.jpg`, type: `Reply`, date: `March 04 2013`, text: `Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore` }, { author: `Alexandrina Victoria`, authorpic: base_url + `test-resources/sap/m/images/dronning_victoria.jpg`, type: `Request`, date: `March 05 2013`, text: `Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat` }, { author: `George Washington`, authorpic: base_url + `test-resources/sap/m/images/george_washington.jpg`, type: `Rejection`, date: `March 07 2013`, text: `Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.` }]; } } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_104.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_104.js index aa4d549..01d82b2 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_104.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_104.js @@ -78,7 +78,7 @@ class z2ui5_cl_demo_app_104 extends z2ui5_if_app { sy_subrc = _i >= 0 && _i < _t.length ? 0 : 4; if (sy_subrc === 0) ls_sel = _t[_i]; } - this.t_tab2.push(ls_sel); + this.t_tab2.push(z2ui5_cl_util.abap_copy(ls_sel)); if (this.classname) { this.view_display_master(); } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_105.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_105.js index dbc8a84..6a7420f 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_105.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_105.js @@ -7,8 +7,10 @@ class z2ui5_cl_demo_app_105 extends z2ui5_if_app { mr_data = null; client = null; - view_display({ xml } = {}) { + view_display(_args = {}) { + let { xml } = _args; this.mo_view_parent.input({ value: this.client._bind_edit(this.mv_class_1), placeholder: `Input From Class 1` }); + Object.assign(_args, { xml }); } on_event() { diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_112.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_112.js index 5d0554f..82c5d98 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_112.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_112.js @@ -7,8 +7,10 @@ class z2ui5_cl_demo_app_112 extends z2ui5_if_app { mr_data = null; client = null; - view_display({ xml } = {}) { + view_display(_args = {}) { + let { xml } = _args; this.mo_view_parent.input({ value: this.client._bind_edit(this.mv_class_2), placeholder: `Input From Class 2` }); + Object.assign(_args, { xml }); } on_event() { diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_114.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_114.js index 2cbda5c..bcdc70f 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_114.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_114.js @@ -27,7 +27,7 @@ class z2ui5_cl_demo_app_114 extends z2ui5_if_app { this.ms_feed.type = `Respond`; this.ms_feed.text = z2ui5_cl_util.abap_copy(this.mv_value); this.mv_value = ``; - this.mt_feed.splice((1) - 1, 0, this.ms_feed); + this.mt_feed.splice((1) - 1, 0, z2ui5_cl_util.abap_copy(this.ms_feed)); this.client.view_model_update(); } } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_126.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_126.js index 505b072..1e68255 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_126.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_126.js @@ -31,7 +31,7 @@ class z2ui5_cl_demo_app_126 extends z2ui5_if_app { for (const com of comp) { sy_tabix++; if (!(com.as_include === true || com.as_include === `X`)) { - result.push(com); + result.push(z2ui5_cl_util.abap_copy(com)); } } } catch (error) { diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_160.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_160.js index 5c9a619..9020571 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_160.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_160.js @@ -20,7 +20,7 @@ class z2ui5_cl_demo_app_160 extends z2ui5_if_app { model_init() { this.mt_output = {}; for (let sy_index = 1; sy_index <= 10; sy_index++) { - this.mt_output.push({ index: sy_index, set_sk: `Test`, matnr: `1234567`, description: `Test`, pl_01: 0, pl_02: 0, is_total: 0, pl_total: 0, per_cent_total: 0, is_01_prev: 0, per_cent_01: 0, is_02_prev: 0, per_cent_02: 0, is_03_prev: 0, pl_03: 0, per_cent_03: 0, is_q01_prev: 0, pl_q01: 0, per_cent_q01: 0, is_q02_prev: 0, pl_q02: 0, per_cent_q02: 0, is_q03_prev: 0, pl_q03: 0, per_cent_q03: 0, is_q04_prev: 0, pl_q04: 0, per_cent_q04: 0 }); + this.mt_output.push(z2ui5_cl_util.abap_copy({ index: sy_index, set_sk: `Test`, matnr: `1234567`, description: `Test`, pl_01: 0, pl_02: 0, is_total: 0, pl_total: 0, per_cent_total: 0, is_01_prev: 0, per_cent_01: 0, is_02_prev: 0, per_cent_02: 0, is_03_prev: 0, pl_03: 0, per_cent_03: 0, is_q01_prev: 0, pl_q01: 0, per_cent_q01: 0, is_q02_prev: 0, pl_q02: 0, per_cent_q02: 0, is_q03_prev: 0, pl_q03: 0, per_cent_q03: 0, is_q04_prev: 0, pl_q04: 0, per_cent_q04: 0 })); } } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_197.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_197.js index b32cb4c..1318e1d 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_197.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_197.js @@ -62,7 +62,7 @@ class z2ui5_cl_demo_app_197 extends z2ui5_if_app { for (const l_member of l_members) { sy_tabix++; lv_val = lo_json.get(`/` + l_member + `/mProperties/text`); - lt_range.push({ sign: `I`, option: `EQ`, low: lv_val }); + lt_range.push(z2ui5_cl_util.abap_copy({ sign: `I`, option: `EQ`, low: lv_val })); } } catch (error) { } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_258.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_258.js index f0e3a95..daf8b6d 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_258.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_258.js @@ -73,15 +73,18 @@ class z2ui5_cl_demo_app_258 extends z2ui5_if_app { .navigation_list_item({ text: `Fixed Entry 2`, icon: `sap-icon://flight`, select: client._event(`MENU_FIX2`), key: `Fix2` }) .navigation_list_item({ text: `Fixed Entry 3`, icon: `sap-icon://email-read`, select: client._event(`MENU_FIX3`), key: `Fix3` }) .navigation_list_item({ text: `Link`, icon: `sap-icon://chain-link`, href: `https://github.com/abap2UI5/abap2UI5` }); - const site_content = content.flex_box({ id: `site_content`, class: `sapUiTinyMarginTop sapUiTinyMarginBegin`, width: `100%`, height: `100%`, backgrounddesign: `Solid`, alignitems: `Center`, justifycontent: `Center` }) + let site_content = content.flex_box({ id: `site_content`, class: `sapUiTinyMarginTop sapUiTinyMarginBegin`, width: `100%`, height: `100%`, backgrounddesign: `Solid`, alignitems: `Center`, justifycontent: `Center` }) .layout_data() .flex_item_data({ growfactor: `4`, backgrounddesign: `Solid` }) .get_parent(); - this.render_site_content({ client, site_content }); + const _out0 = { client, site_content }; + this.render_site_content(_out0); + if ("site_content" in _out0) site_content = _out0.site_content; client.view_display(page.stringify()); } - render_site_content({ client, site_content } = {}) { + render_site_content(_args = {}) { + let { client, site_content } = _args; switch (this.selected_menu_entry) { case `Home`: site_content.text(`Welcome to the Home Page`); @@ -111,6 +114,7 @@ class z2ui5_cl_demo_app_258 extends z2ui5_if_app { site_content.text(`Welcome to the third fixed Page`); break; } + Object.assign(_args, { site_content }); } async main(client) { diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_271.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_271.js index 73d3dfd..133cf0a 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_271.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_271.js @@ -6,7 +6,7 @@ class z2ui5_cl_demo_app_271 extends z2ui5_if_app { client = null; view_display({ client } = {}) { - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com`; const page = z2ui5_cl_xml_view.factory() .shell() .page({ title: `abap2UI5 - Sample: ImageContent`, navbuttonpress: client._event_nav_app_leave(), shownavbutton: client.check_app_prev_stack() }); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_272.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_272.js index 0028bcf..f03ecac 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_272.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_272.js @@ -6,7 +6,7 @@ class z2ui5_cl_demo_app_272 extends z2ui5_if_app { client = null; view_display({ client } = {}) { - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com/`; const view = z2ui5_cl_xml_view.factory(); const page = view.shell() .page({ title: `abap2UI5 - Object Header with Circle-shaped Image`, navbuttonpress: client._event_nav_app_leave(), shownavbutton: client.check_app_prev_stack() }) diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_273.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_273.js index 063edb0..32448b6 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_273.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_273.js @@ -6,7 +6,7 @@ class z2ui5_cl_demo_app_273 extends z2ui5_if_app { client = null; view_display({ client } = {}) { - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com/`; const page = z2ui5_cl_xml_view.factory() .shell() .page({ title: `abap2UI5 - Sample: LightBox`, navbuttonpress: client._event_nav_app_leave(), shownavbutton: client.check_app_prev_stack() }); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_274.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_274.js index 6a0215b..dff966a 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_274.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_274.js @@ -6,7 +6,7 @@ class z2ui5_cl_demo_app_274 extends z2ui5_if_app { client = null; view_display({ client } = {}) { - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com/`; const page = z2ui5_cl_xml_view.factory() .shell() .page({ title: `abap2UI5 - Sample: Slide Tile`, navbuttonpress: client._event_nav_app_leave(), shownavbutton: client.check_app_prev_stack() }); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_278.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_278.js index b01f823..7f0212e 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_278.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_278.js @@ -7,7 +7,7 @@ class z2ui5_cl_demo_app_278 extends z2ui5_if_app { view_display({ client } = {}) { const css = `.tileLayout {` + ` float: left;` + `}`; - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com/`; const page = z2ui5_cl_xml_view.factory() .shell() .page({ title: `abap2UI5 - Sample: Feed and News Tile`, navbuttonpress: client._event_nav_app_leave(), shownavbutton: client.check_app_prev_stack() }); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_281.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_281.js index 7c7a289..84ec0d9 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_281.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_281.js @@ -7,7 +7,7 @@ class z2ui5_cl_demo_app_281 extends z2ui5_if_app { view_display({ client } = {}) { const css = `.tileLayout {` + ` float: left;` + `}`; - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com/`; const page = z2ui5_cl_xml_view.factory() .shell() .page({ title: `abap2UI5 - Sample: Tile Statuses`, navbuttonpress: client._event_nav_app_leave(), shownavbutton: client.check_app_prev_stack() }); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_283.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_283.js index 168a1ab..5dfd2a8 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_283.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_283.js @@ -6,7 +6,7 @@ class z2ui5_cl_demo_app_283 extends z2ui5_if_app { client = null; view_display({ client } = {}) { - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com/`; const page = z2ui5_cl_xml_view.factory() .shell() .page({ title: `abap2UI5 - Sample: Feed Input`, navbuttonpress: client._event_nav_app_leave(), shownavbutton: client.check_app_prev_stack() }); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_294.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_294.js index 37bb7d2..c9d6806 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_294.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_294.js @@ -50,11 +50,11 @@ class z2ui5_cl_demo_app_294 extends z2ui5_if_app { this.s_text = {}; this.lt_a_data = {}; this.s_text = `DatePicker with valueState `; - this.lt_a_data.push({ label: this.s_text + `None`, value_state: `None`, value_state_text: `` }); - this.lt_a_data.push({ label: this.s_text + `Information`, value_state: `Information`, value_state_text: `` }); - this.lt_a_data.push({ label: this.s_text + `Success`, value_state: `Success`, value_state_text: `` }); - this.lt_a_data.push({ label: this.s_text + `Warning and long valueStateText`, value_state: `Warning`, value_state_text: `Warning message. This is an extra long text used as a warning message. ` + `It illustrates how the text wraps into two or more lines without truncation to show the full length of the message.` }); - this.lt_a_data.push({ label: this.s_text + `Error`, value_state: `Error`, value_state_text: `` }); + this.lt_a_data.push(z2ui5_cl_util.abap_copy({ label: this.s_text + `None`, value_state: `None`, value_state_text: `` })); + this.lt_a_data.push(z2ui5_cl_util.abap_copy({ label: this.s_text + `Information`, value_state: `Information`, value_state_text: `` })); + this.lt_a_data.push(z2ui5_cl_util.abap_copy({ label: this.s_text + `Success`, value_state: `Success`, value_state_text: `` })); + this.lt_a_data.push(z2ui5_cl_util.abap_copy({ label: this.s_text + `Warning and long valueStateText`, value_state: `Warning`, value_state_text: `Warning message. This is an extra long text used as a warning message. ` + `It illustrates how the text wraps into two or more lines without truncation to show the full length of the message.` })); + this.lt_a_data.push(z2ui5_cl_util.abap_copy({ label: this.s_text + `Error`, value_state: `Error`, value_state_text: `` })); } } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_306.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_306.js index c39fdf4..b39686b 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_306.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_306.js @@ -59,7 +59,7 @@ class z2ui5_cl_demo_app_306 extends z2ui5_if_app { } switch (client.get().EVENT) { case `CAPTURE`: - this.mt_picture.push({ data: this.mv_picture_base, thumbnail: this.mv_picture_thumb, time: sy_uzeit, id: ``, name: ``, selected: false }); + this.mt_picture.push(z2ui5_cl_util.abap_copy({ data: this.mv_picture_base, thumbnail: this.mv_picture_thumb, time: sy_uzeit, id: ``, name: ``, selected: false })); this.mv_picture_base = {}; this.mv_picture_thumb = {}; client.view_model_update(); @@ -88,7 +88,7 @@ class z2ui5_cl_demo_app_306 extends z2ui5_if_app { sy_tabix = 0; for (const ls_pic of this.mt_picture) { sy_tabix++; - this.mt_picture_out.push({ name: `picture ${sy_tabix}`, id: sy_tabix, thumbnail: ls_pic.thumbnail, selected: (sy_tabix === this.selected_picture.id ? true : null), time: ``, data: `` }); + this.mt_picture_out.push(z2ui5_cl_util.abap_copy({ name: `picture ${sy_tabix}`, id: sy_tabix, thumbnail: ls_pic.thumbnail, selected: (sy_tabix === this.selected_picture.id ? true : null), time: ``, data: `` })); } } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_307.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_307.js index 2dbd436..de521b6 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_307.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_307.js @@ -73,9 +73,9 @@ class z2ui5_cl_demo_app_307 extends z2ui5_if_app { drop_position = drop_position - 1; } if (insert_position === `Before`) { - this.items.splice((drop_position) - 1, 0, item); + this.items.splice((drop_position) - 1, 0, z2ui5_cl_util.abap_copy(item)); } else { - this.items.splice((drop_position + 1) - 1, 0, item); + this.items.splice((drop_position + 1) - 1, 0, z2ui5_cl_util.abap_copy(item)); } } client.view_model_update(); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_362.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_362.js index 0145441..94ba6ce 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_362.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_362.js @@ -18,7 +18,7 @@ class z2ui5_cl_demo_app_362 extends z2ui5_if_app { on_init() { for (let sy_index = 1; sy_index <= 100; sy_index++) { - this.t_tab.push({ title: `Row ${sy_index}`, value: `red`, info: `completed`, descr: `this is a description` }); + this.t_tab.push(z2ui5_cl_util.abap_copy({ title: `Row ${sy_index}`, value: `red`, info: `completed`, descr: `this is a description` })); } this.view_display(); } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_375.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_375.js index a02a841..44d0dcf 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_375.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_375.js @@ -86,7 +86,7 @@ class z2ui5_cl_demo_app_375 extends z2ui5_if_app { } set_data() { - let base_url = ``; + let base_url = `https://sapui5.hana.ondemand.com/`; this.t_items = [{ title: `New order (#2525) With a very long title - Lorem ipsum dolor sit amet, consectetur adipiscing elit. ` + `Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.`, description: `And with a very long description and long labels of the action buttons - Lorem ipsum dolor sit amet, consectetur adipiscing elit. ` + `Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.`, showclosebutton: true, datetime: `1 hour`, unread: true, priority: `None`, authorname: `Jean Doe`, authorpicture: base_url + `test-resources/sap/m/images/Woman_04.png`, buttons: `ACCEPT_REJECT_LONG` }, { title: `New order (#2524), without action buttons`, description: `Short description`, showclosebutton: true, datetime: `3 days`, unread: true, priority: `High`, authorname: `Office Notification`, authorpicture: `sap-icon://group` }, { title: `New order (#2523) With a long title - Lorem ipsum dolor sit amet, consectetur adipiscing elit.`, description: `And short description`, showclosebutton: false, unread: false, datetime: `3 days`, priority: `High`, authorname: `Patricia Clark`, buttons: `ACCEPT_REJECT_ICON` }, { title: `New order (#2522)`, description: `With a very long description - Lorem ipsum dolor sit amet, consectetur adipiscing elit. ` + `Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.`, showclosebutton: true, datetime: `3 days`, unread: true, priority: `Medium`, authorname: `John Smith` }, { title: `New order (#2521)`, description: `With a very long description and no action buttons below - Lorem ipsum dolor sit amet, consectetur adipiscing elit. ` + `Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.`, showclosebutton: true, datetime: `3 days`, unread: true, priority: `Low`, authorname: `John Smith`, authorpicture: base_url + `test-resources/sap/m/images/headerImg2.jpg` }, { title: `New order (#2525) With a very long title and truncation disabled by default! Lorem ipsum dolor sit amet, consectetur adipiscing elit. ` + `Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.`, description: `And a very long description and long labels of the action buttons - Lorem ipsum dolor sit amet, consectetur adipiscing elit. ` + `Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.`, showclosebutton: true, datetime: `2 day`, unread: false, priority: `Low`, authorname: `Jean Doe`, authorpicture: base_url + `test-resources/sap/m/images/Woman_04.png`, truncate: `false`, buttons: `ACCEPT` }, { title: `New order (#2525) With a very long title and with truncation enabled but 'Show More' hidden! Lorem ipsum dolor sit amet, consectetur adipiscing elit. ` + `Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.`, description: `And a very long description and long labels of the action buttons - Lorem ipsum dolor sit amet, consectetur adipiscing elit. ` + `Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.`, showclosebutton: true, datetime: `2 day`, unread: false, priority: `Low`, authorname: `Jean Doe`, authorpicture: base_url + `test-resources/sap/m/images/Woman_04.png`, hideshowmorebutton: `true`, showbuttons: `false`, buttons: `ACCEPT_REJECT` }, { title: `New order (#2523) With a long title without description - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet`, showclosebutton: false, unread: false, datetime: `3 days`, priority: `High`, authorname: `Patricia Clark`, authorpicture: base_url + `test-resources/sap/m/images/female_BaySu.jpg`, buttons: `ACCEPT_REJECT_ICON` }, { title: `New order (#2523) With a long title without description`, showclosebutton: true, unread: false, datetime: `3 days`, priority: `High` }]; } diff --git a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_447.js b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_447.js index 65c48f0..74d9e62 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_demo_app_447.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_demo_app_447.js @@ -10,7 +10,7 @@ class z2ui5_cl_demo_app_447 extends z2ui5_if_app { this.client = z2ui5_cl_util.abap_copy(client); if (client.check_on_init()) { for (let sy_index = 1; sy_index <= 30; sy_index++) { - this.mt_row.push({ index: sy_index, text: `Row number ${sy_index}` }); + this.mt_row.push(z2ui5_cl_util.abap_copy({ index: sy_index, text: `Row number ${sy_index}` })); } this.view_display(); } else { diff --git a/run/input/core/srv/app/samples/z2ui5_cl_sample_app_001.js b/run/input/core/srv/app/samples/z2ui5_cl_sample_app_001.js index 9905e70..5f4e2e5 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_sample_app_001.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_sample_app_001.js @@ -129,7 +129,7 @@ class z2ui5_cl_sample_app_001 extends z2ui5_if_app { _fs$fs_block = sy_subrc === 0 ? { o: _t, k: _i } : null; } if (sy_subrc !== 0) { - fs_block = { group: tile.group, base: base, width: 0 }; + fs_block = z2ui5_cl_util.abap_copy({ group: tile.group, base: base, width: 0 }); result.push(fs_block); } width = this.header_width({ header: tile.header }); diff --git a/run/input/core/srv/app/samples/z2ui5_cl_sample_context.js b/run/input/core/srv/app/samples/z2ui5_cl_sample_context.js index 72ee789..3de6ef5 100644 --- a/run/input/core/srv/app/samples/z2ui5_cl_sample_context.js +++ b/run/input/core/srv/app/samples/z2ui5_cl_sample_context.js @@ -81,7 +81,7 @@ class z2ui5_cl_sample_context { } lo_ele = (lo_descr); result = z2ui5_cl_sample_context.boolean_check_by_name({ val: lo_ele.get_relative_name() }); - z2ui5_cl_sample_context.mt_bool_cache.push({ absolute_name: lv_abs_name, is_bool: result }); + z2ui5_cl_sample_context.mt_bool_cache.push(z2ui5_cl_util.abap_copy({ absolute_name: lv_abs_name, is_bool: result })); } catch (error) { } return result; @@ -126,7 +126,8 @@ class z2ui5_cl_sample_context { return result; } - static filter_itab({ filter, val } = {}) { + static filter_itab(_args = {}) { + let { filter, val } = _args; let sy_tabix = 0; let sy_subrc = 0; let fs_field = null; @@ -150,6 +151,7 @@ class z2ui5_cl_sample_context { } sy_tabix = _sy_tabix_1; } + Object.assign(_args, { val }); } static filter_get_multi_by_data({ val } = {}) { @@ -158,7 +160,7 @@ class z2ui5_cl_sample_context { sy_tabix = 0; for (const lr_comp of z2ui5_cl_sample_context.rtti_get_t_attri_by_any({ val: val })) { sy_tabix++; - result.push({ name: lr_comp.name, t_range: [], t_token: [], t_token_added: [], t_token_removed: [] }); + result.push(z2ui5_cl_util.abap_copy({ name: lr_comp.name, t_range: [], t_token: [], t_token_added: [], t_token_removed: [] })); } return result; } @@ -215,7 +217,7 @@ class z2ui5_cl_sample_context { sy_tabix = 0; for (const ls_token of val) { sy_tabix++; - result.push(z2ui5_cl_sample_context.filter_get_range_by_token({ val: ls_token.text })); + result.push(z2ui5_cl_util.abap_copy(z2ui5_cl_sample_context.filter_get_range_by_token({ val: ls_token.text }))); } return result; } @@ -231,20 +233,23 @@ class z2ui5_cl_sample_context { let sy_tabix = 0; let lv_value; const lt_mapping = z2ui5_cl_sample_context.filter_get_token_range_mapping(); - const lt_tab = {}; - z2ui5_cl_sample_context.itab_corresponding({ val, tab: lt_tab }); + let lt_tab = {}; + const _out0 = { val, tab: lt_tab }; + z2ui5_cl_sample_context.itab_corresponding(_out0); + if ("tab" in _out0) lt_tab = _out0.tab; sy_tabix = 0; for (const lr_row of lt_tab) { sy_tabix++; lv_value = lt_mapping.find((row) => row.n === lr_row.option).v; - // TODO(abap2js): REPLACE `{LOW}` IN lv_value WITH lr_row->low. - // TODO(abap2js): REPLACE `{HIGH}` IN lv_value WITH lr_row->high. - result.push({ key: lv_value, text: lv_value, visible: true, editable: true, selkz: false }); + lv_value = String(lv_value).replace(`{LOW}`, lr_row.low ?? ``); + lv_value = String(lv_value).replace(`{HIGH}`, lr_row.high ?? ``); + result.push(z2ui5_cl_util.abap_copy({ key: lv_value, text: lv_value, visible: true, editable: true, selkz: false })); } return result; } - static itab_filter_by_val({ val, fields, ignore_case = false, tab } = {}) { + static itab_filter_by_val(_args = {}) { + let { val, fields, ignore_case = false, tab } = _args; let sy_tabix = 0; let sy_subrc = 0; let fs_field = null; @@ -254,6 +259,7 @@ class z2ui5_cl_sample_context { let lv_name; let lv_value; if (!val) { + Object.assign(_args, { tab }); return; } const lv_search = ((ignore_case === true || ignore_case === `X`) ? val.toUpperCase() : val); @@ -302,6 +308,7 @@ class z2ui5_cl_sample_context { // TODO(abap2js): DELETE tab. } } + Object.assign(_args, { tab }); } static itab_get_csv_by_itab({ val } = {}) { @@ -323,18 +330,18 @@ class z2ui5_cl_sample_context { sy_subrc = 0; const tab = (cl_abap_typedescr.describe_by_data(fs_tab)); const struc = (tab.get_table_line_type()); - lv_line = null; + lv_line = ``; sy_tabix = 0; for (const lr_comp of struc.get_components()) { sy_tabix++; lv_line = `${lv_line}${lr_comp.name};`; } - lt_lines.push(lv_line); + lt_lines.push(z2ui5_cl_util.abap_copy(lv_line)); let lr_row = null; sy_tabix = 0; for (const lr_row of fs_tab) { sy_tabix++; - lv_line = null; + lv_line = ``; lv_index = 1; for (let sy_index = 1; ; sy_index++) { // TODO(abap2js): ASSIGN lr_row->* TO FIELD-SYMBOL(). @@ -346,10 +353,10 @@ class z2ui5_cl_sample_context { } lv_index = lv_index + 1; lv_field_val = `${fs_field}`; - // TODO(abap2js): REPLACE ALL OCCURRENCES OF `;` IN lv_field_val WITH `,`. + lv_field_val = String(lv_field_val).replaceAll(`;`, `,` ?? ``); lv_line = `${lv_line}${lv_field_val};`; } - lt_lines.push(lv_line); + lt_lines.push(z2ui5_cl_util.abap_copy(lv_line)); } result = lt_lines.join(z2ui5_cl_sample_context.cv_char_util_cr_lf); return result; @@ -374,8 +381,8 @@ class z2ui5_cl_sample_context { for (const lr_col of lt_cols) { sy_tabix++; lv_name = z2ui5_cl_sample_context.c_trim_upper({ val: lr_col }); - // TODO(abap2js): REPLACE ALL OCCURRENCES OF ` ` IN lv_name WITH `_`. - lt_comp.push({ name: lv_name, type: cl_abap_elemdescr.get_c(40) }); + lv_name = String(lv_name).replaceAll(` `, `_` ?? ``); + lt_comp.push(z2ui5_cl_util.abap_copy({ name: lv_name, type: cl_abap_elemdescr.get_c(40) })); } const struc = cl_abap_structdescr.get(lt_comp); const data = (struc); @@ -403,17 +410,19 @@ class z2ui5_cl_sample_context { if (_fs$fs_field) _fs$fs_field.o[_fs$fs_field.k] = fs_field; } sy_tabix = _sy_tabix_1; - fs_tab.push(fs_row); + fs_tab.push(z2ui5_cl_util.abap_copy(fs_row)); } return result; } - static json_parse({ val, data } = {}) { + static json_parse(_args = {}) { + let { val, data } = _args; try { // TODO(abap2js): z2ui5_cl_ajson=>parse( val )->to_abap( EXPORTING iv_corresponding = abap_true IMPORTING ev_container = data ). } catch (x) { throw new z2ui5_cx_sample_error({ val: x }); } + Object.assign(_args, { data }); } static rtti_check_class_exists({ val } = {}) { @@ -449,11 +458,11 @@ class z2ui5_cl_sample_context { sy_tabix = 0; for (const lr_incl_comp of incl_comps) { sy_tabix++; - result.push(lr_incl_comp); + result.push(z2ui5_cl_util.abap_copy(lr_incl_comp)); } sy_tabix = _sy_tabix_1; } else { - result.push(lr_comp); + result.push(z2ui5_cl_util.abap_copy(lr_comp)); } } return result; @@ -506,7 +515,7 @@ class z2ui5_cl_sample_context { for (const lr_comp of comps) { sy_tabix++; if (!(lr_comp.as_include === true || lr_comp.as_include === `X`)) { - result.push(lr_comp); + result.push(z2ui5_cl_util.abap_copy(lr_comp)); } else { lt_attri = z2ui5_cl_sample_context.rtti_get_t_attri_by_include(lr_comp.type); result.push(...lt_attri); @@ -516,13 +525,13 @@ class z2ui5_cl_sample_context { lr_cache.o_struct = z2ui5_cl_util.abap_copy(lo_struct); lr_cache.t_attri = z2ui5_cl_util.abap_copy(result); } else { - z2ui5_cl_sample_context.mt_attri_cache.push({ absolute_name: lv_absolute_name, o_struct: lo_struct, t_attri: result }); + z2ui5_cl_sample_context.mt_attri_cache.push(z2ui5_cl_util.abap_copy({ absolute_name: lv_absolute_name, o_struct: lo_struct, t_attri: result })); } return result; } static time_get_timestampl() { - let result = null; + let result = 0; // TODO(abap2js): GET TIME STAMP FIELD result. return result; } @@ -552,17 +561,20 @@ class z2ui5_cl_sample_context { for (const lr_param of lt_param) { sy_tabix++; let [lv_name, lv_value] = lr_param.split(`=`); - rt_params.push({ n: lv_name, v: lv_value }); + rt_params.push(z2ui5_cl_util.abap_copy({ n: lv_name, v: lv_value })); } return rt_params; } - static xml_parse({ xml, any } = {}) { + static xml_parse(_args = {}) { + let { xml, any } = _args; if (!xml) { any = null; + Object.assign(_args, { any }); return; } // TODO(abap2js): CALL TRANSFORMATION id SOURCE XML xml RESULT data = any. + Object.assign(_args, { any }); } static xml_srtti_parse({ rtti_data } = {}) { @@ -643,22 +655,25 @@ class z2ui5_cl_sample_context { sy_tabix++; if ((lr_comp.as_include === true || lr_comp.as_include === `X`)) { lt_attri = z2ui5_cl_sample_context.rtti_get_t_attri_by_include(lr_comp.type); - result.push(...lt_attri); + result.push(...lt_attri.map((_r) => z2ui5_cl_util.abap_copy(_r))); } else { - result.push(lr_comp); + result.push(z2ui5_cl_util.abap_copy(lr_comp)); } } return result; } - static itab_corresponding({ val, tab } = {}) { + static itab_corresponding(_args = {}) { + let { val, tab } = _args; let sy_tabix = 0; sy_tabix = 0; for (const fs_row_in of val) { sy_tabix++; - tab.push({}); + let fs_row_out = {}; + tab.push(fs_row_out); // TODO(abap2js): MOVE-CORRESPONDING TO . } + Object.assign(_args, { tab }); } static msg_get_t() { @@ -780,7 +795,7 @@ class z2ui5_cl_sample_context { let dfies = null; let s_dfies = {}; let temp9 = []; - let comps = null; + let comps = []; let temp10 = null; let lo_struct = null; let new_struct_desc = null; @@ -831,8 +846,8 @@ class z2ui5_cl_sample_context { _fs$fs_value_dest = null; } sy_tabix = _sy_tabix_1; - result.push(s_dfies); - s_dfies = null; + result.push(z2ui5_cl_util.abap_copy(s_dfies)); + s_dfies = {}; } } catch (error) { } @@ -887,7 +902,7 @@ class z2ui5_cl_sample_context { fs_field = _fs$fs_field ? _fs$fs_field.o[_fs$fs_field.k] : null; sy_subrc = _fs$fs_field ? 0 : 4; if (!(sy_subrc === 0)) throw new Error(`ASSERT failed`); - names.push(fs_field); + names.push(z2ui5_cl_util.abap_copy(fs_field)); } } } catch (error) { @@ -900,7 +915,7 @@ class z2ui5_cl_sample_context { if (names.some((row) => row.table_line === lr_comp.name)) { lv_check_key = true; } - result.push({ fieldname: lr_comp.name, rollname: lr_comp.name, keyflag: lv_check_key, scrtext_s: lr_comp.name, scrtext_m: lr_comp.name, scrtext_l: lr_comp.name, tabname: ``, langu: ``, position: ``, offset: ``, domname: ``, checktable: ``, leng: ``, intlen: ``, outputlen: ``, decimals: ``, datatype: ``, inttype: ``, reftable: ``, reffield: ``, precfield: ``, authorid: ``, memoryid: ``, logflag: ``, mask: ``, masklen: ``, convexit: ``, headlen: ``, scrlen1: ``, scrlen2: ``, scrlen3: ``, fieldtext: ``, reptext: ``, lowercase: ``, mac: ``, genkey: ``, noforkey: ``, valexi: ``, noauthch: ``, sign: ``, dynpfld: ``, f4availabl: ``, comptype: ``, lfieldname: ``, ltrflddis: ``, bidictrlc: ``, outputstyle: ``, nohistory: ``, ampmformat: `` }); + result.push(z2ui5_cl_util.abap_copy({ fieldname: lr_comp.name, rollname: lr_comp.name, keyflag: lv_check_key, scrtext_s: lr_comp.name, scrtext_m: lr_comp.name, scrtext_l: lr_comp.name, tabname: ``, langu: ``, position: ``, offset: ``, domname: ``, checktable: ``, leng: ``, intlen: ``, outputlen: ``, decimals: ``, datatype: ``, inttype: ``, reftable: ``, reffield: ``, precfield: ``, authorid: ``, memoryid: ``, logflag: ``, mask: ``, masklen: ``, convexit: ``, headlen: ``, scrlen1: ``, scrlen2: ``, scrlen3: ``, fieldtext: ``, reptext: ``, lowercase: ``, mac: ``, genkey: ``, noforkey: ``, valexi: ``, noauthch: ``, sign: ``, dynpfld: ``, f4availabl: ``, comptype: ``, lfieldname: ``, ltrflddis: ``, bidictrlc: ``, outputstyle: ``, nohistory: ``, ampmformat: `` })); } return result; } @@ -980,7 +995,7 @@ class z2ui5_cl_sample_context { for (const symbol of fs_tab) { sy_tabix++; lt_tab = z2ui5_cl_sample_context.msg_get_internal({ val: fs_row }); - result.push(...lt_tab); + result.push(...lt_tab.map((_r) => z2ui5_cl_util.abap_copy(_r))); } break; case cl_abap_datadescr.typekind_struct1: @@ -1005,7 +1020,7 @@ class z2ui5_cl_sample_context { } if (ls_attri.name === `ITEM`) { lt_tab = z2ui5_cl_sample_context.msg_get_internal({ val: fs_comp }); - result.push(...lt_tab); + result.push(...lt_tab.map((_r) => z2ui5_cl_util.abap_copy(_r))); return result; } else { ls_result = z2ui5_cl_sample_context.msg_map({ name: ls_attri.name, val: fs_comp, is_msg: ls_result }); @@ -1015,14 +1030,14 @@ class z2ui5_cl_sample_context { ls_result.id = ls_result.id.toUpperCase(); // TODO(abap2js): MESSAGE ID ls_result-id TYPE `I` NUMBER ls_result-no WITH ls_result-v1 ls_result-v2 ls_result-v3 ls_result-v4 INTO ls_result-text. } - result.push(ls_result); + result.push(z2ui5_cl_util.abap_copy(ls_result)); break; case cl_abap_datadescr.typekind_oref: result = z2ui5_cl_sample_context.msg_get_by_oref({ val: val }); break; default: if (z2ui5_cl_sample_context.rtti_check_clike({ val: val })) { - result.push({ text: val, id: ``, no: ``, type: ``, v1: ``, v2: ``, v3: ``, v4: ``, timestampl: null, t_meta: [] }); + result.push(z2ui5_cl_util.abap_copy({ text: val, id: ``, no: ``, type: ``, v1: ``, v2: ``, v3: ``, v4: ``, timestampl: 0, t_meta: [] })); } break; } @@ -1057,7 +1072,7 @@ class z2ui5_cl_sample_context { } ls_result = z2ui5_cl_sample_context.msg_map({ name: ls_attri_o.name, val: fs_comp, is_msg: ls_result }); } - result.push(ls_result); + result.push(z2ui5_cl_util.abap_copy(ls_result)); } catch (error) { let obj = null; obj = z2ui5_cl_util.abap_copy(val); @@ -1067,14 +1082,14 @@ class z2ui5_cl_sample_context { // TODO(abap2js): ASSIGN lr_tab->* TO FIELD-SYMBOL(). // TODO(abap2js): CALL METHOD obj->(`IF_BALI_LOG~GET_ALL_ITEMS`) RECEIVING item_table = . lt_tab2 = z2ui5_cl_sample_context.msg_get_internal({ val: fs_tab2 }); - result.push(...lt_tab2); + result.push(...lt_tab2.map((_r) => z2ui5_cl_util.abap_copy(_r))); } catch (error) { try { // TODO(abap2js): CREATE DATA lr_tab TYPE (`BAPIRETTAB`). // TODO(abap2js): ASSIGN lr_tab->* TO . // TODO(abap2js): CALL METHOD obj->(`ZIF_LOGGER~EXPORT_TO_TABLE`) RECEIVING rt_bapiret = . lt_tab2 = z2ui5_cl_sample_context.msg_get_internal({ val: fs_tab2 }); - result.push(...lt_tab2); + result.push(...lt_tab2.map((_r) => z2ui5_cl_util.abap_copy(_r))); } catch (error) { lt_attri_o = z2ui5_cl_sample_context.rtti_get_t_attri_by_oref({ val: val }); sy_tabix = 0; @@ -1088,7 +1103,7 @@ class z2ui5_cl_sample_context { } ls_result = z2ui5_cl_sample_context.msg_map({ name: ls_attri_o.name, val: fs_comp, is_msg: ls_result }); } - result.push(ls_result); + result.push(z2ui5_cl_util.abap_copy(ls_result)); } } } @@ -1207,7 +1222,9 @@ class z2ui5_cl_sample_context { if (lv_kind !== cl_abap_datadescr.typekind_struct1 && lv_kind !== cl_abap_datadescr.typekind_struct2) { return result; } - // TODO(abap2js): msg_get_rap_row( EXPORTING val = val entity_name = entity_name IMPORTING messages = result is_row = DATA(lv_is_row) ). + const _out0 = { val, entity_name, messages: result, is_row: this.data(lv_is_row) }; + z2ui5_cl_sample_context.msg_get_rap_row(_out0); + if ("messages" in _out0) result = _out0.messages; if ((lv_is_row === true || lv_is_row === `X`)) { return result; } @@ -1223,27 +1240,28 @@ class z2ui5_cl_sample_context { fs_ftab = fs_tab; _fs$fs_ftab = null; sy_subrc = 0; - const _sy_tabix_1 = sy_tabix; + const _sy_tabix_2 = sy_tabix; sy_tabix = 0; for (const symbol of fs_ftab) { sy_tabix++; if (z2ui5_cl_sample_context.rtti_get_type_kind({ val: fs_row }) === cl_abap_datadescr.typekind_oref) { if (fs_row) { try { - result.push(...z2ui5_cl_sample_context.msg_get_t(fs_row)); + result.push(...z2ui5_cl_sample_context.msg_get_t(fs_row).map((_r) => z2ui5_cl_util.abap_copy(_r))); } catch (error) { } } } else { - result.push(...z2ui5_cl_sample_context.msg_get_rap({ val: fs_row, entity_name: ls_attri.name })); + result.push(...z2ui5_cl_sample_context.msg_get_rap({ val: fs_row, entity_name: ls_attri.name }).map((_r) => z2ui5_cl_util.abap_copy(_r))); } } - sy_tabix = _sy_tabix_1; + sy_tabix = _sy_tabix_2; } return result; } - static msg_get_rap_row({ val, entity_name, messages, is_row } = {}) { + static msg_get_rap_row(_args = {}) { + let { val, entity_name, messages, is_row } = _args; let sy_tabix = 0; let sy_subrc = 0; let fs_msg = null; @@ -1270,7 +1288,7 @@ class z2ui5_cl_sample_context { sy_tabix++; fs_m.t_meta = z2ui5_cl_util.abap_copy(lt_meta); } - messages.push(...lt_one); + messages.push(...lt_one.map((_r) => z2ui5_cl_util.abap_copy(_r))); } catch (error) { } } @@ -1290,9 +1308,10 @@ class z2ui5_cl_sample_context { if (entity_name) { lv_text = `${entity_name}: ${lv_text}`; } - messages.push({ type: `E`, text: lv_text, t_meta: lt_meta }); + messages.push(z2ui5_cl_util.abap_copy({ type: `E`, text: lv_text, t_meta: lt_meta })); } } + Object.assign(_args, { messages, is_row }); } static msg_get_rap_element({ val } = {}) { @@ -1450,27 +1469,27 @@ class z2ui5_cl_sample_context { let lv = ``; lv = z2ui5_cl_sample_context.msg_get_rap_element({ val: val }); if (lv) { - result.push({ n: `element`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `element`, v: lv })); } lv = z2ui5_cl_sample_context.msg_get_rap_state_area({ val: val }); if (lv) { - result.push({ n: `state_area`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `state_area`, v: lv })); } lv = z2ui5_cl_sample_context.msg_get_rap_action({ val: val }); if (lv) { - result.push({ n: `action`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `action`, v: lv })); } lv = z2ui5_cl_sample_context.msg_get_rap_pid({ val: val }); if (lv) { - result.push({ n: `pid`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `pid`, v: lv })); } lv = z2ui5_cl_sample_context.msg_get_rap_cid({ val: val }); if (lv) { - result.push({ n: `cid`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `cid`, v: lv })); } lv = z2ui5_cl_sample_context.msg_get_rap_tky({ val: val }); if (lv) { - result.push({ n: `tky`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `tky`, v: lv })); } return result; } diff --git a/run/input/core/srv/z2ui5/00/00/cl_abap_unit_assert.js b/run/input/core/srv/z2ui5/00/00/cl_abap_unit_assert.js index 24126cc..837b9fb 100644 --- a/run/input/core/srv/z2ui5/00/00/cl_abap_unit_assert.js +++ b/run/input/core/srv/z2ui5/00/00/cl_abap_unit_assert.js @@ -22,9 +22,74 @@ function isInitial(v) { (typeof v === "object" && !Array.isArray(v) && v.constructor === Object && !Object.keys(v).length); } +// ABAP-typed equality. Transpiled fixtures lose ABAP's MOVE conversions +// (e.g. `-index = ' '` into TYPE i yields 0, not the string), so a +// strict JSON comparison would flag pairs ABAP considers equal. Rules: +// - strict equality wins, +// - undefined/null equal any "initial" scalar ('', 0, false, blanks), +// - number vs numeric string compare numerically ("8 " ≙ 8, "" ≙ 0), +// - boolean vs string compares via the abap_bool chars ('X'/'' — and the +// transpiled 'true'/'false' words), +// - arrays compare element-wise; ajson node-row arrays (rows with +// path/name/type) are compared with sorted-table semantics (both sides +// ordered by path+name, as the ABAP sorted tables under comparison are), +// - plain objects compare per key over the union of keys. +function isInitialScalar(v) { + return v === undefined || v === null || v === false || v === 0 || + (typeof v === "string" && v.trim() === ""); +} +function abapEquals(act, exp) { + if (act === exp) return true; + const aArr = Array.isArray(act); + const eArr = Array.isArray(exp); + if (aArr && eArr) { + if (act.length !== exp.length) return false; + let a = act; + let e = exp; + const nodeRow = (r) => r !== null && typeof r === "object" && "path" in r && "name" in r && "type" in r; + if (act.length && act.every(nodeRow) && exp.every(nodeRow)) { + const byKey = (x, y) => + String(x.path ?? "") < String(y.path ?? "") ? -1 : + String(x.path ?? "") > String(y.path ?? "") ? 1 : + String(x.name ?? "") < String(y.name ?? "") ? -1 : + String(x.name ?? "") > String(y.name ?? "") ? 1 : 0; + a = [...act].sort(byKey); + e = [...exp].sort(byKey); + } + return a.every((row, i) => abapEquals(row, e[i])); + } + if (aArr !== eArr) { + // an empty table equals an initial value + return (aArr ? act.length === 0 : exp.length === 0) && isInitialScalar(aArr ? exp : act); + } + const aObj = act !== null && typeof act === "object"; + const eObj = exp !== null && typeof exp === "object"; + if (aObj && eObj) { + const keys = new Set([...Object.keys(act), ...Object.keys(exp)]); + for (const k of keys) if (!abapEquals(act[k], exp[k])) return false; + return true; + } + if (aObj || eObj) return false; + // scalars of differing type/value — apply ABAP MOVE-style coercions + if (isInitialScalar(act) && isInitialScalar(exp)) return true; + const pair = [act, exp]; + const numSide = pair.find((v) => typeof v === "number"); + const strSide = pair.find((v) => typeof v === "string"); + const boolSide = pair.find((v) => typeof v === "boolean"); + if (numSide !== undefined && strSide !== undefined) { + const n = Number(strSide.trim() === "" ? 0 : strSide.trim()); + return !Number.isNaN(n) && n === numSide; + } + if (boolSide !== undefined && strSide !== undefined) { + const s = strSide.trim(); + return boolSide ? s === "X" || s === "true" : s === "" || s === "false"; + } + return false; +} + class cl_abap_unit_assert { static assert_equals({ act, exp, msg } = {}) { - if (JSON.stringify(act) !== JSON.stringify(exp)) fail(msg, act, exp); + if (JSON.stringify(act) !== JSON.stringify(exp) && !abapEquals(act, exp)) fail(msg, act, exp); return true; } static assert_differs({ act, exp, msg } = {}) { @@ -38,6 +103,17 @@ class cl_abap_unit_assert { static assert_bound(arg) { const act = actOf(arg); if (act === undefined || act === null) fail(msgOf(arg), act); return true; } static assert_not_bound(arg) { const act = actOf(arg); if (act !== undefined && act !== null) fail(msgOf(arg), act); return true; } static fail(arg) { fail(typeof arg === "string" ? arg : arg?.msg); } + // ABAP CP pattern: '*' any sequence, '+' any single char, case-insensitive + static assert_char_cp({ act, exp, msg } = {}) { + const rx = new RegExp(`^${String(exp ?? "").replace(/[.^${}()|[\]\\?]/g, "\\$&").replace(/\*/g, ".*").replace(/\+/g, ".")}$`, "is"); + if (!rx.test(String(act ?? ""))) fail(msg, act, exp); + return true; + } + static assert_char_np({ act, exp, msg } = {}) { + const rx = new RegExp(`^${String(exp ?? "").replace(/[.^${}()|[\]\\?]/g, "\\$&").replace(/\*/g, ".*").replace(/\+/g, ".")}$`, "is"); + if (rx.test(String(act ?? ""))) fail(msg, act, exp); + return true; + } } module.exports = cl_abap_unit_assert; diff --git a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson.js b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson.js index 205ec50..1b2d5da 100644 --- a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson.js +++ b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson.js @@ -1,593 +1,1737 @@ +// Hand-port of z2ui5_cl_ajson (upstream sbcgua/ajson clone) — the JSON +// node-tree engine. The ABAP class pool keeps its logic in local classes +// (lcl_utils, lcl_json_parser, lcl_json_serializer, lcl_json_to_abap, +// lcl_abap_to_json, filter/mapper/mutator runners) — they are ported +// in-file below, ABAP-faithful but JS-idiomatic: +// +// - the SXML-based lcl_json_parser is replaced by a small recursive-descent +// JSON parser that produces the same node table and reports parse errors +// with the same "Line N, Offset M" locations, +// - mt_json_tree is a plain array of node rows { path, name, type, value, +// index, order, children }; ABAP's sorted-table semantics are recreated +// where they matter (serializer/members/... sort by their table key), +// - ABAP typed containers do not exist in JS: lcl_abap_to_json converts by +// value shape (string/number/boolean/'X'/array/plain object/ajson +// instance), lcl_json_to_abap fills arrays/objects in place, +// - xstrings are modeled as upper-case hex strings (ecosystem convention). +// +// The file exports only z2ui5_cl_ajson; the locals are attached as +// z2ui5_cl_ajson.__locals so the transpiled upstream unit tests (which +// exercise the ABAP locals directly) can bind to the hand-ported ones. +"use strict"; + const z2ui5_cx_ajson_error = require("abap2UI5/z2ui5_cx_ajson_error"); const z2ui5_if_ajson_types = require("abap2UI5/z2ui5_if_ajson_types"); +const z2ui5_if_ajson_filter = require("abap2UI5/z2ui5_if_ajson_filter"); -class z2ui5_cl_ajson { - static go_float_regex = null; +const NT = z2ui5_if_ajson_types.node_type; // { boolean:'bool', string:'str', number:'num', null:'null', array:'array', object:'object' } - ms_opts = null; - mi_custom_mapping = null; +// --------------------------------------------------------------------------- +// shared helpers +// --------------------------------------------------------------------------- - constructor({ iv_keep_item_order = false, iv_format_datetime = true, iv_to_abap_corresponding_only = false } = {}) { - this.ms_opts.keep_item_order = iv_keep_item_order; - this.ms_opts.to_abap_corresponding_only = iv_to_abap_corresponding_only; - this.format_datetime(iv_format_datetime); +/** ABAP boolean truthiness — transpiled code carries true/'X' */ +function isTrue(v) { + return v === true || v === "X"; +} + +/** ABAP IS INITIAL */ +function isInitial(v) { + if (v === undefined || v === null || v === "" || v === 0 || v === false) return true; + if (Array.isArray(v)) return v.length === 0; + if (typeof v === "object" && (v.constructor === Object || !v.constructor)) return Object.keys(v).length === 0; + return false; +} + +/** numeric read of a node column that may arrive as string (test fixtures) */ +function num(v) { + if (typeof v === "number") return v; + const s = String(v ?? "").trim(); + if (!s) return 0; + const n = Number(s); + return Number.isNaN(n) ? 0 : n; +} + +/** a fresh node row (ABAP ty_node initial) */ +function newNode(over) { + return { path: "", name: "", type: "", value: "", index: 0, order: 0, children: 0, ...over }; +} + +/** ABAP-call convention: accept positional scalar or named-args object */ +function namedArgs(arg, key) { + if (arg !== null && typeof arg === "object" && !Array.isArray(arg) && Object.getPrototypeOf(arg) === Object.prototype) { + return arg; } + return { [key]: arg }; +} + +const byPrimaryKey = (a, b) => { + const ap = String(a.path ?? ""), bp = String(b.path ?? ""); + if (ap !== bp) return ap < bp ? -1 : 1; + const an = String(a.name ?? ""), bn = String(b.name ?? ""); + return an < bn ? -1 : an > bn ? 1 : 0; +}; +const byArrayIndex = (a, b) => num(a.index) - num(b.index) || byPrimaryKey(a, b); +const byItemOrder = (a, b) => num(a.order) - num(b.order) || byPrimaryKey(a, b); + +/** rows of one parent path, sorted by the given table key */ +function rowsOfPath(tree, path, cmp) { + return tree.filter((r) => String(r.path ?? "") === path).sort(cmp); +} + +// --------------------------------------------------------------------------- +// lcl_utils +// --------------------------------------------------------------------------- - static create_empty({ ii_custom_mapping, iv_keep_item_order = false, iv_format_datetime = true, iv_to_abap_corresponding_only = false } = {}) { - let ro_instance = null; - ro_instance = null; // TODO(abap2js): CREATE OBJECT ro_instance EXPORTING iv_to_abap_corresponding_only = iv_to_abap_corresponding_only iv_format_datetime = iv_format_datetime iv_keep_item_order = iv_keep_item_order. - ro_instance.mi_custom_mapping = ii_custom_mapping; - return ro_instance; +class lcl_utils { + static normalize_path(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + let path = String(iv_path ?? ""); + if (path.length === 0) path = "/"; + if (path[0] !== "/") path = "/" + path; + if (path[path.length - 1] !== "/") path = path + "/"; + return path; } - static create_from({ ii_source_json, ii_filter, ii_mapper } = {}) { - let ro_instance = null; - let lo_mutator_queue = null; - if (ii_source_json != null) { - z2ui5_cx_ajson_error.raise(`Source not bound`); + static split_path(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const path = String(iv_path ?? ""); + const len = path.length; + if (len === 0 || path === "/") { + return { path: "", name: "" }; // empty path is the alias for root item } - ro_instance = null; // TODO(abap2js): CREATE OBJECT ro_instance EXPORTING iv_to_abap_corresponding_only = ii_source_json->opts( )-to_abap_corresponding_only iv_format_datetime = ii_source_json->opts( )-format_datetime iv_keep_item_order = ii_source_json->opts( )-keep_item_order. - if (ii_filter != null && ii_mapper != null) { - ro_instance.mt_json_tree = ii_source_json.mt_json_tree; - } else { - lo_mutator_queue = null; // TODO(abap2js): CREATE OBJECT lo_mutator_queue. - if (ii_mapper != null) { - lo_mutator_queue.add(lcl_mapper_runner.new_(ii_mapper)); - } - if (ii_filter != null) { - lo_mutator_queue.add(lcl_filter_runner.new_(ii_filter)); - } - // TODO(abap2js): lo_mutator_queue->lif_mutator_runner~run( EXPORTING it_source_tree = ii_source_json->mt_json_tree IMPORTING et_dest_tree = ro_instance->mt_json_tree ). + const trimSlash = path[len - 1] === "/" ? 1 : 0; + // offset of the last '/' before the (optionally trailing-slash-stripped) name + let offs = [...path].reverse().join("").indexOf("/", trimSlash); + if (offs === -1) offs = len; // treat whole string as the 'name' part + offs = len - offs; + const name = path.slice(offs, len - trimSlash).replace(/\t/g, "/"); // tabs stand in for slashes in names + return { + path: lcl_utils.normalize_path(path.slice(0, offs)), + name, + }; + } + + static validate_array_index({ iv_path, iv_index } = {}) { + const s = String(iv_index ?? ""); + if (!/^[0-9]+$/.test(s)) { + z2ui5_cx_ajson_error.raise(`Cannot add non-numeric key [${s}] to array [${iv_path}]`); + } + const index = Number(s); + if (index === 0) { + z2ui5_cx_ajson_error.raise(`Cannot add zero key to array [${iv_path}]`); } - return ro_instance; + return index; } - delete_subtree({ iv_path, iv_name, ir_parent } = {}) { - let rs_top_node = null; - let lv_parent_path = ``; - let lr_parent = null; - // TODO(abap2js): READ TABLE mt_json_tree INTO rs_top_node WITH TABLE KEY path = iv_path name = iv_name. - if (sy_subrc !== 0) { - return rs_top_node; - } - // TODO(abap2js): DELETE mt_json_tree INDEX sy-tabix. - if (rs_top_node.children > 0) { - lv_parent_path = iv_path + iv_name + `/*`; - for (let _i = mt_json_tree.length - 1; _i >= 0; _i--) { const row = mt_json_tree[_i]; if (String(row.path).includes(String(lv_parent_path).replace(/\*/g, ""))) mt_json_tree.splice(_i, 1); } - } - if (ir_parent !== undefined) { - ir_parent.children = ir_parent.children - 1; - } else { - lr_parent = this.get_item({ iv_path: iv_path }); - if (lr_parent) { - lr_parent.children = lr_parent.children - 1; - } + static string_to_xstring_utf8(arg) { + const { iv_str } = namedArgs(arg, "iv_str"); + return Buffer.from(String(iv_str ?? ""), "utf8").toString("hex").toUpperCase(); + } + + static xstring_to_string_utf8(arg) { + const { iv_xstr } = namedArgs(arg, "iv_xstr"); + return Buffer.from(String(iv_xstr ?? ""), "hex").toString("utf8"); + } + + static sanity_check(arg) { + const { iv_data } = namedArgs(arg, "iv_data"); + if (!/^\s*(true|false|null|-?\d|"|\{|\[)/.test(String(iv_data ?? ""))) { + z2ui5_cx_ajson_error.raise({ + iv_msg: `Json parsing error: Not JSON`, + iv_location: `Line 1, Offset 1`, + }); } - return rs_top_node; } - get_item({ iv_path } = {}) { - let rv_item = null; - let ls_path_name = null; - ls_path_name = lcl_utils.split_path(iv_path); - // TODO(abap2js): READ TABLE mt_json_tree REFERENCE INTO rv_item WITH KEY path = ls_path_name-path name = ls_path_name-name. - return rv_item; + /** JSON input as text: string (JSON or hex-xstring) or string table */ + static any_to_string(arg) { + const { iv_data } = namedArgs(arg, "iv_data"); + if (typeof iv_data === "string") return iv_data; + if (Array.isArray(iv_data)) return iv_data.map((r) => String(r ?? "")).join("\n"); + z2ui5_cx_ajson_error.raise(`Unsupported type of input (must be char, string, string_table, or xstring)`); } - static new({ iv_keep_item_order = false, iv_format_datetime = true, iv_to_abap_corresponding_only = false } = {}) { - let ro_instance = null; - ro_instance = null; // TODO(abap2js): CREATE OBJECT ro_instance EXPORTING iv_to_abap_corresponding_only = iv_to_abap_corresponding_only iv_format_datetime = iv_format_datetime iv_keep_item_order = iv_keep_item_order. - return ro_instance; - } - - static normalize_path({ iv_path } = {}) { - let rv_path = ``; - rv_path = lcl_utils.normalize_path(iv_path); - return rv_path; - } - - static parse({ iv_json, iv_freeze = false, ii_custom_mapping, iv_keep_item_order = false } = {}) { - let ro_instance = null; - let lo_parser = null; - ro_instance = null; // TODO(abap2js): CREATE OBJECT ro_instance. - lo_parser = null; // TODO(abap2js): CREATE OBJECT lo_parser. - ro_instance.mt_json_tree = lo_parser.parse({ iv_json, iv_keep_item_order }); - ro_instance.mi_custom_mapping = ii_custom_mapping; - ro_instance.ms_opts.keep_item_order = iv_keep_item_order; - if (iv_freeze === true) { - ro_instance.freeze(); - } - return ro_instance; - } - - prove_path_exists({ iv_path } = {}) { - let rr_end_node = null; - let lt_path = []; - let lr_node_parent = null; - let lv_cur_path = ``; - let lv_cur_name = ``; - let ls_new_node = null; - lt_path = iv_path.split(`/`); - for (let _i = lt_path.length - 1; _i >= 0; _i--) { const row = lt_path[_i]; if (!row.table_line) lt_path.splice(_i, 1); } - while (true) { - lr_node_parent = rr_end_node; - // TODO(abap2js): READ TABLE mt_json_tree REFERENCE INTO rr_end_node WITH TABLE KEY path = lv_cur_path name = lv_cur_name. - if (sy_subrc !== 0) { - ls_new_node = null; - if (lr_node_parent) { - lr_node_parent.children = lr_node_parent.children + 1; - if (lr_node_parent.type === z2ui5_if_ajson_types.node_type.array) { - ls_new_node.index = lcl_utils.validate_array_index({ iv_path: lv_cur_path, iv_index: lv_cur_name }); + static any_to_xstring(arg) { + const { iv_data } = namedArgs(arg, "iv_data"); + if (typeof iv_data === "string") { + lcl_utils.sanity_check(iv_data); + return lcl_utils.string_to_xstring_utf8(iv_data); + } + if (Array.isArray(iv_data)) { + const s = iv_data.map((r) => String(r ?? "")).join("\n"); + lcl_utils.sanity_check(s); + return lcl_utils.string_to_xstring_utf8(s); + } + z2ui5_cx_ajson_error.raise(`Unsupported type of input (must be char, string, string_table, or xstring)`); + } +} + +// --------------------------------------------------------------------------- +// lcl_json_parser — recursive descent over the JSON text, producing the +// ajson node table with SXML-compatible error locations +// --------------------------------------------------------------------------- + +class lcl_json_parser { + mv_keep_item_order = false; + + parse({ iv_json, iv_keep_item_order = false } = {}) { + this.mv_keep_item_order = isTrue(iv_keep_item_order); + + if (iv_json !== undefined && iv_json !== null && typeof iv_json !== "string" && !Array.isArray(iv_json)) { + z2ui5_cx_ajson_error.raise(`Unsupported type of input (must be char, string, string_table, or xstring)`); + } + + let text; + if (Array.isArray(iv_json)) { + text = iv_json.map((r) => String(r ?? "")).join("\n"); + lcl_utils.sanity_check(text); + return this._parse(text); + } + + text = String(iv_json ?? ""); + try { + lcl_utils.sanity_check(text); + return this._parse(text); + } catch (e) { + // the ABAP parser receives xstrings as a distinct type; in JS an + // xstring is a hex string — if the raw text does not parse, retry the + // hex-decoded content before giving up + if (/^(?:[0-9A-Fa-f]{2})+$/.test(text)) { + try { + const decoded = Buffer.from(text, "hex").toString("utf8"); + lcl_utils.sanity_check(decoded); + return this._parse(decoded); + } catch { + throw e; + } + } + throw e; + } + } + + _parse(text) { + const nodes = []; + if (!text) return nodes; + + let pos = 0; // current read position + let lastTokenEnd = 0; // end of the last consumed token — SXML-style error anchor + + const location = (offset) => { + let off = Math.max(0, Math.min(offset, text.length)); + const upTo = text.slice(0, off).replace(/\r\n/g, "\n"); + const lines = upTo.split("\n"); + const line = Math.max(1, lines.length); + const posInLine = (lines[lines.length - 1] ?? "").length + 1; + return `Line ${line}, Offset ${posInLine}`; + }; + const raise = (msg, offset = lastTokenEnd) => { + z2ui5_cx_ajson_error.raise({ + iv_msg: `Json parsing error: ${msg}`, + iv_location: location(offset), + }); + }; + const skipWs = () => { + while (pos < text.length && /[ \t\r\n]/.test(text[pos])) pos++; + }; + const readString = () => { + // pos is at the opening quote + const start = pos; + pos++; + let out = ""; + for (;;) { + if (pos >= text.length) raise(`Unexpected end of data`, start); + const c = text[pos]; + if (c === '"') { + pos++; + lastTokenEnd = pos; + return out; + } + if (c === "\\") { + pos++; + const e = text[pos]; + if (e === '"' || e === "\\" || e === "/") out += e; + else if (e === "n") out += "\n"; + else if (e === "t") out += "\t"; + else if (e === "r") out += "\r"; + else if (e === "b") out += "\b"; + else if (e === "f") out += "\f"; + else if (e === "u") { + const hex = text.slice(pos + 1, pos + 5); + if (!/^[0-9A-Fa-f]{4}$/.test(hex)) raise(`Invalid unicode escape`, pos); + out += String.fromCharCode(parseInt(hex, 16)); + pos += 4; + } else raise(`Invalid escape sequence`, pos); + pos++; + } else { + out += c; + pos++; + } + } + }; + + const self = this; + + // emit one value at (path, name); returns the created node row + function parseValue(path, name, index, order) { + skipWs(); + if (pos >= text.length) raise(`Unexpected end of data`); + const c = text[pos]; + const node = newNode({ path, name, index, order }); + nodes.push(node); + + if (c === "{") { + pos++; + lastTokenEnd = pos; + node.type = NT.object; + const childPath = path + name.replace(/\//g, "\t") + "/"; + skipWs(); + if (text[pos] === "}") { + pos++; + lastTokenEnd = pos; + return node; + } + for (;;) { + skipWs(); + if (text[pos] !== '"') raise(`Unexpected token, expected member name`); + const memberName = readString(); + if (memberName === "") raise(`Node without name (maybe not JSON)`); + skipWs2Expect(":"); + node.children++; + parseValue(childPath, memberName, 0, self.mv_keep_item_order ? node.children : 0); + skipWs(); + if (text[pos] === ",") { + pos++; + lastTokenEnd = pos; + continue; + } + if (text[pos] === "}") { + pos++; + lastTokenEnd = pos; + return node; } + raise(`Unexpected token, expected ',' or '}'`); } - ls_new_node.path = lv_cur_path; - ls_new_node.name = lv_cur_name; - ls_new_node.type = z2ui5_if_ajson_types.node_type.object; - rr_end_node = ls_new_node; - mt_json_tree.push(rr_end_node); } - lv_cur_path = lv_cur_path + lv_cur_name + `/`; - // TODO(abap2js): READ TABLE lt_path INDEX sy-index INTO lv_cur_name. - if (sy_subrc !== 0) { + + if (c === "[") { + pos++; + lastTokenEnd = pos; + node.type = NT.array; + const childPath = path + name.replace(/\//g, "\t") + "/"; + skipWs(); + if (text[pos] === "]") { + pos++; + lastTokenEnd = pos; + return node; + } + for (;;) { + node.children++; + parseValue(childPath, `${node.children}`, node.children, 0); + skipWs(); + if (text[pos] === ",") { + pos++; + lastTokenEnd = pos; + continue; + } + if (text[pos] === "]") { + pos++; + lastTokenEnd = pos; + return node; + } + raise(`Unexpected token, expected ',' or ']'`); + } + } + + if (c === '"') { + node.type = NT.string; + node.value = readString(); + return node; + } + + const rest = text.slice(pos); + let m; + if ((m = rest.match(/^true\b/))) { + node.type = NT.boolean; + node.value = "true"; + pos += 4; + lastTokenEnd = pos; + return node; + } + if ((m = rest.match(/^false\b/))) { + node.type = NT.boolean; + node.value = "false"; + pos += 5; + lastTokenEnd = pos; + return node; + } + if ((m = rest.match(/^null\b/))) { + node.type = NT.null; + node.value = ""; + pos += 4; + lastTokenEnd = pos; + return node; + } + if ((m = rest.match(/^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/))) { + node.type = NT.number; + node.value = m[0]; + pos += m[0].length; + lastTokenEnd = pos; + return node; + } + raise(`Unexpected token`, pos); + } + + function skipWs2Expect(ch) { + const anchor = lastTokenEnd; + skipWs(); + if (text[pos] !== ch) raise(`Unexpected token, expected '${ch}'`, anchor); + pos++; + lastTokenEnd = pos; + } + + parseValue("", "", 0, 0); + skipWs(); + if (pos < text.length) raise(`Unexpected trailing content`, pos); + + return nodes; + } +} + +// --------------------------------------------------------------------------- +// lcl_json_serializer +// --------------------------------------------------------------------------- + +class lcl_json_serializer { + mt_json_tree = []; + mv_keep_item_order = false; + mt_buffer = []; + mv_indent_step = 0; + mv_level = 0; + + static stringify({ it_json_tree, iv_indent = 0, iv_keep_item_order = false } = {}) { + const lo = new lcl_json_serializer(); + lo.mt_json_tree = it_json_tree || []; + lo.mv_indent_step = num(iv_indent); + lo.mv_keep_item_order = isTrue(iv_keep_item_order); + return lo._stringify(); + } + + _stringify() { + const root = this.mt_json_tree.find((n) => String(n.path ?? "") === "" && String(n.name ?? "") === ""); + if (!root) return ""; + this.stringify_node({ is_node: root }); + return this.mt_buffer.join(""); + } + + stringify_node({ is_node } = {}) { + let item = ""; + let indentPrefix = ""; + if (this.mv_indent_step > 0) { + indentPrefix = " ".repeat(this.mv_indent_step * this.mv_level); + item = indentPrefix; + } + + const name = String(is_node.name ?? ""); + if (name !== "" && num(is_node.index) === 0) { + // not root, not array item + item += this.mv_indent_step > 0 ? `"${name}": ` : `"${name}":`; + } + + switch (is_node.type) { + case NT.array: + item += "["; break; + case NT.object: + item += "{"; + break; + case NT.string: + item += `"${lcl_json_serializer.escape_string({ iv_unescaped: String(is_node.value ?? "") })}"`; + break; + case NT.boolean: + case NT.number: + item += String(is_node.value ?? ""); + break; + case NT.null: + item += "null"; + break; + default: + z2ui5_cx_ajson_error.raise({ + iv_msg: `Unexpected type [${is_node.type}]`, + iv_location: String(is_node.path ?? "") + name, + }); + } + + const children = num(is_node.children); + const isComplex = is_node.type === NT.array || is_node.type === NT.object; + + if (this.mv_indent_step > 0 && isComplex && children > 0) { + this.mv_level++; + item += "\n"; + } + + this.mt_buffer.push(item); + + if (isComplex) { + const childrenPath = String(is_node.path ?? "") + name + "/"; // root: '' + '' + '/' = '/' + if (children > 0) { + this.stringify_set({ iv_parent_path: childrenPath, iv_array: is_node.type === NT.array }); } + let tail = is_node.type === NT.array ? "]" : "}"; + if (this.mv_indent_step > 0 && children > 0) { + tail = indentPrefix + tail; + this.mv_level--; + } + this.mt_buffer.push(tail); } - return rr_end_node; } - read_only_watchdog() { - if (this.ms_opts.read_only === true) { - z2ui5_cx_ajson_error.raise(`This json instance is read only`); + stringify_set({ iv_parent_path, iv_array } = {}) { + // ABAP iterates the sorted table by the fitting secondary key + const cmp = isTrue(iv_array) ? byArrayIndex : this.mv_keep_item_order ? byItemOrder : byPrimaryKey; + const rows = rowsOfPath(this.mt_json_tree, iv_parent_path, cmp); + let firstDone = false; + for (const row of rows) { + if (!firstDone) firstDone = true; + else this.mt_buffer.push(this.mv_indent_step > 0 ? ",\n" : ","); + this.stringify_node({ is_node: row }); } + if (this.mv_indent_step > 0 && firstDone) this.mt_buffer.push("\n"); } - array_to_string_table() { - let sy_tabix = 0; - let lv_normalized_path = ``; - let lr_node = null; - // TODO(abap2js): FIELD-SYMBOLS LIKE LINE OF mt_json_tree. - lv_normalized_path = lcl_utils.normalize_path(iv_path); - lr_node = this.get_item({ iv_path: iv_path }); - if (!lr_node) { - z2ui5_cx_ajson_error.raise(`Path not found: ${iv_path}`); + static escape_string({ iv_unescaped } = {}) { + let s = String(iv_unescaped ?? ""); + if (/["\\\t\n\r]/.test(s)) { + s = s + .replace(/\\/g, "\\\\") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/\t/g, "\\t") + .replace(/"/g, '\\"'); } - if (lr_node.type !== z2ui5_if_ajson_types.node_type.array) { - z2ui5_cx_ajson_error.raise(`Array expected at: ${iv_path}`); + return s; + } +} + +// --------------------------------------------------------------------------- +// lcl_json_to_abap — fills JS containers (array / plain object) in place; +// ABAP's RTTI-driven typed assignment has no JS counterpart, values keep +// their JSON-derived native types (string/number/boolean/null) +// --------------------------------------------------------------------------- + +class lcl_json_to_abap { + mi_custom_mapping = null; + mi_refs_initiator = null; + mv_corresponding = false; + mr_nodes = []; + + constructor({ iv_corresponding = false, ii_custom_mapping = null, ii_refs_initiator = null } = {}) { + this.mi_custom_mapping = ii_custom_mapping ?? null; + this.mi_refs_initiator = ii_refs_initiator ?? null; + this.mv_corresponding = isTrue(iv_corresponding); + } + + to_abap(args = {}) { + const { it_nodes, c_container } = args; + this.mr_nodes = it_nodes || []; + const root = this.mr_nodes.find((n) => String(n.path ?? "") === "" && String(n.name ?? "") === ""); + + let result; + if (!root) { + // CLEAR c_container + if (Array.isArray(c_container)) c_container.length = 0; + result = c_container; + } else { + result = this.build_value(root, c_container); } - sy_tabix = 0; - for (const item of mt_json_tree) { - sy_tabix++; - if (!(item.path === lv_normalized_path)) continue; - switch (item.type) { - case z2ui5_if_ajson_types.node_type.number: - case z2ui5_if_ajson_types.node_type.string: - rt_string_table.push(item.value); - break; - case z2ui5_if_ajson_types.node_type.null: - rt_string_table.push(``); - break; - case z2ui5_if_ajson_types.node_type.boolean: - let lv_tmp = ``; - if (item.value === `true`) { - lv_tmp = true; - } else { - lv_tmp = null; + // CHANGING c_container — sync onto the args object for scalar write-back + if (args !== null && typeof args === "object") args.c_container = result; + return result; + } + + /** + * Recursively build the JS value for a node. `container` carries the + * (possibly typed) target — arrays/objects fill in place, primitives + * steer ABAP-like type checks. null/undefined container = free-form. + */ + build_value(node, container) { + const childPath = String(node.path ?? "") + String(node.name ?? "") + "/"; + switch (node.type) { + case NT.object: { + const isPlain = container !== null && container !== undefined && typeof container === "object" && !Array.isArray(container); + if (container !== null && container !== undefined && !isPlain) { + z2ui5_cx_ajson_error.raise(`Expected structure`); + } + const out = isPlain ? container : {}; + const typed = isPlain && Object.keys(out).length > 0; // struct with declared components + for (const child of rowsOfPath(this.mr_nodes, childPath, byPrimaryKey)) { + let field = String(child.name ?? ""); + if (this.mi_custom_mapping) { + const mapped = this.mi_custom_mapping.to_abap({ iv_path: child.path, iv_name: child.name }); + if (!isInitial(mapped)) field = String(mapped); } - rt_string_table.push(lv_tmp); - break; - default: - z2ui5_cx_ajson_error.raise(`Cannot convert [${item.type}] to string at [${item.path}${item.name}]`); - break; + if (!(field in out) && field.toLowerCase() in out) field = field.toLowerCase(); + if (typed && !(field in out)) { + if (this.mv_corresponding) continue; + z2ui5_cx_ajson_error.raise(`Path not found`); + } + const cur = field in out ? out[field] : undefined; + if (cur === null && this.mi_refs_initiator) { + // REF TO data fields resolve through the refs initiator and are + // filled in place (JS refs alias arrays/objects naturally) + const ref = this.get_data_ref(child); + out[field] = this.build_value(child, ref); + continue; + } + out[field] = this.build_value(child, cur); + } + return out; + } + case NT.array: { + if (container !== null && container !== undefined && !Array.isArray(container) && typeof container === "object") { + // plain object container for an array node + z2ui5_cx_ajson_error.raise(`Expected table`); + } + if (container !== null && container !== undefined && !Array.isArray(container)) { + z2ui5_cx_ajson_error.raise(`Expected table`); + } + const out = Array.isArray(container) ? container : []; + out.length = 0; + for (const child of rowsOfPath(this.mr_nodes, childPath, byArrayIndex)) { + out.push(this.build_value(child, undefined)); + } + return out; } + case NT.string: { + if (typeof container === "object" && container !== null) { + z2ui5_cx_ajson_error.raise(Array.isArray(container) ? `Expected table` : `Expected structure`); + } + const s = String(node.value ?? ""); + if (typeof container === "number") { + // typed numeric target — ABAP timestamps are packed numbers, so a + // timestamp-shaped string converts via to_timestamp + if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(s)) return this.to_timestamp(s); + const n = Number(s); + if (Number.isNaN(n)) z2ui5_cx_ajson_error.raise(`Source is not a number`); + return n; + } + return s; + } + case NT.number: + if (typeof container === "object" && container !== null) { + z2ui5_cx_ajson_error.raise(Array.isArray(container) ? `Expected table` : `Expected structure`); + } + if (typeof container === "string") return String(node.value ?? ""); + return num(node.value); + case NT.boolean: + return String(node.value) === "true"; + case NT.null: + // ABAP: null leaves the target initial + return container !== undefined ? container : null; + default: + z2ui5_cx_ajson_error.raise({ + iv_msg: `Unexpected JSON type [${node.type}]`, + iv_location: String(node.path ?? "") + String(node.name ?? ""), + }); } } - clear() { - this.read_only_watchdog(); - mt_json_tree = null; + get_data_ref(is_node) { + if (!this.mi_refs_initiator) { + z2ui5_cx_ajson_error.raise(`Missing ref initiator`); + } + const ref = this.mi_refs_initiator.get_data_ref(is_node); + if (ref === null || ref === undefined) { + z2ui5_cx_ajson_error.raise(`Cannot use initial data ref`); + } + return ref; } - clone() { - ri_json = z2ui5_cl_ajson.create_from({ ii_source_json: this }); + to_date(iv_value) { + const { iv_value: v } = namedArgs(iv_value, "iv_value"); + const m = String(v ?? "").match(/^(\d{4})-(\d{2})-(\d{2})(T|$)/); + if (!m) z2ui5_cx_ajson_error.raise(`Unexpected date format`); + return `${m[1]}${m[2]}${m[3]}`; } - delete() { - this.read_only_watchdog(); - let ls_split_path = null; - ls_split_path = lcl_utils.split_path(iv_path); - this.delete_subtree({ iv_path: ls_split_path.path, iv_name: ls_split_path.name }); - ri_json = this; + to_time(iv_value) { + const { iv_value: v } = namedArgs(iv_value, "iv_value"); + const m = String(v ?? "").match(/^(\d{2}):(\d{2}):(\d{2})(T|$)/); + if (!m) z2ui5_cx_ajson_error.raise(`Unexpected time format`); + return `${m[1]}${m[2]}${m[3]}`; + } + + to_timestamp(iv_value) { + const { iv_value: v } = namedArgs(iv_value, "iv_value"); + const tsl = this.to_timestampl(v); + const [intPart, fracPart] = String(tsl).split("."); + // short timestamp must not have any fraction (.000 is acceptable) + if (fracPart && /[1-9]/.test(fracPart)) { + z2ui5_cx_ajson_error.raise(`Unexpected timestamp format`); + } + return Number(intPart); } - exists() { - rv_exists = Boolean(this.get_item({ iv_path: iv_path })); + to_timestampl(iv_value) { + const { iv_value: v } = namedArgs(iv_value, "iv_value"); + const s = String(v ?? ""); + const withHour = s.match(/^(\d{4})-(\d{2})-(\d{2})(T)(\d{2}):(\d{2}):(\d{2})(\+)(\d{2}):(\d{2})/); + const utc = withHour ? null : s.match(/^(\d{4})-(\d{2})-(\d{2})(T)(\d{2}):(\d{2}):(\d{2})(\.\d+)?(Z|$)/); + if (!withHour && !utc) { + z2ui5_cx_ajson_error.raise(`Unexpected timestamp format`); + } + const m = withHour || utc; + const [y, mo, d, h, mi, sec] = [m[1], m[2], m[3], m[5], m[6], m[7]].map(Number); + let frac = withHour ? "" : (m[8] ?? ""); + if (y === 0 && mo === 0 && d === 0) return 0; // 0000-00-00T00:00:00Z stays initial + let epoch = Date.UTC(y, mo - 1, d, h, mi, sec); + if (withHour) { + // local time with +hh:mm offset → subtract the offset to get UTC + const offSec = Number(m[9]) * 3600 + Number(m[10]) * 60; + epoch -= offSec * 1000; + } + const dt = new Date(epoch); + const p2 = (n) => String(n).padStart(2, "0"); + const stamp = + `${String(dt.getUTCFullYear()).padStart(4, "0")}${p2(dt.getUTCMonth() + 1)}${p2(dt.getUTCDate())}` + + `${p2(dt.getUTCHours())}${p2(dt.getUTCMinutes())}${p2(dt.getUTCSeconds())}`; + if (frac && frac !== ".") return Number(`${stamp}${frac}`); + return Number(stamp); } +} + +// --------------------------------------------------------------------------- +// lcl_abap_to_json — converts JS-native data to node rows by shape +// --------------------------------------------------------------------------- + +class lcl_abap_to_json { + mi_custom_mapping = null; + mv_keep_item_order = false; + mv_format_datetime = false; + mv_raw_string = false; // TYPE string semantics — no abap_bool/utclong shape reads - filter() { - ri_json = z2ui5_cl_ajson.create_from({ ii_source_json: this, ii_filter }); + static convert({ iv_data, is_prefix, iv_array_index = 0, ii_custom_mapping = null, is_opts = {}, iv_item_order = 0 } = {}) { + const lo = new lcl_abap_to_json(); + lo.mi_custom_mapping = ii_custom_mapping ?? null; + lo.mv_keep_item_order = isTrue(is_opts?.keep_item_order); + lo.mv_format_datetime = isTrue(is_opts?.format_datetime); + lo.mv_raw_string = isTrue(is_opts?.raw_string); + const nodes = []; + lo.convert_any({ + iv_data, + is_prefix: is_prefix || { path: "", name: "" }, + iv_index: iv_array_index, + iv_item_order, + ct_nodes: nodes, + }); + return nodes; } - format_datetime() { - this.ms_opts.format_datetime = iv_use_iso; - ri_json = this; + static insert_with_type({ iv_data, iv_type, is_prefix, iv_array_index = 0, ii_custom_mapping = null, is_opts = {}, iv_item_order = 0 } = {}) { + const lo = new lcl_abap_to_json(); + lo.mi_custom_mapping = ii_custom_mapping ?? null; + lo.mv_keep_item_order = isTrue(is_opts?.keep_item_order); + lo.mv_format_datetime = isTrue(is_opts?.format_datetime); + const nodes = []; + lo.insert_value_with_type({ + iv_data, + iv_type, + is_prefix: is_prefix || { path: "", name: "" }, + iv_index: iv_array_index, + iv_item_order, + ct_nodes: nodes, + }); + return nodes; } - freeze() { - this.ms_opts.read_only = true; + /** true when the value is an ajson instance (this class or a clone port) */ + static is_ajson(v) { + return ( + v !== null && + typeof v === "object" && + !Array.isArray(v) && + Array.isArray(v.mt_json_tree) && + typeof v.stringify === "function" + ); } - get() { - let lr_item = null; - lr_item = this.get_item({ iv_path: iv_path }); - if (lr_item) { - rv_value = lr_item.value; + convert_any({ iv_data, is_prefix, iv_index = 0, iv_item_order = 0, ct_nodes } = {}) { + if (Array.isArray(iv_data)) { + this.convert_table({ iv_data, is_prefix, iv_index, iv_item_order, ct_nodes }); + } else if (lcl_abap_to_json.is_ajson(iv_data)) { + this.convert_ajson({ io_json: iv_data, is_prefix, iv_index, iv_item_order, ct_nodes }); + } else if (iv_data === null || iv_data === undefined) { + this.convert_ref({ iv_data: null, is_prefix, iv_index, iv_item_order, ct_nodes }); + } else if (typeof iv_data === "object" && (iv_data.constructor === Object || !iv_data.constructor)) { + this.convert_struc({ iv_data, is_prefix, iv_index, iv_item_order, ct_nodes }); + } else if (typeof iv_data === "object" || typeof iv_data === "function") { + z2ui5_cx_ajson_error.raise(`Unsupported type [r] @${is_prefix.path + is_prefix.name}`); + } else { + this.convert_value({ iv_data, is_prefix, iv_index, iv_item_order, ct_nodes }); } } - get_boolean() { - let lr_item = null; - lr_item = this.get_item({ iv_path: iv_path }); - if (!lr_item || lr_item.type === z2ui5_if_ajson_types.node_type.null) { - return; - } else if (lr_item.type === z2ui5_if_ajson_types.node_type.boolean) { - rv_value = Boolean(lr_item.value === `true`); - } else if (lr_item.value) { - rv_value = true; + convert_ajson({ io_json, is_prefix, iv_index = 0, iv_item_order = 0, ct_nodes } = {}) { + if (!io_json) return; + for (const src of io_json.mt_json_tree) { + const dst = { ...src }; + if (isInitial(dst.path) && isInitial(dst.name)) { + // root node + dst.path = is_prefix.path; + dst.name = is_prefix.name; + dst.index = iv_index; + dst.order = iv_item_order; + } else { + dst.path = is_prefix.path + is_prefix.name + dst.path; + } + ct_nodes.push(dst); } } - get_date() { - let lr_item = null; - let lv_y = ``; - let lv_m = ``; - let lv_d = ``; - lr_item = this.get_item({ iv_path: iv_path }); - if (lr_item && lr_item.type === z2ui5_if_ajson_types.node_type.string) { - // TODO(abap2js): FIND FIRST OCCURRENCE OF REGEX '^(\d{4})-(\d{2})-(\d{2})(T|$)' IN lr_item->value SUBMATCHES lv_y lv_m lv_d . - rv_value = [lv_y, lv_m, lv_d].join(``); + apply_mapping(is_prefix) { + let name = is_prefix.name; + if (this.mi_custom_mapping) { + const mapped = this.mi_custom_mapping.to_json({ iv_path: is_prefix.path, iv_name: is_prefix.name }); + if (!isInitial(mapped)) name = mapped; } + return name; } - get_integer() { - let lr_item = null; - lr_item = this.get_item({ iv_path: iv_path }); - if (lr_item && lr_item.type === z2ui5_if_ajson_types.node_type.number) { - rv_value = lr_item.value; + convert_value({ iv_data, is_prefix, iv_index = 0, iv_item_order = 0, ct_nodes } = {}) { + const node = newNode({ + path: is_prefix.path, + name: is_prefix.name, + index: iv_index, + order: iv_item_order, + }); + + if (iv_data === true || iv_data === false || (iv_data === "X" && !this.mv_raw_string)) { + // ABAP booleans arrive as true/false or as the abap_bool chars 'X'/'' + node.type = NT.boolean; + node.value = iv_data === false ? "false" : "true"; + } else if (typeof iv_data === "number" || typeof iv_data === "bigint") { + node.type = NT.number; + node.value = String(iv_data); + } else if (typeof iv_data === "string") { + node.type = NT.string; + // utclong values arrive as 'YYYY-MM-DD hh:mm:ss[.frac]' — ABAP emits + // them as ISO timestamps + node.value = + !this.mv_raw_string && /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?$/.test(iv_data) + ? iv_data.replace(" ", "T") + "Z" + : iv_data; + } else { + z2ui5_cx_ajson_error.raise(`Unexpected elementary type [${typeof iv_data}] @${is_prefix.path + is_prefix.name}`); } + + ct_nodes.push(node); } - get_node_type() { - let lr_item = null; - lr_item = this.get_item({ iv_path: iv_path }); - if (lr_item) { - rv_node_type = lr_item.type; + convert_ref({ iv_data, is_prefix, iv_index = 0, iv_item_order = 0, ct_nodes } = {}) { + const node = newNode({ + path: is_prefix.path, + name: this.apply_mapping(is_prefix), + index: iv_index, + order: iv_item_order, + }); + if (iv_data === null || iv_data === undefined) { + node.type = NT.null; + node.value = "null"; + ct_nodes.push(node); + } else { + this.convert_any({ iv_data, is_prefix, iv_index, iv_item_order, ct_nodes }); } } - get_number() { - let lr_item = null; - lr_item = this.get_item({ iv_path: iv_path }); - if (lr_item && lr_item.type === z2ui5_if_ajson_types.node_type.number) { - rv_value = lr_item.value; + convert_struc({ iv_data, is_prefix, iv_index = 0, iv_item_order = 0, ct_nodes } = {}) { + const root = newNode({ + path: is_prefix.path, + name: this.apply_mapping(is_prefix), + type: NT.object, + index: iv_index, + order: iv_item_order, + }); + ct_nodes.push(root); + + const nextPath = is_prefix.path + root.name + "/"; + for (const key of Object.keys(iv_data)) { + root.children++; + let memberName = key.toLowerCase(); + const val = iv_data[key]; + const elementary = !Array.isArray(val) && !(val !== null && typeof val === "object"); + if (this.mi_custom_mapping && elementary) { + const mapped = this.mi_custom_mapping.to_json({ iv_path: nextPath, iv_name: memberName }); + if (!isInitial(mapped)) memberName = mapped; + } + this.convert_any({ + iv_data: val, + is_prefix: { path: nextPath, name: memberName }, + iv_item_order: this.mv_keep_item_order ? root.children : 0, + ct_nodes, + }); } } - get_string() { - let lr_item = null; - lr_item = this.get_item({ iv_path: iv_path }); - if (lr_item && lr_item.type !== z2ui5_if_ajson_types.node_type.null) { - rv_value = lr_item.value; + convert_table({ iv_data, is_prefix, iv_index = 0, iv_item_order = 0, ct_nodes } = {}) { + const root = newNode({ + path: is_prefix.path, + name: this.apply_mapping(is_prefix), + type: NT.array, + index: iv_index, + order: iv_item_order, + }); + ct_nodes.push(root); + + const nextPath = is_prefix.path + root.name + "/"; + for (const val of iv_data) { + this.convert_any({ + iv_data: val, + is_prefix: { path: nextPath, name: `${root.children + 1}` }, + iv_index: root.children + 1, + ct_nodes, + }); + root.children++; } } - get_timestamp() { - let lo_to_abap = null; - let lr_item = null; - lr_item = this.get_item({ iv_path: iv_path }); - if (!lr_item) { - return; + insert_value_with_type({ iv_data, iv_type, is_prefix, iv_index = 0, iv_item_order = 0, ct_nodes } = {}) { + const prefix = is_prefix.path + is_prefix.name; + const sval = iv_data === true ? "true" : iv_data === false ? "false" : String(iv_data ?? ""); + + if (typeof iv_data === "number" || typeof iv_data === "bigint") { + if (iv_type !== NT.number) { + z2ui5_cx_ajson_error.raise(`Unexpected value for numeric [${sval}] @${prefix}`); + } + } else if (iv_type === NT.boolean && sval !== "true" && sval !== "false") { + z2ui5_cx_ajson_error.raise(`Unexpected boolean value [${sval}] @${prefix}`); + } else if (iv_type === NT.null && !isInitial(iv_data)) { + z2ui5_cx_ajson_error.raise(`Unexpected null value [${sval}] @${prefix}`); + } else if (iv_type === NT.number && !/^[0-9. E+-]*$/.test(sval)) { + z2ui5_cx_ajson_error.raise(`Unexpected numeric value [${sval}] @${prefix}`); + } else if (iv_type !== NT.string && iv_type !== NT.boolean && iv_type !== NT.null && iv_type !== NT.number) { + z2ui5_cx_ajson_error.raise(`Unexpected type for value [${iv_type},${sval}] @${prefix}`); } - lo_to_abap = null; // TODO(abap2js): CREATE OBJECT lo_to_abap. - try { - rv_value = lo_to_abap.to_timestamp(lr_item.value); - } catch (error) { - return; + + const node = newNode({ + path: is_prefix.path, + name: this.apply_mapping(is_prefix), + index: iv_index, + value: sval, + type: iv_type, + order: iv_item_order, + }); + ct_nodes.push(node); + } + + static format_date(arg) { + const { iv_date } = namedArgs(arg, "iv_date"); + const d = String(iv_date ?? ""); + if (!d || d === "00000000") return ""; + return `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`; + } + + static format_time(arg) { + const { iv_time } = namedArgs(arg, "iv_time"); + const t = String(iv_time ?? ""); + if (!t || t === "000000") return ""; + return `${t.slice(0, 2)}:${t.slice(2, 4)}:${t.slice(4, 6)}`; + } + + static format_timestamp(arg) { + const { iv_ts } = namedArgs(arg, "iv_ts"); + const s = String(Math.trunc(num(iv_ts))).padStart(14, "0"); + return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}T${s.slice(8, 10)}:${s.slice(10, 12)}:${s.slice(12, 14)}Z`; + } + + static format_timestampl(arg) { + const { iv_ts } = namedArgs(arg, "iv_ts"); + const [intPart, fracRaw = ""] = String(iv_ts ?? 0).split("."); + const s = String(Math.trunc(num(intPart))).padStart(14, "0"); + let frac = fracRaw.replace(/0+$/, ""); + if (!frac) frac = "0"; + return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}T${s.slice(8, 10)}:${s.slice(10, 12)}:${s.slice(12, 14)}.${frac}Z`; + } +} + +// --------------------------------------------------------------------------- +// mutators: filter runner, mapper runner, mutator queue +// --------------------------------------------------------------------------- + +class lcl_filter_runner { + mi_filter = null; + mr_source_tree = null; + mr_dest_tree = null; + + static new(arg) { + const { ii_filter } = namedArgs(arg, "ii_filter"); + return new lcl_filter_runner({ ii_filter }); + } + + constructor(arg) { + const { ii_filter } = namedArgs(arg, "ii_filter"); + if (!ii_filter) throw new Error(`ASSERT failed`); + this.mi_filter = ii_filter; + } + + run({ it_source_tree, et_dest_tree } = {}) { + this.mr_source_tree = it_source_tree || []; + this.mr_dest_tree = et_dest_tree || []; + this.mr_dest_tree.length = 0; + this.walk({ iv_path: "" }); + return this.mr_dest_tree; + } + + /** call keep_node accepting hand-written (return value) implementations */ + keep(is_node, iv_visit) { + const r = this.mi_filter.keep_node(iv_visit === undefined ? { is_node } : { is_node, iv_visit }); + return isTrue(r); + } + + walk({ iv_path, cs_parent = null } = {}) { + const cmp = cs_parent && cs_parent.type === NT.array ? byArrayIndex : byPrimaryKey; + for (const src of rowsOfPath(this.mr_source_tree, iv_path, cmp)) { + const node = { ...src }; + switch (node.type) { + case NT.boolean: + case NT.null: + case NT.number: + case NT.string: + if (!this.keep(node)) continue; + break; + case NT.array: + case NT.object: { + if (!this.keep(node, z2ui5_if_ajson_filter.visit_type.open)) continue; + // intentionally clear AFTER "open" + node.children = 0; + this.walk({ iv_path: iv_path + node.name + "/", cs_parent: node }); + if (!this.keep(node, z2ui5_if_ajson_filter.visit_type.close)) continue; + break; + } + default: + z2ui5_cx_ajson_error.raise(`Unexpected node type ${node.type}`); + } + if (cs_parent) { + cs_parent.children = num(cs_parent.children) + 1; + if (cs_parent.type === NT.array) { + node.name = `${cs_parent.children}`; + node.index = cs_parent.children; + } + } + this.mr_dest_tree.push(node); } } +} + +class lcl_mapper_runner { + mi_mapper = null; + mr_source_tree = null; + mr_dest_tree = null; - get_timestampl() { - let lo_to_abap = null; - let lr_item = null; - lr_item = this.get_item({ iv_path: iv_path }); - if (!lr_item) { - return; + static new(arg) { + const { ii_mapper } = namedArgs(arg, "ii_mapper"); + return new lcl_mapper_runner({ ii_mapper }); + } + + constructor(arg) { + const { ii_mapper } = namedArgs(arg, "ii_mapper"); + if (!ii_mapper) throw new Error(`ASSERT failed`); + this.mi_mapper = ii_mapper; + } + + run({ it_source_tree, et_dest_tree } = {}) { + const source = it_source_tree || []; + const dest = et_dest_tree || []; + const root = source.find((n) => String(n.path ?? "") === "" && String(n.name ?? "") === ""); + if (!root || (root.type !== NT.array && root.type !== NT.object)) { + // empty or one-value-only tree + dest.length = 0; + dest.push(...source.map((r) => ({ ...r }))); + return dest; } - lo_to_abap = null; // TODO(abap2js): CREATE OBJECT lo_to_abap. - try { - rv_value = lo_to_abap.to_timestampl(lr_item.value); - } catch (error) { - return; + this.mr_source_tree = source; + this.mr_dest_tree = dest; + dest.length = 0; + dest.push({ ...root }); + this.process_deep_node({ iv_path: "/", iv_renamed_path: "/", iv_node_type: root.type }); + return dest; + } + + rename(node) { + // CHANGING cv_name — transpiled implementations sync it back onto the + // args object; hand-written ones may return the new name directly + const bag = { is_node: node, cv_name: node.name }; + const r = this.mi_mapper.rename_node(bag); + let name = bag.cv_name; + if (r !== null && typeof r === "object" && "cv_name" in r) name = r.cv_name; + else if (typeof r === "string") name = r; + return name ?? ""; + } + + process_deep_node({ iv_path, iv_renamed_path, iv_node_type } = {}) { + for (const item of rowsOfPath(this.mr_source_tree, iv_path, iv_node_type === NT.array ? byArrayIndex : byPrimaryKey)) { + const renamed = { ...item }; + if (iv_node_type !== NT.array) { + // don't rename array item names -> they are numeric index + renamed.name = this.rename(item); + if (isInitial(renamed.name)) { + z2ui5_cx_ajson_error.raise({ iv_msg: `Renamed node name cannot be empty`, is_node: item }); + } + } + renamed.path = iv_renamed_path; + + if (this.mr_dest_tree.some((r) => r.path === renamed.path && r.name === renamed.name)) { + z2ui5_cx_ajson_error.raise({ iv_msg: `Renamed node has a duplicate`, is_node: renamed }); + } + this.mr_dest_tree.push(renamed); + + if (item.type === NT.array || item.type === NT.object) { + this.process_deep_node({ + iv_path: iv_path + item.name + "/", + iv_renamed_path: iv_renamed_path + renamed.name + "/", + iv_node_type: item.type, + }); + } + } + } +} + +class lcl_mutator_queue { + mt_queue = []; + + static new() { + return new lcl_mutator_queue(); + } + + add(arg) { + const { ii_mutator } = namedArgs(arg, "ii_mutator"); + if (ii_mutator) this.mt_queue.push(ii_mutator); + return this; + } + + run({ it_source_tree, et_dest_tree } = {}) { + const dest = et_dest_tree || []; + if (this.mt_queue.length === 0) { + dest.length = 0; + dest.push(...(it_source_tree || []).map((r) => ({ ...r }))); + return dest; + } + let from = it_source_tree || []; + for (let i = 0; i < this.mt_queue.length; i++) { + const to = i === this.mt_queue.length - 1 ? dest : []; + this.mt_queue[i].run({ it_source_tree: from, et_dest_tree: to }); + from = to; } + return dest; + } +} + +// --------------------------------------------------------------------------- +// z2ui5_cl_ajson +// --------------------------------------------------------------------------- + +class z2ui5_cl_ajson { + mt_json_tree = []; + ms_opts = { read_only: false, keep_item_order: false, to_abap_corresponding_only: false, format_datetime: false }; + mi_custom_mapping = null; + + constructor({ iv_keep_item_order = false, iv_format_datetime = true, iv_to_abap_corresponding_only = false } = {}) { + this.ms_opts.keep_item_order = isTrue(iv_keep_item_order); + this.ms_opts.to_abap_corresponding_only = isTrue(iv_to_abap_corresponding_only); + this.format_datetime(iv_format_datetime); + } + + // ---- static factories --------------------------------------------------- + + static create_empty({ ii_custom_mapping = null, iv_keep_item_order = false, iv_format_datetime = true, iv_to_abap_corresponding_only = false } = {}) { + const ro = new z2ui5_cl_ajson({ iv_to_abap_corresponding_only, iv_format_datetime, iv_keep_item_order }); + ro.mi_custom_mapping = ii_custom_mapping; + return ro; + } + + static create_from(arg) { + const bag = + arg !== null && typeof arg === "object" && Object.getPrototypeOf(arg) === Object.prototype + ? arg + : { ii_source_json: arg }; + const { ii_source_json, ii_filter = null, ii_mapper = null } = bag; + + if (!ii_source_json) { + z2ui5_cx_ajson_error.raise(`Source not bound`); + } + const opts = ii_source_json.opts(); + const ro = new z2ui5_cl_ajson({ + iv_to_abap_corresponding_only: opts.to_abap_corresponding_only, + iv_format_datetime: opts.format_datetime, + iv_keep_item_order: opts.keep_item_order, + }); + + if (!ii_filter && !ii_mapper) { + ro.mt_json_tree = ii_source_json.mt_json_tree.map((r) => ({ ...r })); + } else { + const queue = new lcl_mutator_queue(); + // mapping goes first + if (ii_mapper) queue.add(lcl_mapper_runner.new(ii_mapper)); + if (ii_filter) queue.add(lcl_filter_runner.new(ii_filter)); + queue.run({ it_source_tree: ii_source_json.mt_json_tree, et_dest_tree: ro.mt_json_tree }); + } + return ro; + } + + static new({ iv_keep_item_order = false, iv_format_datetime = true, iv_to_abap_corresponding_only = false } = {}) { + return new z2ui5_cl_ajson({ iv_to_abap_corresponding_only, iv_format_datetime, iv_keep_item_order }); + } + + static normalize_path(arg) { + return lcl_utils.normalize_path(arg); + } + + static parse(arg) { + const bag = typeof arg === "string" || Array.isArray(arg) ? { iv_json: arg } : arg || {}; + const { iv_json, iv_freeze = false, ii_custom_mapping = null, iv_keep_item_order = false } = bag; + const ro = new z2ui5_cl_ajson(); + const parser = new lcl_json_parser(); + ro.mt_json_tree = parser.parse({ iv_json, iv_keep_item_order }); + ro.mi_custom_mapping = ii_custom_mapping; + ro.ms_opts.keep_item_order = isTrue(iv_keep_item_order); + if (isTrue(iv_freeze)) ro.freeze(); + return ro; + } + + // ---- private-ish helpers (exposed like the ABAP privates for tests) ----- + + get_item(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const { path, name } = lcl_utils.split_path(iv_path); + return this.mt_json_tree.find((r) => String(r.path ?? "") === path && String(r.name ?? "") === name) ?? null; + } + + prove_path_exists(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const segments = String(iv_path ?? "").split("/").filter((s) => s !== ""); + + let curPath = ""; + let curName = ""; + let endNode = null; + for (let i = 0; ; i++) { + const parent = endNode; + endNode = this.mt_json_tree.find((r) => String(r.path ?? "") === curPath && String(r.name ?? "") === curName) ?? null; + if (!endNode) { + // new node, assume it is always object as it has a named child; use touch_array to init array + const node = newNode({ path: curPath, name: curName, type: NT.object }); + if (parent) { + parent.children = num(parent.children) + 1; + if (parent.type === NT.array) { + node.index = lcl_utils.validate_array_index({ iv_path: curPath, iv_index: curName }); + } + } + this.mt_json_tree.push(node); + endNode = node; + } + curPath = curPath + curName + "/"; + if (i >= segments.length) break; + curName = segments[i]; + } + return endNode; + } + + delete_subtree({ iv_path, iv_name, ir_parent } = {}) { + const idx = this.mt_json_tree.findIndex((r) => String(r.path ?? "") === iv_path && String(r.name ?? "") === iv_name); + if (idx < 0) return newNode(); // not found? nothing to delete! + + const top = this.mt_json_tree[idx]; + this.mt_json_tree.splice(idx, 1); + + if (num(top.children) > 0) { + // only for objects and arrays + const parentPath = String(iv_path ?? "") + String(iv_name ?? "") + "/"; + this.mt_json_tree = this.mt_json_tree.filter((r) => !String(r.path ?? "").startsWith(parentPath)); + } + + // decrement parent children + if (ir_parent !== undefined && ir_parent !== null) { + ir_parent.children = num(ir_parent.children) - 1; + } else { + const parent = this.get_item(iv_path); + if (parent) parent.children = num(parent.children) - 1; + } + return top; + } + + read_only_watchdog() { + if (this.ms_opts.read_only === true) { + z2ui5_cx_ajson_error.raise(`This json instance is read only`); + } + } + + // ---- z2ui5_if_ajson: reading -------------------------------------------- + + exists(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + return this.get_item(iv_path) !== null; } is_empty() { - rv_yes = Boolean(mt_json_tree.length === 0); + return this.mt_json_tree.length === 0; } - keep_item_order() { - this.ms_opts.keep_item_order = true; - ri_json = this; + members(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const path = lcl_utils.normalize_path(iv_path); + return rowsOfPath(this.mt_json_tree, path, byPrimaryKey).map((r) => String(r.name ?? "")); + } + + get(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const item = this.get_item(iv_path); + return item ? String(item.value ?? "") : ""; + } + + get_node_type(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const item = this.get_item(iv_path); + return item ? item.type : ""; + } + + get_boolean(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const item = this.get_item(iv_path); + if (!item || item.type === NT.null) return false; + if (item.type === NT.boolean) return String(item.value) === "true"; + return !isInitial(item.value); + } + + get_integer(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const item = this.get_item(iv_path); + if (item && item.type === NT.number) return Math.round(num(item.value)); + return 0; + } + + get_number(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const item = this.get_item(iv_path); + if (item && item.type === NT.number) return num(item.value); + return 0; } - map() { - ri_json = z2ui5_cl_ajson.create_from({ ii_source_json: this, ii_mapper }); + get_string(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const item = this.get_item(iv_path); + if (item && item.type !== NT.null) return String(item.value ?? ""); + return ""; } - members() { - let sy_tabix = 0; - let lv_normalized_path = ``; - // TODO(abap2js): FIELD-SYMBOLS LIKE LINE OF mt_json_tree. - lv_normalized_path = lcl_utils.normalize_path(iv_path); - sy_tabix = 0; - for (const item of mt_json_tree) { - sy_tabix++; - if (!(item.path === lv_normalized_path)) continue; - rt_members.push(item.name); + get_date(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const item = this.get_item(iv_path); + if (item && item.type === NT.string) { + const m = String(item.value ?? "").match(/^(\d{4})-(\d{2})-(\d{2})(T|$)/); + if (m) return `${m[1]}${m[2]}${m[3]}`; } + return ""; } - opts() { - rs_opts = this.ms_opts; + get_timestamp(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const item = this.get_item(iv_path); + if (!item) return 0; + try { + return new lcl_json_to_abap().to_timestamp(String(item.value ?? "")); + } catch { + return 0; + } } - push() { - let lr_parent = null; - let lr_new_node = null; - this.read_only_watchdog(); - lr_parent = this.get_item({ iv_path: iv_path }); - if (!lr_parent) { - z2ui5_cx_ajson_error.raise(`Path [${iv_path}] does not exist`); + get_timestampl(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const item = this.get_item(iv_path); + if (!item) return 0; + try { + return new lcl_json_to_abap().to_timestampl(String(item.value ?? "")); + } catch { + return 0; } - if (lr_parent.type !== z2ui5_if_ajson_types.node_type.array) { - z2ui5_cx_ajson_error.raise(`Path [${iv_path}] is not array`); + } + + slice(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const section = new z2ui5_cl_ajson(); + section.mi_custom_mapping = this.mi_custom_mapping; + + const normalized = lcl_utils.normalize_path(iv_path); + const pathLen = normalized.length; + const { path, name } = lcl_utils.split_path(normalized); + + const rootSrc = this.mt_json_tree.find((r) => String(r.path ?? "") === path && String(r.name ?? "") === name); + if (!rootSrc) return section; + + const root = { ...rootSrc, path: "", name: "", order: 0 }; // this becomes the new root + section.mt_json_tree.push(root); + + for (const src of this.mt_json_tree) { + if (!String(src.path ?? "").startsWith(normalized)) continue; + const item = { ...src }; + item.path = String(item.path).slice(pathLen - 1); // less closing '/' + section.mt_json_tree.push(item); } - let lt_new_nodes = null; - let ls_new_path = null; - let lv_new_index = 0; - lv_new_index = lr_parent.children + 1; - ls_new_path.path = lcl_utils.normalize_path(iv_path); - ls_new_path.name = `${lv_new_index}`; - lt_new_nodes = lcl_abap_to_json.convert({ is_opts: this.ms_opts, iv_data: iv_val, is_prefix: ls_new_path }); - // TODO(abap2js): READ TABLE lt_new_nodes INDEX 1 REFERENCE INTO lr_new_node. - if (!(sy_subrc === 0)) throw new Error(`ASSERT failed`); - lr_new_node.index = lv_new_index; - lr_parent.children = lv_new_index; - mt_json_tree.push(...lt_new_nodes); - ri_json = this; - } - - set() { - let ls_split_path = null; - let lr_parent = null; - let ls_deleted_node = null; - let lv_item_order = null; + return section; + } + + array_to_string_table(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + const normalized = lcl_utils.normalize_path(iv_path); + const node = this.get_item(iv_path); + + if (!node) { + z2ui5_cx_ajson_error.raise(`Path not found: ${iv_path}`); + } + if (node.type !== NT.array) { + z2ui5_cx_ajson_error.raise(`Array expected at: ${iv_path}`); + } + + const out = []; + for (const item of rowsOfPath(this.mt_json_tree, normalized, byPrimaryKey)) { + switch (item.type) { + case NT.number: + case NT.string: + out.push(String(item.value ?? "")); + break; + case NT.null: + out.push(""); + break; + case NT.boolean: + out.push(String(item.value) === "true" ? "X" : ""); + break; + default: + z2ui5_cx_ajson_error.raise(`Cannot convert [${item.type}] to string at [${item.path}${item.name}]`); + } + } + return out; + } + + to_abap(args = {}) { + const { iv_corresponding = false, ii_refs_initiator = null, ev_container } = args; + const converter = new lcl_json_to_abap({ + iv_corresponding: isTrue(iv_corresponding) || this.ms_opts.to_abap_corresponding_only === true, + ii_custom_mapping: this.mi_custom_mapping, + ii_refs_initiator, + }); + const result = converter.to_abap({ it_nodes: this.mt_json_tree, c_container: ev_container }); + // EXPORTING ev_container — sync onto the args object for scalar write-back + if (args !== null && typeof args === "object") args.ev_container = result; + return result; + } + + // ---- z2ui5_if_ajson: writing -------------------------------------------- + + clear() { + this.read_only_watchdog(); + this.mt_json_tree = []; + } + + set({ iv_path, iv_val, iv_ignore_empty = true, iv_node_type = "", iv_raw_string = false } = {}) { this.read_only_watchdog(); - ri_json = this; - if (!iv_val && iv_ignore_empty === true && !iv_node_type) { - return; + const opts = isTrue(iv_raw_string) ? { ...this.ms_opts, raw_string: true } : this.ms_opts; + + if (isInitial(iv_val) && isTrue(iv_ignore_empty) && isInitial(iv_node_type)) { + return this; // nothing to assign } - if (iv_node_type && iv_node_type !== z2ui5_if_ajson_types.node_type.boolean && iv_node_type !== z2ui5_if_ajson_types.node_type.null && iv_node_type !== z2ui5_if_ajson_types.node_type.number && iv_node_type !== z2ui5_if_ajson_types.node_type.string) { + + if ( + !isInitial(iv_node_type) && + iv_node_type !== NT.boolean && + iv_node_type !== NT.null && + iv_node_type !== NT.number && + iv_node_type !== NT.string + ) { z2ui5_cx_ajson_error.raise(`Unexpected type ${iv_node_type}`); } - ls_split_path = lcl_utils.split_path(iv_path); - if (!ls_split_path) { - if (iv_node_type) { - mt_json_tree = lcl_abap_to_json.insert_with_type({ is_opts: this.ms_opts, iv_data: iv_val, iv_type: iv_node_type, is_prefix: ls_split_path, ii_custom_mapping: this.mi_custom_mapping }); + + const splitPath = lcl_utils.split_path(iv_path); + if (splitPath.path === "" && splitPath.name === "") { + // assign root, exceptional processing + if (!isInitial(iv_node_type)) { + this.mt_json_tree = lcl_abap_to_json.insert_with_type({ + is_opts: opts, + iv_data: iv_val, + iv_type: iv_node_type, + is_prefix: splitPath, + ii_custom_mapping: this.mi_custom_mapping, + }); } else { - mt_json_tree = lcl_abap_to_json.convert({ is_opts: this.ms_opts, iv_data: iv_val, is_prefix: ls_split_path, ii_custom_mapping: this.mi_custom_mapping }); + this.mt_json_tree = lcl_abap_to_json.convert({ + is_opts: opts, + iv_data: iv_val, + is_prefix: splitPath, + ii_custom_mapping: this.mi_custom_mapping, + }); } - return; - } - lr_parent = this.prove_path_exists({ iv_path: ls_split_path.path }); - if (!(lr_parent)) throw new Error(`ASSERT failed`); - ls_deleted_node = this.delete_subtree({ ir_parent: lr_parent, iv_path: ls_split_path.path, iv_name: ls_split_path.name }); - lv_item_order = ls_deleted_node.order; - let lt_new_nodes = null; - let lv_array_index = 0; - if (lr_parent.type === z2ui5_if_ajson_types.node_type.array) { - lv_array_index = lcl_utils.validate_array_index({ iv_path: ls_split_path.path, iv_index: ls_split_path.name }); - } else if (lr_parent.type === z2ui5_if_ajson_types.node_type.object && lv_item_order === 0 && this.ms_opts.keep_item_order === true) { - lv_item_order = lr_parent.children + 1; - } - if (iv_node_type) { - lt_new_nodes = lcl_abap_to_json.insert_with_type({ is_opts: this.ms_opts, iv_item_order: lv_item_order, iv_data: iv_val, iv_type: iv_node_type, iv_array_index: lv_array_index, is_prefix: ls_split_path, ii_custom_mapping: this.mi_custom_mapping }); - } else { - lt_new_nodes = lcl_abap_to_json.convert({ is_opts: this.ms_opts, iv_item_order: lv_item_order, iv_data: iv_val, iv_array_index: lv_array_index, is_prefix: ls_split_path, ii_custom_mapping: this.mi_custom_mapping }); - } - if (lt_new_nodes.length > 0) { - lr_parent.children = lr_parent.children + 1; - mt_json_tree.push(...lt_new_nodes); - } - } - - setx() { - let lv_path = ``; - let lv_val = ``; - let lv_int = 0; - let lv_dec = 0; - let lv_last = 0; - if (!iv_param) { - ri_json = this; - return; - } - [lv_path, lv_val] = iv_param.split(`:`); - // TODO(abap2js): CONDENSE lv_path. - // TODO(abap2js): CONDENSE lv_val. - if (!lv_val) { - ri_json = this; - return; - } - if (z2ui5_cl_ajson.go_float_regex != null) { - z2ui5_cl_ajson.go_float_regex = null; // TODO(abap2js): CREATE OBJECT go_float_regex EXPORTING pattern = '^([1-9][0-9]*|0)\.[0-9]+$' . - } - if (lv_val === `null`) { - this.set_null(lv_path); - } else if (lv_val === `true`) { - this.set_boolean({ iv_path: lv_path, iv_val: true }); - } else if (lv_val === `false`) { - this.set_boolean({ iv_path: lv_path, iv_val: false }); - } else if ([...String(lv_val)].every(($c) => String(`0123456789`).includes($c))) { - lv_int = lv_val; - this.set_integer({ iv_path: lv_path, iv_val: lv_int }); - } else if ([...String(lv_val)].every(($c) => String(`0123456789.`).includes($c)) && z2ui5_cl_ajson.go_float_regex.create_matcher({ text: lv_val }).match() === true) { - lv_dec = lv_val; - this.set({ iv_path: lv_path, iv_val: lv_dec }); - } else if (String(lv_val).substr(0, 1) === `{` || String(lv_val).substr(0, 1) === `[`) { - this.set({ iv_path: lv_path, iv_val: z2ui5_cl_ajson.parse({ iv_json: lv_val, iv_keep_item_order: this.ms_opts.keep_item_order }) }); + return this; + } + + // ensure whole path exists + const parent = this.prove_path_exists(splitPath.path); + if (!parent) throw new Error(`ASSERT failed`); + + // delete if exists with subtree + const deletedNode = this.delete_subtree({ ir_parent: parent, iv_path: splitPath.path, iv_name: splitPath.name }); + let itemOrder = num(deletedNode.order); + + let arrayIndex = 0; + if (parent.type === NT.array) { + arrayIndex = lcl_utils.validate_array_index({ iv_path: splitPath.path, iv_index: splitPath.name }); + } else if (parent.type === NT.object && itemOrder === 0 && this.ms_opts.keep_item_order === true) { + itemOrder = num(parent.children) + 1; + } + + let newNodes; + if (!isInitial(iv_node_type)) { + newNodes = lcl_abap_to_json.insert_with_type({ + is_opts: opts, + iv_item_order: itemOrder, + iv_data: iv_val, + iv_type: iv_node_type, + iv_array_index: arrayIndex, + is_prefix: splitPath, + ii_custom_mapping: this.mi_custom_mapping, + }); } else { - lv_last = lv_val.length - 1; - if (String(lv_val).substr(0, 1) === `"` && String(lv_val).substr(lv_last, 1) === `"`) { - lv_val = lv_val.substr(1, lv_last - 1); - } - this.set_string({ iv_path: lv_path, iv_val: lv_val }); + newNodes = lcl_abap_to_json.convert({ + is_opts: opts, + iv_item_order: itemOrder, + iv_data: iv_val, + iv_array_index: arrayIndex, + is_prefix: splitPath, + ii_custom_mapping: this.mi_custom_mapping, + }); } - ri_json = this; + + if (newNodes.length > 0) { + parent.children = num(parent.children) + 1; + this.mt_json_tree.push(...newNodes); + } + return this; } - set_boolean() { - ri_json = this; - let lv_bool = false; - lv_bool = Boolean(iv_val); - this.set({ iv_ignore_empty: false, iv_path, iv_val: lv_bool }); + setx(arg) { + const { iv_param } = namedArgs(arg, "iv_param"); + if (isInitial(iv_param)) return this; + + const s = String(iv_param); + const colon = s.indexOf(":"); + let path = colon >= 0 ? s.slice(0, colon) : s; + let val = colon >= 0 ? s.slice(colon + 1) : ""; + + const condense = (x) => x.replace(/^ +| +$/g, "").replace(/ {2,}/g, " "); + path = condense(path); + val = condense(val); + + if (val === "") return this; + + if (val === "null") { + this.set_null(path); + } else if (val === "true") { + this.set_boolean({ iv_path: path, iv_val: true }); + } else if (val === "false") { + this.set_boolean({ iv_path: path, iv_val: false }); + } else if (/^[0-9]+$/.test(val)) { + this.set_integer({ iv_path: path, iv_val: Number(val) }); + } else if (/^[0-9.]+$/.test(val) && /^([1-9][0-9]*|0)\.[0-9]+$/.test(val)) { + this.set({ iv_path: path, iv_val: Number(val) }); + } else if (val[0] === "{" || val[0] === "[") { + // expect object/array, but no further checks, parser will catch errors + this.set({ + iv_path: path, + iv_val: z2ui5_cl_ajson.parse({ iv_json: val, iv_keep_item_order: this.ms_opts.keep_item_order }), + }); + } else { + // string + if (val.length >= 2 && val[0] === '"' && val[val.length - 1] === '"') { + val = val.slice(1, -1); + } + this.set_string({ iv_path: path, iv_val: val }); + } + return this; } - set_date() { - ri_json = this; - let lv_val = ``; - lv_val = lcl_abap_to_json.format_date(iv_val); - this.set({ iv_ignore_empty: false, iv_path, iv_val: lv_val }); + set_boolean({ iv_path, iv_val } = {}) { + this.set({ iv_ignore_empty: false, iv_path, iv_val: !isInitial(iv_val) }); + return this; } - set_integer() { - ri_json = this; - this.set({ iv_ignore_empty: false, iv_path, iv_val }); + set_string({ iv_path, iv_val } = {}) { + // ABAP MOVE to string: abap_bool true renders as 'X', false as ''. + // The explicit node type keeps 'X' a string (bare set() would read the + // abap_bool char as a boolean). + const val = iv_val === true ? "X" : iv_val === false || iv_val === null || iv_val === undefined ? "" : String(iv_val); + this.set({ iv_ignore_empty: false, iv_path, iv_val: val, iv_raw_string: true }); + return this; } - set_null() { - ri_json = this; - let lv_null_ref = null; - this.set({ iv_ignore_empty: false, iv_path, iv_val: lv_null_ref }); + set_integer({ iv_path, iv_val } = {}) { + this.set({ iv_ignore_empty: false, iv_path, iv_val: num(iv_val) }); + return this; } - set_string() { - ri_json = this; - let lv_val = ``; - lv_val = iv_val; - this.set({ iv_ignore_empty: false, iv_path, iv_val: lv_val }); + set_date({ iv_path, iv_val } = {}) { + this.set({ iv_ignore_empty: false, iv_path, iv_val: lcl_abap_to_json.format_date(iv_val) }); + return this; } - set_timestamp() { - ri_json = this; - let lv_timestamp_iso = ``; - lv_timestamp_iso = lcl_abap_to_json.format_timestamp(iv_val); - this.set({ iv_ignore_empty: false, iv_path, iv_val: lv_timestamp_iso }); + set_timestamp({ iv_path, iv_val } = {}) { + this.set({ iv_ignore_empty: false, iv_path, iv_val: lcl_abap_to_json.format_timestamp(iv_val) }); + return this; } - set_timestampl() { - ri_json = this; - let lv_timestamp_iso = ``; - lv_timestamp_iso = lcl_abap_to_json.format_timestampl(iv_val); - this.set({ iv_ignore_empty: false, iv_path, iv_val: lv_timestamp_iso }); + set_timestampl({ iv_path, iv_val } = {}) { + this.set({ iv_ignore_empty: false, iv_path, iv_val: lcl_abap_to_json.format_timestampl(iv_val) }); + return this; } - slice() { - let sy_tabix = 0; - let lo_section = null; - let ls_item = null; - let lv_normalized_path = ``; - let ls_path_parts = null; - let lv_path_len = 0; - let lv_path_pattern = ``; - lo_section = null; // TODO(abap2js): CREATE OBJECT lo_section. - lo_section.mi_custom_mapping = this.mi_custom_mapping; - lv_normalized_path = lcl_utils.normalize_path(iv_path); - lv_path_len = lv_normalized_path.length; - ls_path_parts = lcl_utils.split_path(lv_normalized_path); - // TODO(abap2js): READ TABLE mt_json_tree INTO ls_item WITH KEY path = ls_path_parts-path name = ls_path_parts-name. - if (sy_subrc !== 0) { - return; - } - ls_item.path = null; - ls_item.name = null; - ls_item.order = null; - lo_section.mt_json_tree.push(ls_item); - lv_path_pattern = lv_normalized_path + `*`; - sy_tabix = 0; - for (const ls_item of mt_json_tree) { - sy_tabix++; - if (!(String(ls_item.path).includes(String(lv_path_pattern).replace(/\*/g, "")))) continue; - ls_item.path = ls_item.path.substr(lv_path_len - 1); - lo_section.mt_json_tree.push(ls_item); - } - ri_json = lo_section; + set_null(arg) { + const { iv_path } = namedArgs(arg, "iv_path"); + this.set({ iv_ignore_empty: false, iv_path, iv_val: null }); + return this; } - stringify() { - rv_json = lcl_json_serializer.stringify({ it_json_tree: mt_json_tree, iv_keep_item_order: this.ms_opts.keep_item_order, iv_indent }); + delete(arg) { + this.read_only_watchdog(); + const { iv_path } = namedArgs(arg, "iv_path"); + const { path, name } = lcl_utils.split_path(iv_path); + this.delete_subtree({ iv_path: path, iv_name: name }); + return this; } - touch_array() { - let lr_node = null; - let ls_deleted_node = null; - let ls_new_node = null; - let ls_split_path = null; + touch_array(arg) { + const { iv_path, iv_clear = false } = namedArgs(arg, "iv_path"); this.read_only_watchdog(); - ls_split_path = lcl_utils.split_path(iv_path); - if (!ls_split_path) { - ls_new_node.path = ls_split_path.path; - ls_new_node.name = ls_split_path.name; - ls_new_node.type = z2ui5_if_ajson_types.node_type.array; - mt_json_tree.push(ls_new_node); - return; - } - if (iv_clear === true) { - ls_deleted_node = this.delete_subtree({ iv_path: ls_split_path.path, iv_name: ls_split_path.name }); + + const splitPath = lcl_utils.split_path(iv_path); + if (splitPath.path === "" && splitPath.name === "") { + // assign root, exceptional processing + this.mt_json_tree.push(newNode({ path: "", name: "", type: NT.array })); + return; // ABAP returns before ri_json is set + } + + let node = null; + let deletedNode = null; + if (isTrue(iv_clear)) { + deletedNode = this.delete_subtree({ iv_path: splitPath.path, iv_name: splitPath.name }); + if (isInitial(deletedNode.type) && isInitial(deletedNode.name)) deletedNode = null; } else { - lr_node = this.get_item({ iv_path: iv_path }); - } - if (!lr_node) { - let lr_parent = null; - lr_parent = this.prove_path_exists({ iv_path: ls_split_path.path }); - if (!(lr_parent)) throw new Error(`ASSERT failed`); - lr_parent.children = lr_parent.children + 1; - ls_new_node.path = ls_split_path.path; - ls_new_node.name = ls_split_path.name; - ls_new_node.type = z2ui5_if_ajson_types.node_type.array; + node = this.get_item(iv_path); + } + + if (!node) { + // or node was cleared + const parent = this.prove_path_exists(splitPath.path); + if (!parent) throw new Error(`ASSERT failed`); + parent.children = num(parent.children) + 1; + + const created = newNode({ path: splitPath.path, name: splitPath.name, type: NT.array }); if (this.ms_opts.keep_item_order === true) { - if (ls_deleted_node) { - ls_new_node.order = ls_deleted_node.order; - } else { - ls_new_node.order = lr_parent.children; - } + created.order = deletedNode ? num(deletedNode.order) : num(parent.children); } - mt_json_tree.push(ls_new_node); - } else if (lr_node.type !== z2ui5_if_ajson_types.node_type.array) { + this.mt_json_tree.push(created); + } else if (node.type !== NT.array) { z2ui5_cx_ajson_error.raise(`Path [${iv_path}] already used and is not array`); } - ri_json = this; + return this; } - to_abap() { - let lo_to_abap = null; - ev_container = null; - lo_to_abap = null; // TODO(abap2js): CREATE OBJECT lo_to_abap EXPORTING iv_corresponding = boolc( iv_corresponding = abap_true OR ms_opts-to_abap_corresponding_only = abap_true ) ii_custom_mapping = mi_custom_mapping ii_refs_initiator = ii_refs_initiator. - lo_to_abap.to_abap({ it_nodes: this.mt_json_tree, c_container: ev_container }); + push({ iv_path, iv_val } = {}) { + this.read_only_watchdog(); + + const parent = this.get_item(iv_path); + if (!parent) { + z2ui5_cx_ajson_error.raise(`Path [${iv_path}] does not exist`); + } + if (parent.type !== NT.array) { + z2ui5_cx_ajson_error.raise(`Path [${iv_path}] is not array`); + } + + const newIndex = num(parent.children) + 1; + const newNodes = lcl_abap_to_json.convert({ + is_opts: this.ms_opts, + iv_data: iv_val, + is_prefix: { path: lcl_utils.normalize_path(iv_path), name: `${newIndex}` }, + }); + if (!newNodes.length) throw new Error(`ASSERT failed`); + newNodes[0].index = newIndex; // assume first record is the array item + + parent.children = newIndex; + this.mt_json_tree.push(...newNodes); + return this; } - to_abap_corresponding_only() { - this.ms_opts.to_abap_corresponding_only = iv_enable; - ri_json = this; + // ---- z2ui5_if_ajson: cloning & options ---------------------------------- + + clone() { + return z2ui5_cl_ajson.create_from({ ii_source_json: this }); + } + + filter(arg) { + const bag = + arg !== null && typeof arg === "object" && Object.getPrototypeOf(arg) === Object.prototype && "ii_filter" in arg + ? arg + : { ii_filter: arg }; + return z2ui5_cl_ajson.create_from({ ii_source_json: this, ii_filter: bag.ii_filter }); + } + + map(arg) { + const bag = + arg !== null && typeof arg === "object" && Object.getPrototypeOf(arg) === Object.prototype && "ii_mapper" in arg + ? arg + : { ii_mapper: arg }; + return z2ui5_cl_ajson.create_from({ ii_source_json: this, ii_mapper: bag.ii_mapper }); + } + + stringify(arg) { + const { iv_indent = 0 } = namedArgs(arg ?? {}, "iv_indent"); + return lcl_json_serializer.stringify({ + it_json_tree: this.mt_json_tree, + iv_keep_item_order: this.ms_opts.keep_item_order, + iv_indent, + }); + } + + opts() { + return { ...this.ms_opts }; + } + + format_datetime(arg) { + const { iv_use_iso = true } = namedArgs(arg ?? {}, "iv_use_iso"); + this.ms_opts.format_datetime = isTrue(iv_use_iso); + return this; + } + + keep_item_order() { + this.ms_opts.keep_item_order = true; + return this; + } + + to_abap_corresponding_only(arg) { + const { iv_enable = true } = namedArgs(arg ?? {}, "iv_enable"); + this.ms_opts.to_abap_corresponding_only = isTrue(iv_enable); + return this; + } + + freeze() { + this.ms_opts.read_only = true; } } +// the ABAP class-pool locals, reachable for the transpiled upstream unit +// tests (transpile-tests.js binds them instead of re-transpiling the ABAP) +z2ui5_cl_ajson.__locals = { + lcl_utils, + lcl_json_parser, + lcl_json_serializer, + lcl_json_to_abap, + lcl_abap_to_json, + lcl_filter_runner, + lcl_mapper_runner, + lcl_mutator_queue, +}; + module.exports = z2ui5_cl_ajson; diff --git a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_filter_lib.js b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_filter_lib.js index 57a7488..29f8719 100644 --- a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_filter_lib.js +++ b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_filter_lib.js @@ -1,22 +1,108 @@ +// Hand-port of z2ui5_cl_ajson_filter_lib (upstream sbcgua/ajson clone) — +// ready-made z2ui5_if_ajson_filter implementations. The ABAP local classes +// (lcl_empty_filter, lcl_paths_filter, lcl_and_filter) live in-file and are +// exposed as __locals for the transpiled upstream unit tests. +"use strict"; -class z2ui5_cl_ajson_filter_lib { - static create_and_filter({ it_filters } = {}) { - let ri_filter = null; - ri_filter = null; // TODO(abap2js): CREATE OBJECT ri_filter TYPE lcl_and_filter EXPORTING it_filters = it_filters. - return ri_filter; +const z2ui5_cx_ajson_error = require("abap2UI5/z2ui5_cx_ajson_error"); +const z2ui5_if_ajson_types = require("abap2UI5/z2ui5_if_ajson_types"); +const z2ui5_if_ajson_filter = require("abap2UI5/z2ui5_if_ajson_filter"); + +const NT = z2ui5_if_ajson_types.node_type; +const VISIT = z2ui5_if_ajson_filter.visit_type; + +const num = (v) => { + const n = Number(String(v ?? "").trim()); + return Number.isNaN(n) ? 0 : n; +}; +const isInitial = (v) => + v === undefined || v === null || v === "" || v === 0 || v === false || (Array.isArray(v) && v.length === 0); + +/** ABAP CP pattern: '*' any sequence, '+' single char, case-insensitive */ +function cpMatch(value, pattern) { + const rx = new RegExp( + `^${String(pattern).replace(/[.^${}()|[\]\\?]/g, "\\$&").replace(/\*/g, ".*").replace(/\+/g, ".")}$`, + "is" + ); + return rx.test(String(value)); +} + +class lcl_empty_filter { + keep_node({ is_node, iv_visit = VISIT.value } = {}) { + // children = 0 on open for initially empty nodes and on close for filtered ones + return ( + (iv_visit === VISIT.value && is_node.type !== NT.number && !isInitial(is_node.value)) || // string & bool & null + (iv_visit === VISIT.value && is_node.type === NT.number && String(is_node.value) !== "0") || // num + (iv_visit !== VISIT.value && num(is_node.children) > 0) // array & object + ); + } +} + +class lcl_paths_filter { + mt_skip_paths = []; + mv_pattern_search = false; + + constructor({ it_skip_paths, iv_skip_paths, iv_pattern_search = false } = {}) { + if (isInitial(iv_skip_paths) === isInitial(it_skip_paths)) { + // XOR + z2ui5_cx_ajson_error.raise(`no filter path specified`); + } + + let paths = []; + if (!isInitial(it_skip_paths)) { + paths = it_skip_paths.map((s) => String(s).trim()); + } + if (!isInitial(iv_skip_paths)) { + paths = String(iv_skip_paths) + .split(",") + .filter((s) => s !== "") + .map((s) => s.trim()); + } + paths.sort(); + this.mt_skip_paths = [...new Set(paths)]; + this.mv_pattern_search = iv_pattern_search === true || iv_pattern_search === "X"; + } + + keep_node({ is_node } = {}) { + const fullPath = String(is_node.path ?? "") + String(is_node.name ?? ""); + if (this.mv_pattern_search) { + return !this.mt_skip_paths.some((p) => cpMatch(fullPath, p)); + } + return !this.mt_skip_paths.includes(fullPath); + } +} + +class lcl_and_filter { + mt_filters = []; + + constructor({ it_filters } = {}) { + this.mt_filters = (it_filters || []).filter((f) => f !== null && f !== undefined); + } + + keep_node({ is_node, iv_visit = VISIT.value } = {}) { + for (const filter of this.mt_filters) { + const keep = filter.keep_node({ is_node, iv_visit }); + if (!(keep === true || keep === "X")) return false; + } + return true; } +} +class z2ui5_cl_ajson_filter_lib { static create_empty_filter() { - let ri_filter = null; - ri_filter = null; // TODO(abap2js): CREATE OBJECT ri_filter TYPE lcl_empty_filter. - return ri_filter; + return new lcl_empty_filter(); } static create_path_filter({ it_skip_paths, iv_skip_paths, iv_pattern_search = false } = {}) { - let ri_filter = null; - ri_filter = null; // TODO(abap2js): CREATE OBJECT ri_filter TYPE lcl_paths_filter EXPORTING iv_pattern_search = iv_pattern_search it_skip_paths = it_skip_paths iv_skip_paths = iv_skip_paths. - return ri_filter; + return new lcl_paths_filter({ it_skip_paths, iv_skip_paths, iv_pattern_search }); + } + + static create_and_filter(arg) { + const it_filters = Array.isArray(arg) ? arg : (arg || {}).it_filters; + return new lcl_and_filter({ it_filters }); } } +z2ui5_cl_ajson_filter_lib.__locals = { lcl_empty_filter, lcl_paths_filter, lcl_and_filter }; + module.exports = z2ui5_cl_ajson_filter_lib; diff --git a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_mapping.js b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_mapping.js index 90904b3..b2d712e 100644 --- a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_mapping.js +++ b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_mapping.js @@ -1,60 +1,297 @@ +// Hand-port of z2ui5_cl_ajson_mapping (upstream sbcgua/ajson clone) — +// name-mapper factories for z2ui5_if_ajson_mapping. The ABAP local classes +// live in-file and are exposed as __locals for the transpiled upstream unit +// tests. rename_node keeps ABAP's CHANGING semantics by writing the new +// name back onto the passed args object (cv_name). +"use strict"; + +/** ABAP CP pattern: '*' any sequence, '+' single char, case-insensitive */ +function cpMatch(value, pattern) { + const rx = new RegExp( + `^${String(pattern).replace(/[.^${}()|[\]\\?]/g, "\\$&").replace(/\*/g, ".*").replace(/\+/g, ".")}$`, + "is" + ); + return rx.test(String(value)); +} + +/** invoke a mapper's rename_node with CHANGING write-back (both the + * bag-sync and the returned-object convention) and return the new name */ +function renameVia(mapper, node, name) { + const bag = { is_node: node, cv_name: name }; + const r = mapper.rename_node(bag); + let out = bag.cv_name; + if (r !== null && r !== undefined && typeof r === "object" && "cv_name" in r) out = r.cv_name; + else if (typeof r === "string") out = r; + return out ?? ""; +} + +class lcl_mapping_fields { + // deprecated, will be removed (kept for parity with upstream) + mt_mapping_fields = []; + + constructor({ it_mapping_fields } = {}) { + for (const row of it_mapping_fields || []) { + this.mt_mapping_fields.push({ abap: String(row.abap ?? "").toUpperCase(), json: String(row.json ?? "") }); + } + } + + to_abap({ iv_path, iv_name } = {}) { + const hit = this.mt_mapping_fields.find((r) => r.json === iv_name); + return hit ? hit.abap : ""; + } + + to_json({ iv_path, iv_name } = {}) { + const field = String(iv_name ?? "").toUpperCase(); + const hit = this.mt_mapping_fields.find((r) => r.abap === field); + return hit ? hit.json : ""; + } + + rename_node(args = {}) { + return args.cv_name; + } +} + +class lcl_rename { + mt_rename_map = []; + mv_rename_by = 0; + + constructor({ it_rename_map, iv_rename_by = 0 } = {}) { + this.mt_rename_map = (it_rename_map || []).map((r) => ({ from: String(r.from ?? ""), to: String(r.to ?? "") })); + this.mv_rename_by = iv_rename_by; + } + + to_abap() { + return ""; + } + + to_json() { + return ""; + } + + rename_node(args = {}) { + const { is_node, cv_name } = args; + let hit = null; + switch (this.mv_rename_by) { + case z2ui5_cl_ajson_mapping.rename_by.attr_name: + hit = this.mt_rename_map.find((r) => r.from === cv_name) ?? null; + break; + case z2ui5_cl_ajson_mapping.rename_by.full_path: + hit = this.mt_rename_map.find((r) => r.from === `${is_node.path}${cv_name}`) ?? null; + break; + case z2ui5_cl_ajson_mapping.rename_by.pattern: { + const fullPath = `${is_node.path}${cv_name}`; + hit = this.mt_rename_map.find((r) => cpMatch(fullPath, r.from)) ?? null; + break; + } + default: + hit = null; // no rename + } + if (hit) args.cv_name = hit.to; + return args.cv_name; + } +} + +class lcl_mapping_to_upper { + constructor({ it_mapping_fields } = {}) { + this.mi_mapping_fields = z2ui5_cl_ajson_mapping.create_field_mapping({ it_mapping_fields }); + } + + to_abap({ iv_path, iv_name } = {}) { + return this.mi_mapping_fields.to_abap({ iv_path, iv_name }); + } + + to_json({ iv_path, iv_name } = {}) { + const mapped = this.mi_mapping_fields.to_json({ iv_path, iv_name }); + if (mapped) return mapped; // mapping found + return String(iv_name ?? "").toUpperCase(); + } + + rename_node(args = {}) { + args.cv_name = String(args.cv_name ?? "").toUpperCase(); + return args.cv_name; + } +} + +class lcl_mapping_to_lower { + constructor({ it_mapping_fields } = {}) { + this.mi_mapping_fields = z2ui5_cl_ajson_mapping.create_field_mapping({ it_mapping_fields }); + } + + to_abap({ iv_path, iv_name } = {}) { + return this.mi_mapping_fields.to_abap({ iv_path, iv_name }); + } + + to_json({ iv_path, iv_name } = {}) { + const mapped = this.mi_mapping_fields.to_json({ iv_path, iv_name }); + if (mapped) return mapped; // mapping found + return String(iv_name ?? "").toLowerCase(); + } + + rename_node(args = {}) { + args.cv_name = String(args.cv_name ?? "").toLowerCase(); + return args.cv_name; + } +} + +class lcl_mapping_camel { + // deprecated, will be removed (kept for parity with upstream) + mv_first_json_upper = true; + + constructor({ it_mapping_fields, iv_first_json_upper = true } = {}) { + this.mi_mapping_fields = z2ui5_cl_ajson_mapping.create_field_mapping({ it_mapping_fields }); + this.mv_first_json_upper = iv_first_json_upper === true || iv_first_json_upper === "X"; + } + + to_abap({ iv_path, iv_name } = {}) { + const mapped = this.mi_mapping_fields.to_abap({ iv_path, iv_name }); + if (mapped) return mapped; // mapping found + return String(iv_name ?? "").replace(/([a-z])([A-Z])/g, "$1_$2"); + } + + to_json({ iv_path, iv_name } = {}) { + const mapped = this.mi_mapping_fields.to_json({ iv_path, iv_name }); + if (mapped) return mapped; // mapping found + + let name = String(iv_name ?? "") + .replace(/__/g, "*") // double underscore stays an underscore + .toLowerCase() + .replace(/[/:~]/g, "_"); + const tokens = name.split("_"); + const from = this.mv_first_json_upper ? 0 : 1; + for (let i = from; i < tokens.length; i++) { + if (tokens[i]) tokens[i] = tokens[i][0].toUpperCase() + tokens[i].slice(1); + } + return tokens.join("").replace(/\*/g, "_"); + } + + rename_node(args = {}) { + return args.cv_name; + } +} + +class lcl_compound_mapper { + mt_queue = []; + + constructor({ it_queue } = {}) { + this.mt_queue = it_queue || []; + } + + to_abap() { + return ""; + } + + to_json() { + return ""; + } + + rename_node(args = {}) { + const node = { ...args.is_node }; + for (const mapper of this.mt_queue) { + args.cv_name = renameVia(mapper, node, args.cv_name); + node.name = args.cv_name; + } + return args.cv_name; + } +} + +class lcl_to_snake { + to_abap() { + return ""; + } + + to_json() { + return ""; + } + + rename_node(args = {}) { + args.cv_name = String(args.cv_name ?? "") + .replace(/([a-z])([A-Z])/g, "$1_$2") + .toLowerCase(); + return args.cv_name; + } +} + +class lcl_to_camel { + mv_first_json_upper = false; + + constructor({ iv_first_json_upper } = {}) { + this.mv_first_json_upper = iv_first_json_upper === true || iv_first_json_upper === "X"; + } + + to_abap() { + return ""; + } + + to_json() { + return ""; + } + + rename_node(args = {}) { + // double underscore is a forced underscore in the output + const tokens = String(args.cv_name ?? "") + .replace(/__/g, "\t") + .split("_") + .filter((t) => t !== ""); + const from = this.mv_first_json_upper ? 0 : 1; + for (let i = from; i < tokens.length; i++) { + tokens[i] = tokens[i][0].toUpperCase() + tokens[i].slice(1); + } + args.cv_name = tokens.join("").replace(/\t/g, "_"); + return args.cv_name; + } +} class z2ui5_cl_ajson_mapping { static rename_by = { attr_name: 0, full_path: 1, pattern: 2 }; static create_camel_case({ it_mapping_fields, iv_first_json_upper = true } = {}) { - let ri_mapping = null; - ri_mapping = null; // TODO(abap2js): CREATE OBJECT ri_mapping TYPE lcl_mapping_camel EXPORTING it_mapping_fields = it_mapping_fields iv_first_json_upper = iv_first_json_upper. - return ri_mapping; + // DEPRECATED + return new lcl_mapping_camel({ it_mapping_fields, iv_first_json_upper }); } - static create_compound_mapper({ ii_mapper1, ii_mapper2, ii_mapper3, it_more } = {}) { - let ri_mapping = null; - let lt_queue = null; - lt_queue.push(ii_mapper1); - lt_queue.push(ii_mapper2); - lt_queue.push(ii_mapper3); - lt_queue.push(...it_more); - for (let _i = lt_queue.length - 1; _i >= 0; _i--) { const row = lt_queue[_i]; if (!row.table_line) lt_queue.splice(_i, 1); } - ri_mapping = null; // TODO(abap2js): CREATE OBJECT ri_mapping TYPE lcl_compound_mapper EXPORTING it_queue = lt_queue. - return ri_mapping; + static create_upper_case({ it_mapping_fields } = {}) { + return new lcl_mapping_to_upper({ it_mapping_fields }); } - static create_field_mapping({ it_mapping_fields } = {}) { - let ri_mapping = null; - ri_mapping = null; // TODO(abap2js): CREATE OBJECT ri_mapping TYPE lcl_mapping_fields EXPORTING it_mapping_fields = it_mapping_fields. - return ri_mapping; + static create_lower_case({ it_mapping_fields } = {}) { + return new lcl_mapping_to_lower({ it_mapping_fields }); } - static create_lower_case({ it_mapping_fields } = {}) { - let ri_mapping = null; - ri_mapping = null; // TODO(abap2js): CREATE OBJECT ri_mapping TYPE lcl_mapping_to_lower EXPORTING it_mapping_fields = it_mapping_fields. - return ri_mapping; + static create_field_mapping(arg) { + // DEPRECATED + const it_mapping_fields = Array.isArray(arg) ? arg : (arg || {}).it_mapping_fields; + return new lcl_mapping_fields({ it_mapping_fields }); } - static create_rename({ it_rename_map, iv_rename_by = z2ui5_cl_ajson_mapping.rename_by.attr_name } = {}) { - let ri_mapping = null; - ri_mapping = null; // TODO(abap2js): CREATE OBJECT ri_mapping TYPE lcl_rename EXPORTING it_rename_map = it_rename_map iv_rename_by = iv_rename_by. - return ri_mapping; + static create_rename(arg) { + const bag = Array.isArray(arg) ? { it_rename_map: arg } : arg || {}; + const { it_rename_map, iv_rename_by = z2ui5_cl_ajson_mapping.rename_by.attr_name } = bag; + return new lcl_rename({ it_rename_map, iv_rename_by }); } - static create_to_camel_case({ iv_first_json_upper = false } = {}) { - let ri_mapping = null; - ri_mapping = null; // TODO(abap2js): CREATE OBJECT ri_mapping TYPE lcl_to_camel EXPORTING iv_first_json_upper = iv_first_json_upper. - return ri_mapping; + static create_compound_mapper({ ii_mapper1, ii_mapper2, ii_mapper3, it_more } = {}) { + const queue = [ii_mapper1, ii_mapper2, ii_mapper3, ...(it_more || [])].filter((m) => m !== null && m !== undefined); + return new lcl_compound_mapper({ it_queue: queue }); } static create_to_snake_case() { - let ri_mapping = null; - ri_mapping = null; // TODO(abap2js): CREATE OBJECT ri_mapping TYPE lcl_to_snake. - return ri_mapping; + return new lcl_to_snake(); } - static create_upper_case({ it_mapping_fields } = {}) { - let ri_mapping = null; - ri_mapping = null; // TODO(abap2js): CREATE OBJECT ri_mapping TYPE lcl_mapping_to_upper EXPORTING it_mapping_fields = it_mapping_fields. - return ri_mapping; + static create_to_camel_case({ iv_first_json_upper = false } = {}) { + return new lcl_to_camel({ iv_first_json_upper }); } } +z2ui5_cl_ajson_mapping.__locals = { + lcl_mapping_fields, + lcl_rename, + lcl_mapping_to_upper, + lcl_mapping_to_lower, + lcl_mapping_camel, + lcl_compound_mapper, + lcl_to_snake, + lcl_to_camel, +}; + module.exports = z2ui5_cl_ajson_mapping; diff --git a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_refinitlib.js b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_refinitlib.js index da7bf4a..e8ac972 100644 --- a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_refinitlib.js +++ b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_refinitlib.js @@ -1,10 +1,33 @@ +// Hand-port of z2ui5_cl_ajson_refinitlib (upstream sbcgua/ajson clone) — +// z2ui5_if_ajson_refinit implementations. lcl_path_refs_init resolves data +// refs by node path+name; exposed as __locals for the transpiled tests. +"use strict"; + +class lcl_path_refs_init { + mt_data_refs = []; + + constructor(arg) { + const it_data_refs = + Array.isArray(arg) ? arg : arg && typeof arg === "object" ? arg.it_data_refs : arg; + this.mt_data_refs = it_data_refs || []; + } + + get_data_ref(arg) { + const is_node = arg && typeof arg === "object" && "is_node" in arg ? arg.is_node : arg; + const hit = this.mt_data_refs.find( + (r) => String(r.path ?? "") === String(is_node.path ?? "") && String(r.name ?? "") === String(is_node.name ?? "") + ); + return hit ? hit.dref : null; + } +} class z2ui5_cl_ajson_refinitlib { - static create_path_refs_init({ it_data_refs } = {}) { - let ri_refs_init = null; - ri_refs_init = null; // TODO(abap2js): CREATE OBJECT ri_refs_init TYPE lcl_path_refs_init EXPORTING it_data_refs = it_data_refs. - return ri_refs_init; + static create_path_refs_init(arg) { + const it_data_refs = Array.isArray(arg) ? arg : (arg || {}).it_data_refs; + return new lcl_path_refs_init(it_data_refs); } } +z2ui5_cl_ajson_refinitlib.__locals = { lcl_path_refs_init }; + module.exports = z2ui5_cl_ajson_refinitlib; diff --git a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_utilities.js b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_utilities.js index 89c211e..c2d977d 100644 --- a/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_utilities.js +++ b/run/input/core/srv/z2ui5/00/01/z2ui5_cl_ajson_utilities.js @@ -1,7 +1,87 @@ +// Hand-port of z2ui5_cl_ajson_utilities (upstream sbcgua/ajson clone) — +// diff / merge / sort / is_equal / iterators over ajson instances. The ABAP +// local class lcl_node_iterator lives in-file and is exposed as __locals for +// the transpiled upstream unit tests. EXPORTING params (diff's eo_*) are +// written back onto the args object per the out-param convention. +"use strict"; + const z2ui5_cl_ajson = require("abap2UI5/z2ui5_cl_ajson"); const z2ui5_cx_ajson_error = require("abap2UI5/z2ui5_cx_ajson_error"); const z2ui5_if_ajson_types = require("abap2UI5/z2ui5_if_ajson_types"); +const NT = z2ui5_if_ajson_types.node_type; + +const num = (v) => { + const n = Number(String(v ?? "").trim()); + return Number.isNaN(n) ? 0 : n; +}; +const isInitial = (v) => v === undefined || v === null || v === "" || v === 0 || v === false; + +const byPrimaryKey = (a, b) => { + const ap = String(a.path ?? ""), bp = String(b.path ?? ""); + if (ap !== bp) return ap < bp ? -1 : 1; + const an = String(a.name ?? ""), bn = String(b.name ?? ""); + return an < bn ? -1 : an > bn ? 1 : 0; +}; +const byArrayIndex = (a, b) => num(a.index) - num(b.index) || byPrimaryKey(a, b); + +function rowsOfPath(tree, path, cmp) { + return tree.filter((r) => String(r.path ?? "") === path).sort(cmp); +} + +// --------------------------------------------------------------------------- +// lcl_node_iterator +// --------------------------------------------------------------------------- + +class lcl_node_iterator { + mi_json = null; + mv_node_type = ""; + mv_base_path = ""; + mt_rows = []; + mv_pos = 0; + + constructor({ ii_json, iv_path, iv_node_type } = {}) { + if (iv_node_type !== NT.array && iv_node_type !== NT.object) { + z2ui5_cx_ajson_error.raise(`Iterator can iterate arrays or objects only ("${iv_node_type}" passed)`); + } + + this.mv_base_path = z2ui5_cl_ajson.normalize_path(iv_path); + this.mv_node_type = iv_node_type; + this.mi_json = ii_json; + + const nodeType = ii_json.get_node_type(this.mv_base_path); + if (isInitial(nodeType)) { + z2ui5_cx_ajson_error.raise(`Path not found: ${iv_path}`); + } else if (iv_node_type === NT.array && nodeType !== iv_node_type) { + z2ui5_cx_ajson_error.raise(`Array expected at: ${iv_path}`); + } else if (iv_node_type === NT.object && nodeType !== iv_node_type) { + z2ui5_cx_ajson_error.raise(`Object expected at: ${iv_path}`); + } + + this.mt_rows = rowsOfPath( + ii_json.mt_json_tree, + this.mv_base_path, + iv_node_type === NT.array ? byArrayIndex : byPrimaryKey + ); + this.mv_pos = 0; + } + + has_next() { + return this.mv_pos < this.mt_rows.length; + } + + next() { + if (!this.has_next()) return null; + const cursor = this.mt_rows[this.mv_pos]; + this.mv_pos++; + return this.mi_json.slice(`${cursor.path}${cursor.name}`); + } +} + +// --------------------------------------------------------------------------- +// z2ui5_cl_ajson_utilities +// --------------------------------------------------------------------------- + class z2ui5_cl_ajson_utilities { mo_json_a = null; mo_json_b = null; @@ -9,223 +89,215 @@ class z2ui5_cl_ajson_utilities { mo_delete = null; mo_change = null; + static new() { + return new z2ui5_cl_ajson_utilities(); + } + + normalize_input({ iv_json, io_json } = {}) { + if (isInitial(iv_json) === isInitial(io_json)) { + z2ui5_cx_ajson_error.raise(`Either supply JSON string or instance, but not both`); + } + if (!isInitial(iv_json)) { + return z2ui5_cl_ajson.parse(iv_json); + } + return io_json; + } + delete_empty_nodes({ io_json, iv_keep_empty_arrays } = {}) { - let sy_tabix = 0; - let ls_json_tree = null; - let lv_done = false; - while (true) { - lv_done = true; - if (iv_keep_empty_arrays === false) { - sy_tabix = 0; - for (const ls_json_tree of io_json.mt_json_tree) { - sy_tabix++; - if (!(ls_json_tree.type === z2ui5_if_ajson_types.node_type.array && ls_json_tree.children === 0)) continue; - io_json.delete_(ls_json_tree.path + ls_json_tree.name); - } - if (sy_subrc === 0) { - lv_done = false; + for (;;) { + let done = true; + + if (!(iv_keep_empty_arrays === true || iv_keep_empty_arrays === "X")) { + const emptyArrays = io_json.mt_json_tree.filter((r) => r.type === NT.array && num(r.children) === 0); + for (const row of emptyArrays) { + io_json.delete(`${row.path}${row.name}`); } + if (emptyArrays.length > 0) done = false; } - sy_tabix = 0; - for (const ls_json_tree of io_json.mt_json_tree) { - sy_tabix++; - if (!(ls_json_tree.type === z2ui5_if_ajson_types.node_type.object && ls_json_tree.children === 0)) continue; - io_json.delete_(ls_json_tree.path + ls_json_tree.name); - } - if (sy_subrc === 0) { - lv_done = false; - } - if (lv_done === true) { - break; + + const emptyObjects = io_json.mt_json_tree.filter((r) => r.type === NT.object && num(r.children) === 0); + for (const row of emptyObjects) { + io_json.delete(`${row.path}${row.name}`); } + if (emptyObjects.length > 0) done = false; + + if (done) break; // nothing else to delete } } - diff({ iv_json_a, iv_json_b, io_json_a, io_json_b, iv_keep_empty_arrays = false, eo_insert, eo_delete, eo_change } = {}) { + diff(args = {}) { + const { iv_json_a, iv_json_b, io_json_a, io_json_b, iv_keep_empty_arrays = false } = args; + this.mo_json_a = this.normalize_input({ iv_json: iv_json_a, io_json: io_json_a }); this.mo_json_b = this.normalize_input({ iv_json: iv_json_b, io_json: io_json_b }); + this.mo_insert = z2ui5_cl_ajson.create_empty(); this.mo_delete = z2ui5_cl_ajson.create_empty(); this.mo_change = z2ui5_cl_ajson.create_empty(); - this.diff_a_b({ iv_path: `/` }); + + this.diff_a_b(`/`); this.diff_b_a({ iv_path: `/` }); - eo_insert = this.mo_insert; - eo_delete = this.mo_delete; - eo_change = this.mo_change; - this.delete_empty_nodes({ io_json: eo_insert, iv_keep_empty_arrays }); - this.delete_empty_nodes({ io_json: eo_delete, iv_keep_empty_arrays }); - this.delete_empty_nodes({ io_json: eo_change, iv_keep_empty_arrays }); + + this.delete_empty_nodes({ io_json: this.mo_insert, iv_keep_empty_arrays }); + this.delete_empty_nodes({ io_json: this.mo_delete, iv_keep_empty_arrays }); + this.delete_empty_nodes({ io_json: this.mo_change, iv_keep_empty_arrays }); + + // EXPORTING eo_* — write back onto the args object + if (args !== null && typeof args === "object") { + args.eo_insert = this.mo_insert; + args.eo_delete = this.mo_delete; + args.eo_change = this.mo_change; + } + return { eo_insert: this.mo_insert, eo_delete: this.mo_delete, eo_change: this.mo_change }; } - diff_a_b({ iv_path } = {}) { - let sy_tabix = 0; - let lv_path_a = ``; - let lv_path_b = ``; - // TODO(abap2js): FIELD-SYMBOLS LIKE LINE OF mo_json_a->mt_json_tree, - // TODO(abap2js): FIELD-SYMBOLS LIKE LINE OF mo_json_a->mt_json_tree. - sy_tabix = 0; - for (const node_a of this.mo_json_a.mt_json_tree) { - sy_tabix++; - if (!(node_a.path === iv_path)) continue; - lv_path_a = node_a.path + node_a.name + `/`; - // TODO(abap2js): READ TABLE mo_json_b->mt_json_tree ASSIGNING WITH TABLE KEY path = -path name = -name. - if (sy_subrc === 0) { - lv_path_b = node_b.path + node_b.name + `/`; - if (node_a.type === node_b.type) { - switch (node_a.type) { - case z2ui5_if_ajson_types.node_type.array: - this.mo_insert.touch_array(lv_path_a); - this.mo_change.touch_array(lv_path_a); - this.mo_delete.touch_array(lv_path_a); - this.diff_a_b({ iv_path: lv_path_a }); + diff_a_b(arg) { + const iv_path = typeof arg === "string" ? arg : arg.iv_path; + + for (const nodeA of rowsOfPath(this.mo_json_a.mt_json_tree, iv_path, byPrimaryKey)) { + const pathA = `${nodeA.path}${nodeA.name}/`; + const nodeB = this.mo_json_b.mt_json_tree.find( + (r) => String(r.path ?? "") === String(nodeA.path ?? "") && String(r.name ?? "") === String(nodeA.name ?? "") + ); + + if (nodeB) { + const pathB = `${nodeB.path}${nodeB.name}/`; + if (nodeA.type === nodeB.type) { + switch (nodeA.type) { + case NT.array: + this.mo_insert.touch_array(pathA); + this.mo_change.touch_array(pathA); + this.mo_delete.touch_array(pathA); + this.diff_a_b(pathA); break; - case z2ui5_if_ajson_types.node_type.object: - this.diff_a_b({ iv_path: lv_path_a }); + case NT.object: + this.diff_a_b(pathA); break; default: - if (node_a.value !== node_b.value) { - this.mo_change.set({ iv_path: lv_path_b, iv_val: node_b.value, iv_node_type: node_b.type }); + if (String(nodeA.value ?? "") !== String(nodeB.value ?? "")) { + // save as changed value + this.mo_change.set({ iv_path: pathB, iv_val: nodeB.value, iv_node_type: nodeB.type }); } - break; } } else { - switch (node_a.type) { - case z2ui5_if_ajson_types.node_type.array: - this.mo_delete.touch_array(lv_path_a); - this.diff_a_b({ iv_path: lv_path_a }); + // save changed type as delete + insert + switch (nodeA.type) { + case NT.array: + this.mo_delete.touch_array(pathA); + this.diff_a_b(pathA); break; - case z2ui5_if_ajson_types.node_type.object: - this.diff_a_b({ iv_path: lv_path_a }); + case NT.object: + this.diff_a_b(pathA); break; default: - this.mo_delete.set({ iv_path: lv_path_a, iv_val: node_a.value, iv_node_type: node_a.type }); - break; + this.mo_delete.set({ iv_path: pathA, iv_val: nodeA.value, iv_node_type: nodeA.type }); } - switch (node_b.type) { - case z2ui5_if_ajson_types.node_type.array: - this.mo_insert.touch_array(lv_path_b); - this.diff_b_a({ iv_path: lv_path_b }); + switch (nodeB.type) { + case NT.array: + this.mo_insert.touch_array(pathB); + this.diff_b_a({ iv_path: pathB }); break; - case z2ui5_if_ajson_types.node_type.object: - this.diff_b_a({ iv_path: lv_path_b }); + case NT.object: + this.diff_b_a({ iv_path: pathB }); break; default: - this.mo_insert.set({ iv_path: lv_path_b, iv_val: node_b.value, iv_node_type: node_b.type }); - break; + this.mo_insert.set({ iv_path: pathB, iv_val: nodeB.value, iv_node_type: nodeB.type }); } } } else { - switch (node_a.type) { - case z2ui5_if_ajson_types.node_type.array: - this.mo_delete.touch_array(lv_path_a); - this.diff_a_b({ iv_path: lv_path_a }); + // save as delete + switch (nodeA.type) { + case NT.array: + this.mo_delete.touch_array(pathA); + this.diff_a_b(pathA); break; - case z2ui5_if_ajson_types.node_type.object: - this.diff_a_b({ iv_path: lv_path_a }); + case NT.object: + this.diff_a_b(pathA); break; default: - this.mo_delete.set({ iv_path: lv_path_a, iv_val: node_a.value, iv_node_type: node_a.type }); - break; + this.mo_delete.set({ iv_path: pathA, iv_val: nodeA.value, iv_node_type: nodeA.type }); } } } } - diff_b_a({ iv_path, iv_array = false } = {}) { - let sy_tabix = 0; - let lv_path = ``; - // TODO(abap2js): FIELD-SYMBOLS LIKE LINE OF mo_json_b->mt_json_tree. - sy_tabix = 0; - for (const node_b of this.mo_json_b.mt_json_tree) { - sy_tabix++; - if (!(node_b.path === iv_path)) continue; - lv_path = node_b.path + node_b.name + `/`; - switch (node_b.type) { - case z2ui5_if_ajson_types.node_type.array: - this.mo_insert.touch_array(lv_path); - this.diff_b_a({ iv_path: lv_path, iv_array: true }); + diff_b_a(arg) { + const bag = typeof arg === "string" ? { iv_path: arg } : arg || {}; + const { iv_path, iv_array = false } = bag; + const isArray = iv_array === true || iv_array === "X"; + + for (const nodeB of rowsOfPath(this.mo_json_b.mt_json_tree, iv_path, byPrimaryKey)) { + const path = `${nodeB.path}${nodeB.name}/`; + switch (nodeB.type) { + case NT.array: + this.mo_insert.touch_array(path); + this.diff_b_a({ iv_path: path, iv_array: true }); break; - case z2ui5_if_ajson_types.node_type.object: - this.diff_b_a({ iv_path: lv_path }); + case NT.object: + this.diff_b_a({ iv_path: path }); break; default: - if (iv_array === false) { - // TODO(abap2js): READ TABLE mo_json_a->mt_json_tree TRANSPORTING NO FIELDS WITH TABLE KEY path = -path name = -name. - if (sy_subrc !== 0) { - this.mo_insert.set({ iv_path: lv_path, iv_val: node_b.value, iv_node_type: node_b.type }); + if (!isArray) { + const inA = this.mo_json_a.mt_json_tree.some( + (r) => + String(r.path ?? "") === String(nodeB.path ?? "") && String(r.name ?? "") === String(nodeB.name ?? "") + ); + if (!inA) { + // save as insert + this.mo_insert.set({ iv_path: path, iv_val: nodeB.value, iv_node_type: nodeB.type }); } } else { - // TODO(abap2js): READ TABLE mo_insert->mt_json_tree TRANSPORTING NO FIELDS WITH KEY path = -path value = -value. - if (sy_subrc !== 0) { - this.mo_insert.push({ iv_path, iv_val: node_b.value }); + const inserted = this.mo_insert.mt_json_tree.some( + (r) => + String(r.path ?? "") === String(nodeB.path ?? "") && String(r.value ?? "") === String(nodeB.value ?? "") + ); + if (!inserted) { + // save as new array value + this.mo_insert.push({ iv_path, iv_val: nodeB.value }); } } - break; } } } - is_equal({ iv_json_a, iv_json_b, ii_json_a, ii_json_b } = {}) { - let rv_yes = false; - let li_ins = null; - let li_del = null; - let li_mod = null; - // TODO(abap2js): diff( EXPORTING iv_json_a = iv_json_a iv_json_b = iv_json_b io_json_a = ii_json_a io_json_b = ii_json_b IMPORTING eo_insert = li_ins eo_delete = li_del eo_change = li_mod ). - rv_yes = Boolean(li_ins.is_empty() === true && li_del.is_empty() === true && li_mod.is_empty() === true); - return rv_yes; - } - - static iterate_array({ ii_json, iv_path } = {}) { - let ri_iterator = null; - ri_iterator = null; // TODO(abap2js): CREATE OBJECT ri_iterator TYPE lcl_node_iterator EXPORTING iv_node_type = z2ui5_if_ajson_types=>node_type-array ii_json = ii_json iv_path = iv_path. - return ri_iterator; - } - - static iterate_object({ ii_json, iv_path } = {}) { - let ri_iterator = null; - ri_iterator = null; // TODO(abap2js): CREATE OBJECT ri_iterator TYPE lcl_node_iterator EXPORTING iv_node_type = z2ui5_if_ajson_types=>node_type-object ii_json = ii_json iv_path = iv_path. - return ri_iterator; - } - merge({ iv_json_a, iv_json_b, io_json_a, io_json_b, iv_keep_empty_arrays = false } = {}) { - let ro_json = null; this.mo_json_a = this.normalize_input({ iv_json: iv_json_a, io_json: io_json_a }); this.mo_json_b = this.normalize_input({ iv_json: iv_json_b, io_json: io_json_b }); + + // start with first JSON and add all nodes from second JSON this.mo_insert = this.mo_json_a; this.diff_b_a({ iv_path: `/` }); - ro_json = this.mo_insert; - this.delete_empty_nodes({ io_json: ro_json, iv_keep_empty_arrays }); - return ro_json; + + const result = this.mo_insert; + this.delete_empty_nodes({ io_json: result, iv_keep_empty_arrays }); + return result; } - static new() { - let ro_instance = null; - ro_instance = null; // TODO(abap2js): CREATE OBJECT ro_instance. - return ro_instance; + sort({ iv_json, io_json } = {}) { + const json = this.normalize_input({ iv_json, io_json }); + // nodes are kept in primary-key order by the serializer — no explicit sorting required + return json.stringify({ iv_indent: 2 }); } - normalize_input({ iv_json, io_json } = {}) { - let ro_json = null; - if (Boolean(!iv_json) === Boolean(!io_json)) { - z2ui5_cx_ajson_error.raise(`Either supply JSON string or instance, but not both`); - } - if (iv_json) { - ro_json = z2ui5_cl_ajson.parse(iv_json); - } else if (io_json) { - ro_json = io_json; - } else { - z2ui5_cx_ajson_error.raise(`Supply either JSON string or instance`); - } - return ro_json; + is_equal({ iv_json_a, iv_json_b, ii_json_a, ii_json_b } = {}) { + const { eo_insert, eo_delete, eo_change } = this.diff({ + iv_json_a, + iv_json_b, + io_json_a: ii_json_a, + io_json_b: ii_json_b, + }); + return eo_insert.is_empty() === true && eo_delete.is_empty() === true && eo_change.is_empty() === true; } - sort({ iv_json, io_json } = {}) { - let rv_sorted = ``; - let lo_json = null; - lo_json = this.normalize_input({ iv_json, io_json }); - rv_sorted = lo_json.stringify(2); - return rv_sorted; + static iterate_array({ ii_json, iv_path } = {}) { + return new lcl_node_iterator({ iv_node_type: NT.array, ii_json, iv_path }); + } + + static iterate_object({ ii_json, iv_path } = {}) { + return new lcl_node_iterator({ iv_node_type: NT.object, ii_json, iv_path }); } } +z2ui5_cl_ajson_utilities.__locals = { lcl_node_iterator }; + module.exports = z2ui5_cl_ajson_utilities; diff --git a/run/input/core/srv/z2ui5/00/01/z2ui5_cx_ajson_error.js b/run/input/core/srv/z2ui5/00/01/z2ui5_cx_ajson_error.js index ed53038..f2635ce 100644 --- a/run/input/core/srv/z2ui5/00/01/z2ui5_cx_ajson_error.js +++ b/run/input/core/srv/z2ui5/00/01/z2ui5_cx_ajson_error.js @@ -1,4 +1,10 @@ -const cx_static_check = class {}; // TODO(abap2js): unresolved superclass — replace stub manually +// Hand-port of z2ui5_cx_ajson_error (upstream zcx_ajson_error clone). +// ABAP semantics kept: raise() builds the instance, stamps the location and +// throws; set_location chunks " @" into the four 50-char +// t100 attributes a1..a4 (MOVE of a string into ty_message_parts); get_text +// (if_message) renders those chunks back into the display text. + +const cx_static_check = class {}; class z2ui5_cx_ajson_error extends cx_static_check { static zcx_ajson_error = { msgid: `00`, msgno: `001`, attr1: `A1`, attr2: `A2`, attr3: `A3`, attr4: `A4` }; @@ -12,10 +18,11 @@ class z2ui5_cx_ajson_error extends cx_static_check { a4 = null; constructor({ textid, previous, rc, message, location, a1, a2, a3, a4 } = {}) { - // TODO(abap2js): CALL METHOD SUPER->CONSTRUCTOR EXPORTING PREVIOUS = PREVIOUS . + super(); + this.previous = previous ?? null; this.rc = rc; - this.message = message; - this.location = location; + this.message = message ?? ``; + this.location = location ?? ``; this.a1 = a1; this.a2 = a2; this.a3 = a3; @@ -28,39 +35,41 @@ class z2ui5_cx_ajson_error extends cx_static_check { } } - static raise({ iv_msg, iv_location, is_node } = {}) { - let lx = null; - lx = null; // TODO(abap2js): create object lx exporting message = iv_msg. + // transpiled callers pass a lone positional message (raise('…')) as well + // as the named form — accept both + static raise(arg = {}) { + const { iv_msg, iv_location, is_node } = typeof arg === "string" ? { iv_msg: arg } : arg; + const lx = new z2ui5_cx_ajson_error({ message: iv_msg }); lx.set_location({ iv_location, is_node }); throw lx; } - set_location({ iv_location, is_node } = {}) { - let ls_msg = null; + set_location(arg = {}) { + // positional string (set_location('loc')) and named form both occur + const { iv_location, is_node } = typeof arg === "string" ? { iv_location: arg } : arg; let lv_location = ``; - let lv_tmp = ``; - // TODO(abap2js): field-symbols type string. - // TODO(abap2js): field-symbols type string. if (iv_location) { lv_location = iv_location; } else if (is_node) { - // TODO(abap2js): assign component 'PATH' of structure is_node to . - // TODO(abap2js): assign component 'NAME' of structure is_node to . + const path = is_node.path ?? is_node.PATH; + const name = is_node.name ?? is_node.NAME; if (path != null && name != null) { - lv_location = path + name; + lv_location = `${path}${name}`; } } - if (lv_location) { - lv_tmp = this.message + ` @${lv_location}`; - } else { - lv_tmp = this.message; - } - ls_msg = lv_tmp; + const lv_tmp = lv_location ? `${this.message} @${lv_location}` : `${this.message}`; this.location = lv_location; - this.a1 = ls_msg.a1; - this.a2 = ls_msg.a2; - this.a3 = ls_msg.a3; - this.a4 = ls_msg.a4; + // ls_msg = lv_tmp: a string MOVEd into ty_message_parts (4 × symsgv-like + // 50-char components) splits into consecutive 50-char chunks + this.a1 = lv_tmp.slice(0, 50); + this.a2 = lv_tmp.slice(50, 100); + this.a3 = lv_tmp.slice(100, 150); + this.a4 = lv_tmp.slice(150, 200); + } + + // if_message~get_text: the t100 message rendered from attrs a1..a4 + get_text() { + return `${this.a1 ?? ``}${this.a2 ?? ``}${this.a3 ?? ``}${this.a4 ?? ``}`; } } diff --git a/run/input/core/srv/z2ui5/00/03/z2ui5_cl_a2ui5_context.js b/run/input/core/srv/z2ui5/00/03/z2ui5_cl_a2ui5_context.js index 75aeed7..4bfc745 100644 --- a/run/input/core/srv/z2ui5/00/03/z2ui5_cl_a2ui5_context.js +++ b/run/input/core/srv/z2ui5/00/03/z2ui5_cl_a2ui5_context.js @@ -86,7 +86,7 @@ class z2ui5_cl_a2ui5_context { } lo_ele = (lo_descr); result = z2ui5_cl_a2ui5_context.boolean_check_by_name({ val: lo_ele.get_relative_name() }); - z2ui5_cl_a2ui5_context.mt_bool_cache.push({ typedescr: lo_descr, is_bool: result }); + z2ui5_cl_a2ui5_context.mt_bool_cache.push(z2ui5_cl_util.abap_copy({ typedescr: lo_descr, is_bool: result })); } catch (error) { } return result; @@ -160,7 +160,7 @@ class z2ui5_cl_a2ui5_context { static conv_get_as_data_ref({ val } = {}) { let result = null; - // TODO(abap2js): GET REFERENCE OF val INTO result. + result = val; return result; } @@ -205,20 +205,23 @@ class z2ui5_cl_a2ui5_context { let sy_tabix = 0; let lv_value; const lt_mapping = z2ui5_cl_a2ui5_context.filter_get_token_range_mapping(); - const lt_tab = {}; - z2ui5_cl_a2ui5_context.itab_corresponding({ val, tab: lt_tab }); + let lt_tab = {}; + const _out0 = { val, tab: lt_tab }; + z2ui5_cl_a2ui5_context.itab_corresponding(_out0); + if ("tab" in _out0) lt_tab = _out0.tab; sy_tabix = 0; for (const lr_row of lt_tab) { sy_tabix++; lv_value = lt_mapping.find((row) => row.n === lr_row.option).v; - // TODO(abap2js): REPLACE `{LOW}` IN lv_value WITH lr_row->low. - // TODO(abap2js): REPLACE `{HIGH}` IN lv_value WITH lr_row->high. - result.push({ key: lv_value, text: lv_value, visible: true, editable: true, selkz: false }); + lv_value = String(lv_value).replace(`{LOW}`, lr_row.low ?? ``); + lv_value = String(lv_value).replace(`{HIGH}`, lr_row.high ?? ``); + result.push(z2ui5_cl_util.abap_copy({ key: lv_value, text: lv_value, visible: true, editable: true, selkz: false })); } return result; } - static itab_filter_by_val({ val, fields, ignore_case = false, tab } = {}) { + static itab_filter_by_val(_args = {}) { + let { val, fields, ignore_case = false, tab } = _args; let sy_tabix = 0; let sy_subrc = 0; let fs_field = null; @@ -274,6 +277,7 @@ class z2ui5_cl_a2ui5_context { // TODO(abap2js): DELETE tab INDEX lv_tabix. } } + Object.assign(_args, { tab }); } static rtti_check_class_exists({ val } = {}) { @@ -344,11 +348,11 @@ class z2ui5_cl_a2ui5_context { sy_tabix = 0; for (const lr_incl_comp of incl_comps) { sy_tabix++; - result.push(lr_incl_comp); + result.push(z2ui5_cl_util.abap_copy(lr_incl_comp)); } sy_tabix = _sy_tabix_1; } else { - result.push(lr_comp); + result.push(z2ui5_cl_util.abap_copy(lr_comp)); } } return result; @@ -408,7 +412,7 @@ class z2ui5_cl_a2ui5_context { for (const lr_comp of comps) { sy_tabix++; if (!(lr_comp.as_include === true || lr_comp.as_include === `X`)) { - result.push(lr_comp); + result.push(z2ui5_cl_util.abap_copy(lr_comp)); } else { lt_attri = z2ui5_cl_a2ui5_context.rtti_get_t_attri_by_include(lr_comp.type); result.push(...lt_attri); @@ -418,19 +422,19 @@ class z2ui5_cl_a2ui5_context { lr_cache.o_struct = z2ui5_cl_util.abap_copy(lo_struct); lr_cache.t_attri = z2ui5_cl_util.abap_copy(result); } else { - z2ui5_cl_a2ui5_context.mt_attri_cache.push({ absolute_name: lv_absolute_name, o_struct: lo_struct, t_attri: result }); + z2ui5_cl_a2ui5_context.mt_attri_cache.push(z2ui5_cl_util.abap_copy({ absolute_name: lv_absolute_name, o_struct: lo_struct, t_attri: result })); } return result; } static time_get_timestampl() { - let result = null; + let result = 0; // TODO(abap2js): GET TIME STAMP FIELD result. return result; } static time_subtract_seconds({ time, seconds } = {}) { - let result = null; + let result = 0; result = cl_abap_tstmp.subtractsecs({ tstmp: time, secs: seconds }); return result; } @@ -492,17 +496,20 @@ class z2ui5_cl_a2ui5_context { for (const lr_param of lt_param) { sy_tabix++; let [lv_name, lv_value] = lr_param.split(`=`); - rt_params.push({ n: lv_name, v: lv_value }); + rt_params.push(z2ui5_cl_util.abap_copy({ n: lv_name, v: lv_value })); } return rt_params; } - static xml_parse({ xml, any } = {}) { + static xml_parse(_args = {}) { + let { xml, any } = _args; if (!xml) { any = null; + Object.assign(_args, { any }); return; } // TODO(abap2js): CALL TRANSFORMATION id SOURCE XML xml RESULT data = any. + Object.assign(_args, { any }); } static xml_srtti_parse({ rtti_data } = {}) { @@ -549,14 +556,17 @@ class z2ui5_cl_a2ui5_context { return result; } - static itab_corresponding({ val, tab } = {}) { + static itab_corresponding(_args = {}) { + let { val, tab } = _args; let sy_tabix = 0; sy_tabix = 0; for (const fs_row_in of val) { sy_tabix++; - tab.push({}); + let fs_row_out = {}; + tab.push(fs_row_out); // TODO(abap2js): MOVE-CORRESPONDING TO . } + Object.assign(_args, { tab }); } static itab_get_by_struc({ val } = {}) { @@ -579,7 +589,7 @@ class z2ui5_cl_a2ui5_context { case cl_abap_typedescr.typekind_table: break; default: - result.push({ n: lr_attri.name, v: fs_component }); + result.push(z2ui5_cl_util.abap_copy({ n: lr_attri.name, v: fs_component })); break; } } @@ -676,11 +686,11 @@ class z2ui5_cl_a2ui5_context { } catch (error) { result.check_table_line = true; lo_elem = (lo_table.get_table_line_type()); - lt_comp.push({ name: `TAB_LINE`, type: lo_elem }); + lt_comp.push(z2ui5_cl_util.abap_copy({ name: `TAB_LINE`, type: lo_elem })); } if ((add_sel_field === true || add_sel_field === `X`) && !lt_comp.some((row) => row.name === sel_field_name)) { lo_type_bool = cl_abap_typedescr.describe_by_name(`ABAP_BOOL`); - lt_comp.push({ name: sel_field_name, type: (lo_type_bool) }); + lt_comp.push(z2ui5_cl_util.abap_copy({ name: sel_field_name, type: (lo_type_bool) })); } const lo_line_type = cl_abap_structdescr.create(lt_comp); result.tabledescr = cl_abap_tabledescr.create(lo_line_type); @@ -720,7 +730,7 @@ class z2ui5_cl_a2ui5_context { sy_tabix = 0; for (const lr_msg of lt_msg) { sy_tabix++; - lt_detail_items.push(`
  • ${lr_msg.text}
  • `); + lt_detail_items.push(z2ui5_cl_util.abap_copy(`
  • ${lr_msg.text}
  • `)); } result.details = `
      ` + /* TODO(abap2js) */ concat_lines_of(lt_detail_items) + `
    `; result.title = z2ui5_cl_a2ui5_context.ui5_get_msg_type({ val: lt_msg[(1) - 1].type }); @@ -751,7 +761,7 @@ class z2ui5_cl_a2ui5_context { let result = ``; const lt_param = z2ui5_cl_a2ui5_context.url_param_get_tab({ i_val: search }); for (let _i = lt_param.length - 1; _i >= 0; _i--) { const row = lt_param[_i]; if (row.n === `app_start`) lt_param.splice(_i, 1); } - lt_param.push({ n: `app_start`, v: classname.toLowerCase() }); + lt_param.push(z2ui5_cl_util.abap_copy({ n: `app_start`, v: classname.toLowerCase() })); result = `${origin}${pathname}?` + z2ui5_cl_a2ui5_context.url_param_create_url({ t_params: lt_param }) + hash; return result; } @@ -853,12 +863,10 @@ class z2ui5_cl_a2ui5_context { let _fs$fs_any = null; let obj = null; let lt_implementation_names = []; - // TODO(abap2js): DATA BEGIN OF ls_clskey. - let clsname = ``; - // TODO(abap2js): DATA END OF ls_clskey. + let ls_clskey = { clsname: `` }; let xco_cp_abap = ``; let implementation_name = null; - let ls_class = null; + let ls_class = { classname: ``, description: `` }; ls_clskey.clsname = z2ui5_cl_util.abap_copy(val); xco_cp_abap = `XCO_CP_ABAP`; // TODO(abap2js): CALL METHOD (xco_cp_abap)=>interface EXPORTING iv_name = ls_clskey-clsname RECEIVING ro_interface = obj. @@ -878,7 +886,7 @@ class z2ui5_cl_a2ui5_context { sy_tabix++; ls_class.classname = z2ui5_cl_util.abap_copy(implementation_name); ls_class.description = z2ui5_cl_a2ui5_context.rtti_get_class_descr_on_cloud({ i_classname: implementation_name }); - result.push(ls_class); + result.push(z2ui5_cl_util.abap_copy(ls_class)); } return result; } @@ -893,9 +901,7 @@ class z2ui5_cl_a2ui5_context { let _fs$fs_description = null; let lt_impl = []; let ls_key = {}; - // TODO(abap2js): DATA BEGIN OF ls_clskey. - let clsname = ``; - // TODO(abap2js): DATA END OF ls_clskey. + let ls_clskey = { clsname: `` }; let class_ = null; let type = ``; let lr_impl = null; @@ -925,10 +931,10 @@ class z2ui5_cl_a2ui5_context { fs_description = _fs$fs_description ? _fs$fs_description.o[_fs$fs_description.k] : null; sy_subrc = _fs$fs_description ? 0 : 4; if (!(sy_subrc === 0)) throw new Error(`ASSERT failed`); - ls_class = null; + ls_class = {}; ls_class.classname = z2ui5_cl_util.abap_copy(lr_impl.clsname); ls_class.description = z2ui5_cl_util.abap_copy(fs_description); - result.push(ls_class); + result.push(z2ui5_cl_util.abap_copy(ls_class)); } return result; } @@ -939,9 +945,15 @@ class z2ui5_cl_a2ui5_context { let lv_do_fallback = false; data_element_name = z2ui5_cl_util.abap_copy(val); try { - // TODO(abap2js): rtti_get_dtel_texts_by_ddic( EXPORTING name = data_element_name IMPORTING texts = result do_fallback = lv_do_fallback ). + const _out0 = { name: data_element_name, texts: result, do_fallback: lv_do_fallback }; + z2ui5_cl_a2ui5_context.rtti_get_dtel_texts_by_ddic(_out0); + if ("texts" in _out0) result = _out0.texts; + if ("do_fallback" in _out0) lv_do_fallback = _out0.do_fallback; } catch (error) { - // TODO(abap2js): rtti_get_dtel_texts_by_xco( EXPORTING name = data_element_name IMPORTING texts = result do_fallback = lv_do_fallback ). + const _out1 = { name: data_element_name, texts: result, do_fallback: lv_do_fallback }; + z2ui5_cl_a2ui5_context.rtti_get_dtel_texts_by_xco(_out1); + if ("texts" in _out1) result = _out1.texts; + if ("do_fallback" in _out1) lv_do_fallback = _out1.do_fallback; } if ((lv_do_fallback === true || lv_do_fallback === `X`) && !result) { result.header = z2ui5_cl_util.abap_copy(val); @@ -952,17 +964,13 @@ class z2ui5_cl_a2ui5_context { return result; } - static rtti_get_dtel_texts_by_ddic({ name, texts, do_fallback } = {}) { + static rtti_get_dtel_texts_by_ddic(_args = {}) { + let { name, texts, do_fallback } = _args; let sy_subrc = 0; let fs_ddic = null; let _fs$fs_ddic = null; let ddic_ref = null; - // TODO(abap2js): DATA BEGIN OF ddic, - let reptext = ``; - let scrtext_s = ``; - let scrtext_m = ``; - let scrtext_l = ``; - // TODO(abap2js): DATA END OF ddic. + let ddic = { reptext: ``, scrtext_s: ``, scrtext_m: ``, scrtext_l: `` }; let struct_desrc = null; let lo_typedescr = null; let data_descr = null; @@ -975,11 +983,13 @@ class z2ui5_cl_a2ui5_context { if (!(sy_subrc === 0)) throw new Error(`ASSERT failed`); // TODO(abap2js): cl_abap_elemdescr=>describe_by_name( EXPORTING p_name = name RECEIVING p_descr_ref = lo_typedescr EXCEPTIONS OTHERS = 1 ). if (sy_subrc !== 0) { + Object.assign(_args, { texts, do_fallback }); return; } data_descr = z2ui5_cl_util.abap_copy(lo_typedescr); // TODO(abap2js): CALL METHOD data_descr->(`GET_DDIC_FIELD`) RECEIVING p_flddescr = EXCEPTIONS not_found = 1 no_ddic_type = 2 OTHERS = 3. if (sy_subrc !== 0) { + Object.assign(_args, { texts, do_fallback }); return; } // TODO(abap2js): MOVE-CORRESPONDING TO ddic. @@ -988,9 +998,11 @@ class z2ui5_cl_a2ui5_context { texts.medium = z2ui5_cl_util.abap_copy(ddic.scrtext_m); texts.long = z2ui5_cl_util.abap_copy(ddic.scrtext_l); do_fallback = true; + Object.assign(_args, { texts, do_fallback }); } - static rtti_get_dtel_texts_by_xco({ name, texts, do_fallback } = {}) { + static rtti_get_dtel_texts_by_xco(_args = {}) { + let { name, texts, do_fallback } = _args; let data_element = null; let content = null; let exists = false; @@ -1002,6 +1014,7 @@ class z2ui5_cl_a2ui5_context { // TODO(abap2js): CALL METHOD (lv_xco_cp_abap_dictionary)=>(`DATA_ELEMENT`) EXPORTING iv_name = name RECEIVING ro_data_element = data_element. // TODO(abap2js): CALL METHOD data_element->(`IF_XCO_AD_DATA_ELEMENT~EXISTS`) RECEIVING rv_exists = exists. if (!(exists === true || exists === `X`)) { + Object.assign(_args, { texts, do_fallback }); return; } // TODO(abap2js): CALL METHOD data_element->(`IF_XCO_AD_DATA_ELEMENT~CONTENT`) RECEIVING ro_content = content. @@ -1013,6 +1026,7 @@ class z2ui5_cl_a2ui5_context { } catch (error) { do_fallback = true; } + Object.assign(_args, { texts, do_fallback }); } static uuid_get_c32() { @@ -1073,7 +1087,7 @@ class z2ui5_cl_a2ui5_context { for (const symbol of fs_tab) { sy_tabix++; lt_tab = z2ui5_cl_a2ui5_context.msg_get_internal({ val: fs_row }); - result.push(...lt_tab); + result.push(...lt_tab.map((_r) => z2ui5_cl_util.abap_copy(_r))); } break; case cl_abap_datadescr.typekind_struct1: @@ -1098,7 +1112,7 @@ class z2ui5_cl_a2ui5_context { } if (ls_attri.name === `ITEM`) { lt_tab = z2ui5_cl_a2ui5_context.msg_get_internal({ val: fs_comp }); - result.push(...lt_tab); + result.push(...lt_tab.map((_r) => z2ui5_cl_util.abap_copy(_r))); return result; } else { ls_result = z2ui5_cl_a2ui5_context.msg_map({ name: ls_attri.name, val: fs_comp, is_msg: ls_result }); @@ -1108,14 +1122,14 @@ class z2ui5_cl_a2ui5_context { ls_result.id = ls_result.id.toUpperCase(); // TODO(abap2js): MESSAGE ID ls_result-id TYPE `I` NUMBER ls_result-no WITH ls_result-v1 ls_result-v2 ls_result-v3 ls_result-v4 INTO ls_result-text. } - result.push(ls_result); + result.push(z2ui5_cl_util.abap_copy(ls_result)); break; case cl_abap_datadescr.typekind_oref: result = z2ui5_cl_a2ui5_context.msg_get_by_oref({ val: val }); break; default: if (z2ui5_cl_a2ui5_context.rtti_check_clike({ val: val })) { - result.push({ text: val, id: ``, no: ``, type: ``, v1: ``, v2: ``, v3: ``, v4: ``, timestampl: null, t_meta: [] }); + result.push(z2ui5_cl_util.abap_copy({ text: val, id: ``, no: ``, type: ``, v1: ``, v2: ``, v3: ``, v4: ``, timestampl: 0, t_meta: [] })); } break; } @@ -1150,7 +1164,7 @@ class z2ui5_cl_a2ui5_context { } ls_result = z2ui5_cl_a2ui5_context.msg_map({ name: ls_attri_o.name, val: fs_comp, is_msg: ls_result }); } - result.push(ls_result); + result.push(z2ui5_cl_util.abap_copy(ls_result)); } catch (error) { let obj = null; obj = z2ui5_cl_util.abap_copy(val); @@ -1160,14 +1174,14 @@ class z2ui5_cl_a2ui5_context { // TODO(abap2js): ASSIGN lr_tab->* TO FIELD-SYMBOL(). // TODO(abap2js): CALL METHOD obj->(`IF_BALI_LOG~GET_ALL_ITEMS`) RECEIVING item_table = . lt_tab2 = z2ui5_cl_a2ui5_context.msg_get_internal({ val: fs_tab2 }); - result.push(...lt_tab2); + result.push(...lt_tab2.map((_r) => z2ui5_cl_util.abap_copy(_r))); } catch (error) { try { // TODO(abap2js): CREATE DATA lr_tab TYPE (`BAPIRETTAB`). // TODO(abap2js): ASSIGN lr_tab->* TO . // TODO(abap2js): CALL METHOD obj->(`ZIF_LOGGER~EXPORT_TO_TABLE`) RECEIVING rt_bapiret = . lt_tab2 = z2ui5_cl_a2ui5_context.msg_get_internal({ val: fs_tab2 }); - result.push(...lt_tab2); + result.push(...lt_tab2.map((_r) => z2ui5_cl_util.abap_copy(_r))); } catch (error) { lt_attri_o = z2ui5_cl_a2ui5_context.rtti_get_t_attri_by_oref({ val: val }); sy_tabix = 0; @@ -1181,7 +1195,7 @@ class z2ui5_cl_a2ui5_context { } ls_result = z2ui5_cl_a2ui5_context.msg_map({ name: ls_attri_o.name, val: fs_comp, is_msg: ls_result }); } - result.push(ls_result); + result.push(z2ui5_cl_util.abap_copy(ls_result)); } } } @@ -1300,7 +1314,9 @@ class z2ui5_cl_a2ui5_context { if (lv_kind !== cl_abap_datadescr.typekind_struct1 && lv_kind !== cl_abap_datadescr.typekind_struct2) { return result; } - // TODO(abap2js): msg_get_rap_row( EXPORTING val = val entity_name = entity_name IMPORTING messages = result is_row = DATA(lv_is_row) ). + const _out0 = { val, entity_name, messages: result, is_row: this.data(lv_is_row) }; + z2ui5_cl_a2ui5_context.msg_get_rap_row(_out0); + if ("messages" in _out0) result = _out0.messages; if ((lv_is_row === true || lv_is_row === `X`)) { return result; } @@ -1316,27 +1332,28 @@ class z2ui5_cl_a2ui5_context { fs_ftab = fs_tab; _fs$fs_ftab = null; sy_subrc = 0; - const _sy_tabix_1 = sy_tabix; + const _sy_tabix_2 = sy_tabix; sy_tabix = 0; for (const symbol of fs_ftab) { sy_tabix++; if (z2ui5_cl_a2ui5_context.rtti_get_type_kind({ val: fs_row }) === cl_abap_datadescr.typekind_oref) { if (fs_row) { try { - result.push(...z2ui5_cl_a2ui5_context.msg_get_t({ val: fs_row })); + result.push(...z2ui5_cl_a2ui5_context.msg_get_t({ val: fs_row }).map((_r) => z2ui5_cl_util.abap_copy(_r))); } catch (error) { } } } else { - result.push(...z2ui5_cl_a2ui5_context.msg_get_rap({ val: fs_row, entity_name: ls_attri.name })); + result.push(...z2ui5_cl_a2ui5_context.msg_get_rap({ val: fs_row, entity_name: ls_attri.name }).map((_r) => z2ui5_cl_util.abap_copy(_r))); } } - sy_tabix = _sy_tabix_1; + sy_tabix = _sy_tabix_2; } return result; } - static msg_get_rap_row({ val, entity_name, messages, is_row } = {}) { + static msg_get_rap_row(_args = {}) { + let { val, entity_name, messages, is_row } = _args; let sy_tabix = 0; let sy_subrc = 0; let fs_msg = null; @@ -1363,7 +1380,7 @@ class z2ui5_cl_a2ui5_context { sy_tabix++; fs_m.t_meta = z2ui5_cl_util.abap_copy(lt_meta); } - messages.push(...lt_one); + messages.push(...lt_one.map((_r) => z2ui5_cl_util.abap_copy(_r))); } catch (error) { } } @@ -1383,9 +1400,10 @@ class z2ui5_cl_a2ui5_context { if (entity_name) { lv_text = `${entity_name}: ${lv_text}`; } - messages.push({ type: `E`, text: lv_text, t_meta: lt_meta }); + messages.push(z2ui5_cl_util.abap_copy({ type: `E`, text: lv_text, t_meta: lt_meta })); } } + Object.assign(_args, { messages, is_row }); } static msg_get_rap_element({ val } = {}) { @@ -1543,27 +1561,27 @@ class z2ui5_cl_a2ui5_context { let lv = ``; lv = z2ui5_cl_a2ui5_context.msg_get_rap_element({ val: val }); if (lv) { - result.push({ n: `element`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `element`, v: lv })); } lv = z2ui5_cl_a2ui5_context.msg_get_rap_state_area({ val: val }); if (lv) { - result.push({ n: `state_area`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `state_area`, v: lv })); } lv = z2ui5_cl_a2ui5_context.msg_get_rap_action({ val: val }); if (lv) { - result.push({ n: `action`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `action`, v: lv })); } lv = z2ui5_cl_a2ui5_context.msg_get_rap_pid({ val: val }); if (lv) { - result.push({ n: `pid`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `pid`, v: lv })); } lv = z2ui5_cl_a2ui5_context.msg_get_rap_cid({ val: val }); if (lv) { - result.push({ n: `cid`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `cid`, v: lv })); } lv = z2ui5_cl_a2ui5_context.msg_get_rap_tky({ val: val }); if (lv) { - result.push({ n: `tky`, v: lv }); + result.push(z2ui5_cl_util.abap_copy({ n: `tky`, v: lv })); } return result; } diff --git a/run/input/core/srv/z2ui5/00/03/z2ui5_cl_a2ui5_json_fltr.js b/run/input/core/srv/z2ui5/00/03/z2ui5_cl_a2ui5_json_fltr.js index 4ad9f7b..f5600e4 100644 --- a/run/input/core/srv/z2ui5/00/03/z2ui5_cl_a2ui5_json_fltr.js +++ b/run/input/core/srv/z2ui5/00/03/z2ui5_cl_a2ui5_json_fltr.js @@ -8,7 +8,8 @@ class z2ui5_cl_a2ui5_json_fltr { return result; } - keep_node() { + keep_node({ is_node, iv_visit = z2ui5_if_ajson_filter.visit_type.value } = {}) { + let rv_keep = false; rv_keep = true; switch (iv_visit) { case z2ui5_if_ajson_filter.visit_type.value: @@ -28,6 +29,7 @@ class z2ui5_cl_a2ui5_json_fltr { rv_keep = (is_node.children !== 0); break; } + return rv_keep; } } diff --git a/run/input/core/srv/z2ui5/99/02/z2ui5_cl_pop_messages.js b/run/input/core/srv/z2ui5/99/02/z2ui5_cl_pop_messages.js index 50e9ba3..f89a6df 100644 --- a/run/input/core/srv/z2ui5/99/02/z2ui5_cl_pop_messages.js +++ b/run/input/core/srv/z2ui5/99/02/z2ui5_cl_pop_messages.js @@ -15,7 +15,7 @@ class z2ui5_cl_pop_messages extends z2ui5_if_app { sy_tabix = 0; for (const lr_row of z2ui5_cl_a2ui5_context.msg_get_t(i_messages)) { sy_tabix++; - r_result.mt_msg.push({ type: z2ui5_cl_a2ui5_context.ui5_get_msg_type(lr_row.type), title: lr_row.text, subtitle: `${lr_row.id} ${lr_row.no}` }); + r_result.mt_msg.push(z2ui5_cl_util.abap_copy({ type: z2ui5_cl_a2ui5_context.ui5_get_msg_type(lr_row.type), title: lr_row.text, subtitle: `${lr_row.id} ${lr_row.no}` })); } r_result.title = z2ui5_cl_util.abap_copy(i_title); return r_result; diff --git a/scripts/mirror-core.js b/scripts/mirror-core.js index 6a872bb..b34cb79 100644 --- a/scripts/mirror-core.js +++ b/scripts/mirror-core.js @@ -57,19 +57,25 @@ function pinnedSha() { return /^[0-9a-f]{40}$/.test(sha) ? sha : null; } +// git fetch/clone may spawn a DETACHED auto-gc that still writes into +// .mirror_tmp/.git while the finally-cleanup deletes it (observed as +// ENOTEMPTY in update_cap) — disable auto-gc and retry the rm. +const GIT_NO_GC = ["-c", "gc.auto=0", "-c", "gc.autoDetach=false", "-c", "maintenance.auto=false"]; +const rmTmp = () => fs.rmSync(tmp, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }); + const local = process.env.MIRROR_SOURCE; if (local) { const commit = execFileSync("git", ["-C", local, "rev-parse", "HEAD"]).toString().trim(); copyCore(local, commit); } else { - fs.rmSync(tmp, { recursive: true, force: true }); + rmTmp(); try { - execFileSync("git", ["clone", "--depth", "1", UPSTREAM, tmp], { stdio: "inherit" }); + execFileSync("git", [...GIT_NO_GC, "clone", "--depth", "1", UPSTREAM, tmp], { stdio: "inherit" }); const head = execFileSync("git", ["-C", tmp, "rev-parse", "HEAD"]).toString().trim(); const pin = pinnedSha(); if (pin && pin !== head) { try { - execFileSync("git", ["-C", tmp, "fetch", "--depth", "1", "origin", pin], { stdio: "inherit" }); + execFileSync("git", ["-C", tmp, ...GIT_NO_GC, "fetch", "--depth", "1", "origin", pin], { stdio: "inherit" }); const date = (sha) => Number(execFileSync("git", ["-C", tmp, "show", "-s", "--format=%ct", sha]).toString().trim()); if (date(pin) > date(head)) { @@ -85,6 +91,6 @@ if (local) { const commit = execFileSync("git", ["-C", tmp, "rev-parse", "HEAD"]).toString().trim(); copyCore(tmp, commit); } finally { - fs.rmSync(tmp, { recursive: true, force: true }); + rmTmp(); } }