From e24ef4c6fc8105343b6338fb9ed3c9fd634d028c Mon Sep 17 00:00:00 2001 From: 4gl <@> Date: Wed, 22 Jul 2026 16:29:18 +0100 Subject: [PATCH] fix: ensure leading blanks are always imported --- base/mp_jsonout.sas | 3 +- meta/mm_webout.sas | 5 ++- server/ms_webout.sas | 5 ++- tests/base/mp_jsonout.test.3.sas | 11 +++++ tests/serveronly/ms_webout.test.sas | 51 ++++++++++++++++++++++++ tests/x-platform/mx_testservice.test.sas | 5 ++- viya/mv_webout.sas | 10 ++++- 7 files changed, 83 insertions(+), 7 deletions(-) diff --git a/base/mp_jsonout.sas b/base/mp_jsonout.sas index f4bb74b..6a341cd 100644 --- a/base/mp_jsonout.sas +++ b/base/mp_jsonout.sas @@ -331,7 +331,8 @@ prxchange('s/\\/\\\\/',-1,&&name&i) )))))))))))))!!'"'; end; - else &&name&i=quote(cats(&&name&i)); + /* trim (not cats) so leading blanks are retained */ + else &&name&i='"'!!trim(&&name&i)!!'"'; %end; %end; run; diff --git a/meta/mm_webout.sas b/meta/mm_webout.sas index f41af4f..653388e 100644 --- a/meta/mm_webout.sas +++ b/meta/mm_webout.sas @@ -77,7 +77,10 @@ data _null_; infile &&_webin_fileref&i termstr=crlf; input; - call symputx('input_statement',_infile_); + /* a plain $ informat strips leading blanks - use $char instead */ + call symputx('input_statement' + ,prxchange('s/:\$(?=[0-9 ])/:\$char/i',-1,_infile_) + ); putlog "&&_webin_name&i input statement: " _infile_; stop; data &&_webin_name&i; diff --git a/server/ms_webout.sas b/server/ms_webout.sas index 0a6e253..216a18f 100644 --- a/server/ms_webout.sas +++ b/server/ms_webout.sas @@ -74,7 +74,10 @@ data _null_; infile &&_webin_fileref&i termstr=crlf lrecl=32767; input; - call symputx('input_statement',_infile_); + /* a plain $ informat strips leading blanks - use $char instead */ + call symputx('input_statement' + ,prxchange('s/:\$(?=[0-9 ])/:\$char/i',-1,_infile_) + ); putlog "&&_webin_name&i input statement: " _infile_; stop; data &&_webin_name&i; diff --git a/tests/base/mp_jsonout.test.3.sas b/tests/base/mp_jsonout.test.3.sas index ea98eb6..7ee5af2 100644 --- a/tests/base/mp_jsonout.test.3.sas +++ b/tests/base/mp_jsonout.test.3.sas @@ -11,9 +11,20 @@ filename webref temp; data demo; + length x $100; do x='"','0A'x,'0D'x,'09'x,'00'x,'0E'x,'0F'x,'01'x,'02'x,'10'x,'11'x,'\'; output; end; + /* embedded quote variants */ + x='say "hi" there'; output; + x='"fully quoted"'; output; + x='back\slash'; output; + x='quote and back\"slash'; output; + /* leading / trailing blank variants */ + x=' leading blanks'; output; + x=' "leading blanks and quotes"'; output; + x='trailing blanks '; output; + x=' both '; output; run; %mp_jsonout(OPEN,jref=webref) %mp_jsonout(OBJ,demo,jref=webref) diff --git a/tests/serveronly/ms_webout.test.sas b/tests/serveronly/ms_webout.test.sas index bd68244..dff7f2f 100644 --- a/tests/serveronly/ms_webout.test.sas +++ b/tests/serveronly/ms_webout.test.sas @@ -32,4 +32,55 @@ run; %mp_assert( iftrue=(%str(&checkval)=%str(&sysvlong)), desc=Check if the sysvlong value was created +) + +/* + Test that ms_webout(FETCH) retains leading blanks in character values + (simulates the CSV format generated by the sasjs adapter, ie an input + statement in the first row followed by unquoted data) +*/ +%let fref2=%mf_getuniquefileref(); +data _null_; + file &fref2 lrecl=32767 termstr=crlf; + put 'col1:$char10. col2:best.'; + put ' padded,1'; +run; +%global _webin_fileref _webin_name; +%let _webin_fileref=&fref2; +%let _webin_name=leadblank; +%let _webin_file_count=1; +%ms_webout(FETCH) + +data _null_; + set leadblank; + if col1=' padded' then call symputx('checkblank','PASS'); + else call symputx('checkblank',cats('FAIL:',col1)); +run; + +%mp_assert( + iftrue=(&checkblank=PASS), + desc=ms_webout FETCH retains leading blanks with $char informat +) + +/* same test, but with a plain $ informat (as sent by some clients) */ +%let fref3=%mf_getuniquefileref(); +data _null_; + file &fref3 lrecl=32767 termstr=crlf; + put 'col1:$10. col2:best.'; + put ' padded,1'; +run; +%let _webin_fileref=&fref3; +%let _webin_name=leadblank2; +%let _webin_file_count=1; +%ms_webout(FETCH) + +data _null_; + set leadblank2; + if col1=' padded' then call symputx('checkblank2','PASS'); + else call symputx('checkblank2',cats('FAIL:',col1)); +run; + +%mp_assert( + iftrue=(&checkblank2=PASS), + desc=ms_webout FETCH retains leading blanks with $ informat ) \ No newline at end of file diff --git a/tests/x-platform/mx_testservice.test.sas b/tests/x-platform/mx_testservice.test.sas index 28e7e89..ee82b56 100644 --- a/tests/x-platform/mx_testservice.test.sas +++ b/tests/x-platform/mx_testservice.test.sas @@ -44,6 +44,7 @@ data work.somedata1 work.somedata2; x=1; y=' t"w"o'; z=.z; + y2=' two'; label x='x factor'; output; run; @@ -58,14 +59,14 @@ run; %let test1=FAIL; data _null_; set testlib1.somedata1; - if x=1 and y=' t"w"o' and z="Z" then call symputx('test1','PASS'); + if x=1 and y=' t"w"o' and z="Z" and y2=' two' then call symputx('test1','PASS'); putlog (_all_)(=); run; %let test2=FAIL; data _null_; set testlib1.somedata2; - if x=1 and y=' t"w"o' and z="Z" then call symputx('test2','PASS'); + if x=1 and y=' t"w"o' and z="Z" and y2=' two' then call symputx('test2','PASS'); putlog (_all_)(=); run; diff --git a/viya/mv_webout.sas b/viya/mv_webout.sas index 96901c7..daa1bd2 100644 --- a/viya/mv_webout.sas +++ b/viya/mv_webout.sas @@ -104,7 +104,10 @@ data _null_; infile "%sysfunc(pathname(work))/&table..csv" termstr=crlf ; input; - if _n_=1 then call symputx('input_statement',_infile_); + /* a plain $ informat strips leading blanks - use $char instead */ + if _n_=1 then call symputx('input_statement' + ,prxchange('s/:\$(?=[0-9 ])/:\$char/i',-1,_infile_) + ); list; data work.&table; infile "%sysfunc(pathname(work))/&table..csv" firstobs=2 dsd @@ -124,7 +127,10 @@ data _null_; infile indata termstr=crlf lrecl=32767; input; - if _n_=1 then call symputx('input_statement',_infile_); + /* a plain $ informat strips leading blanks - use $char instead */ + if _n_=1 then call symputx('input_statement' + ,prxchange('s/:\$(?=[0-9 ])/:\$char/i',-1,_infile_) + ); %if %str(&_debug) ge 128 %then %do; if _n_<20 then putlog _infile_; else stop;