Skip to content

ixml parser: keep a line feed inside a value - #1225

Merged
larshp merged 1 commit into
open-abap:mainfrom
abap2UI5:claude/nifty-meitner-ema3y8
Sep 15, 2026
Merged

larshp merged 1 commit into
open-abap:mainfrom
abap2UI5:claude/nifty-meitner-ema3y8

Conversation

@oblomov-dev

Copy link
Copy Markdown
Contributor

What is wrong

if_ixml_parser~parse removes every literal line feed from the document
before it starts tokenizing, src/ixml/cl_ixml.clas.locals_imp.abap:

REPLACE ALL OCCURRENCES OF |\n| IN lv_xml WITH ||.

A LF is not markup, it is character data, so the strip also hits the text
nodes. Nothing writes a LF as a character reference on the way out either
(lcl_data_to_xml=>escape_text writes &, < and >, added in #1193, and
leaves the LF as it is), so there is no form in which a line break survives
the document.

The visible effect is the asXML roundtrip: a two-line value comes back as one
line.

DATA lv_xml TYPE string.
DATA: BEGIN OF ls_data,
        field TYPE string,
      END OF ls_data.

ls_data-field = |a{ cl_abap_char_utilities=>newline }b|.

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.

" system: `a\nb`, here: `ab`

The change

The strip is not needed. The parse loop already skips the whitespace between
tags itself, at the end of every iteration:

lv_rest = lv_xml.
SHIFT lv_rest LEFT DELETING LEADING lv_whitespace.
IF lv_rest IS INITIAL OR lv_rest(1) = '<'.
  lv_xml = lv_rest.
ENDIF.

and lv_whitespace is cl_abap_char_utilities=>get_simple_spaces_for_cur_cp( ),
which contains newline and the CR. So a pretty-printed document is read
exactly as before — the indentation is dropped because it is whitespace
between tags, not because a LF was removed from the string. What changes is
only the text node: the line break stays where a system keeps it.

One line removed. The write side is untouched — a LF is legal raw in character
data, so no escaping is added and every value serializes byte for byte as it
does today.

Tests

  • kernel_call_transformation: newline_roundtrip — the two-line value above,
    through CALL TRANSFORMATION id and back
  • cl_ixml: parse_value_with_newline — the parser keeps the LF in the
    #text node

The two existing tests that cover this area, root_element_after_crlf and
first_child_after_crlf, are unchanged and still pass.

npm test is green: abaplint: 0 issue(s) found, 735 file(s) analyzed, and
the full unit run passes.

Why it matters downstream

abap2UI5 is stateless per request: the app instance — every attribute of the
app class, including the strings bound to a TextArea — is serialized with
CALL TRANSFORMATION id into the draft and parsed back on the next request
(z2ui5_cl_ui5_util_context=>xml_stringify / xml_parse). On the transpiled
backend that means the user types two lines and the next request has one.
abap2UI5 carries a local pre-transpile patch for this today; with this merged
the last one goes away.

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TKSWLpid4QLV6XWAoPixbb
@larshp
larshp merged commit a7ddd45 into open-abap:main Sep 15, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants