From 0cb8def595af60c05442a967bfcd7c02a1f0e114 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 09:37:58 +0000 Subject: [PATCH] ixml parser: keep a line feed inside a value if_ixml_parser~parse removed every literal LF from the document before it tokenized it, so a value with a line break came back as one line - a string does not survive `CALL TRANSFORMATION id` and the parse back, and nothing writes a LF as a character reference either. The strip is not needed: the loop already skips whitespace between tags (SHIFT ... DELETING LEADING, and get_simple_spaces_for_cur_cp contains newline and CR), so removing it leaves the document structure as it was and keeps the LF where it belongs - in the text node. Tests: the asXML roundtrip of a two-line value, and the parser keeping the LF in a text node. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TKSWLpid4QLV6XWAoPixbb --- src/ixml/cl_ixml.clas.locals_imp.abap | 2 +- src/ixml/cl_ixml.clas.testclasses.abap | 16 +++++++++++ ..._call_transformation.clas.testclasses.abap | 27 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/ixml/cl_ixml.clas.locals_imp.abap b/src/ixml/cl_ixml.clas.locals_imp.abap index 1fd3bbf2..03d8ee0b 100644 --- a/src/ixml/cl_ixml.clas.locals_imp.abap +++ b/src/ixml/cl_ixml.clas.locals_imp.abap @@ -1331,7 +1331,7 @@ CLASS lcl_parser IMPLEMENTATION. lv_xml = lv_xml+1. ENDIF. - REPLACE ALL OCCURRENCES OF |\n| IN lv_xml WITH ||. +* newline handling: whitespace between tags is skipped below, a LF inside a value is kept WHILE lv_xml IS NOT INITIAL. CLEAR lo_node. diff --git a/src/ixml/cl_ixml.clas.testclasses.abap b/src/ixml/cl_ixml.clas.testclasses.abap index 5f530e53..d95cb4a0 100644 --- a/src/ixml/cl_ixml.clas.testclasses.abap +++ b/src/ixml/cl_ixml.clas.testclasses.abap @@ -17,6 +17,7 @@ CLASS ltcl_xml DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT FINAL. METHODS parse_basic FOR TESTING RAISING cx_static_check. METHODS root_element_after_crlf FOR TESTING RAISING cx_static_check. METHODS first_child_after_crlf FOR TESTING RAISING cx_static_check. + METHODS parse_value_with_newline FOR TESTING RAISING cx_static_check. METHODS parse_bom FOR TESTING RAISING cx_static_check. METHODS parse_empty FOR TESTING RAISING cx_static_check. METHODS parse_namespace FOR TESTING RAISING cx_static_check. @@ -458,6 +459,21 @@ CLASS ltcl_xml IMPLEMENTATION. ENDMETHOD. + METHOD parse_value_with_newline. + + DATA lv_xml TYPE string. + DATA li_root TYPE REF TO if_ixml_element. + + lv_xml = |a\nb|. + + li_root = parse( lv_xml )->get_root_element( ). + + cl_abap_unit_assert=>assert_equals( + act = li_root->get_first_child( )->get_value( ) + exp = |a\nb| ). + + ENDMETHOD. + METHOD parse_bom. DATA lv_bom TYPE c LENGTH 1. diff --git a/src/kernel/call_transformation/kernel_call_transformation.clas.testclasses.abap b/src/kernel/call_transformation/kernel_call_transformation.clas.testclasses.abap index c436a454..e6f9b0e5 100644 --- a/src/kernel/call_transformation/kernel_call_transformation.clas.testclasses.abap +++ b/src/kernel/call_transformation/kernel_call_transformation.clas.testclasses.abap @@ -96,6 +96,7 @@ CLASS ltcl_call_transformation DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATI METHODS escape_char_data FOR TESTING RAISING cx_static_check. METHODS escape_char_data_roundtrip FOR TESTING RAISING cx_static_check. METHODS escape_entity_text_roundtrip FOR TESTING RAISING cx_static_check. + METHODS newline_roundtrip FOR TESTING RAISING cx_static_check. METHODS to_string_simple FOR TESTING RAISING cx_static_check. METHODS to_string_empty FOR TESTING RAISING cx_static_check. METHODS to_string_array FOR TESTING RAISING cx_static_check. @@ -336,6 +337,32 @@ CLASS ltcl_call_transformation IMPLEMENTATION. exp = 'literal < entity' ). ENDMETHOD. + METHOD newline_roundtrip. +* a line break inside a value must survive the roundtrip + DATA lv_xml TYPE string. + DATA lv_exp TYPE string. + DATA: BEGIN OF ls_data, + field TYPE string, + END OF ls_data. + + lv_exp = |a{ cl_abap_char_utilities=>newline }b|. + ls_data-field = lv_exp. + + CALL TRANSFORMATION id + SOURCE data = ls_data + RESULT XML lv_xml. + + CLEAR ls_data. + + CALL TRANSFORMATION id + SOURCE XML lv_xml + RESULT data = ls_data. + + cl_abap_unit_assert=>assert_equals( + act = ls_data-field + exp = lv_exp ). + ENDMETHOD. + METHOD convert_json_to_sxml. DATA lo_writer TYPE REF TO cl_sxml_string_writer. lo_writer = cl_sxml_string_writer=>create( ).