I always struggle with the macro quoting.
Why does this work and the following modification does not???
%let kept=Arne Bert Hansi;
%macro quote_names(name_list);
%local i name;
%GLOBAL quoted_names;
%let quoted_names = ;
%let i = 1;
%do %while(%scan(&name_list, &i) ne );
%let name = %scan(&name_list, &i);
%let quoted_names = "ed_names %str(%')&name%str(%')%str(, );
%let i = %eval(&i + 1);
%end;
%let quoted_names = %substr("ed_names, 1, %length("ed_names) - 2);
%mend quote_names;
%quote_names(%str(&kept));
%put %STR("ed_names);
'Arne', 'Bert', 'Hansi'
But if I want to leave only the spaces without the comma, then it fails stating
ERROR: Literal contains unmatched quote.
ERROR: The macro QUOTE_NAMES will stop executing.
%let kept=Arne Bert Hansi;
%macro quote_names(name_list);
%local i name;
%GLOBAL quoted_names;
%let quoted_names = ;
%let i = 1;
%do %while(%scan(&name_list, &i) ne );
%let name = %scan(&name_list, &i);
%let quoted_names = "ed_names %str(%')&name%str(%')%str( );
%let i = %eval(&i + 1);
%end;
%let quoted_names = %substr("ed_names, 1, %length("ed_names) - 2);
%mend quote_names;
%quote_names(%str(&kept));
%put %STR("ed_names);
You might want to use the %QLIST macro, which is much easier than writing your own macro. I have used %QLIST for years and it works as desired.
Your particular problem can be solved by using %UNQUOTE
%let quoted_names = %unquote(%substr("ed_names, 1, %length("ed_names) - 2));
Why does %UNQUOTE work here? I have trouble explaining it other than to say when it looks like you have coded everything properly and you still get errors, and it usually involves a case where you created a quoted string in the macro, try %UNQUOTE. Or perhaps Mr. @Astounding can explain, you can search for his explanation of this in previous threads, or buy his book (the name of which I have forgotten) which also explains.
@PaigeMiller , thanks for the mention. Unfortunately the book "Macro Language Magic" is now out of print, and I have been retired and without SAS for more than 5 years now.
Off the top of my head, the most common "mysterious" need for %unquote is when macro language creates a list of quoted items for use in a WHERE clause within SQL code. Many times the software figures out to unquote a list, but not when SQL is involved.
Book still appears to be on sale at Amazon. https://www.amazon.com/SAS-Macro-Language-Magic-Discovering/dp/1612907105
Hello @acordes,
@acordes wrote:
Why does this work and the following modification does not???
This is because you left the final %LET statement in the macro, which had the purpose of deleting the unwanted %str(, ) after the last word, but which in the modified macro deletes the unwanted trailing blank plus the important closing quotation mark.
Solution: Delete that final %LET statement entirely and also the trailing %str( ) earlier in the code. (Note that you already get the separating blanks by inserting them before the newly appended name -- and the %LET statement automatically avoids creating a leading blank before the first name.)
do you need macro for the process?
how about data step?
%let kept=Arne Bert Hansi;
data _null_;
length str result $ 32767 word $ 128;
str=symget('kept');
sep = " "; /* space, but you can set whatever you like */
do i=1 by 1 while(NOT quit);
word = scan(str, i, " ");
if word NE " " then result = catX(sep, result, quote(strip(word)) );
else quit=1;
end;
call symputX("quoted_names", result, "G");
run;
%put "ed_names.;
Bart
O course DoSubL() function can help in wrapping it into a macro:
%let kept=Arne Bert Hansi;
%macro addQuotes(kept);
%local rc quoted_names;
%let rc=%sysfunc(dosubl(%str(
data _null_;
length str result $ 32767 word $ 128;
str=symget("kept");
sep = " "; /* space */
do i=1 by 1 while(NOT quit);
word = scan(str, i, " ");
if word NE " " then result = catX(sep, result, quote(strip(word)) );
else quit=1;
end;
call symputX("quoted_names", result, "L");
run;
)));
"ed_names.
%mend addQuotes;
%put %addQuotes(&kept.);
Bart
I find it usually works better to only add the separator before the next word.
Also an iterative DO loop are much,much easier to code and understand than a DO WHILE loop where you have to manually increment the counter.
%macro quote_names(name_list);
%local i name sep;
%GLOBAL quoted_names;
%let quoted_names = ;
%let sep=;
%do i=1 %to %sysfunc(countw(&name_list,%str( )));
%let name = %scan(&name_list, &i,%str( ));
%let quoted_names = "ed_names.&sep.%str(%')&name%str(%');
%let sep = %str(, );
%end;
%let quoted_names = %unquote("ed_names);
%mend quote_names;
Let's try it out.
14 %quote_names(a b); 15 %put |"ed_names|; |'a', 'b'| 16 %quote_names( ); 17 %put |"ed_names|; ||
Hey @acordes! I've got a macro and a function called cquote that was greatly inspired by @Tom's work and it has served me well over the years:
https://github.com/stu-code/sas/blob/master/utility-macros/cquote.sas
%macro cquote(strlist, quote);
%if(%upcase("e) = SINGLE) %then %let q = %str(%');
%else %let q = %str(%");
%unquote(%bquote(&q)%qsysfunc(tranwrd(%qsysfunc(compbl(%superq(strlist))),%bquote( ),%bquote(&q,&q)))%bquote(&q))
%mend;
proc fcmp outlib=work.funcs.str;
/* Double quote version */
function cquote(str$) $200;
return (cats('"', tranwrd(compbl(str),' ','","'), '"'));
endfunc;
/* Single quote version */
function scquote(str$) $200;
return (cats("'", tranwrd(compbl(str),' ',"','"), "'"));
endfunc;
run;
You can give these a try as well.
One more option just for fun, with FCMP function called in %SYSFUNC()
proc fcmp outlib=work.f.p;
function addQuotes(str $,s $, q) $ 32767;
length result $ 32767 sep word Usep $ 128 qt $ 1;
sep = s;
Usep = upcase(sep);
select;
when (Usep=" ") sep=" ";
when (Usep="C") sep=",";
when (Usep="SC") sep=";";
when (Usep="P") sep=".";
when (Usep=:"S") sep=substr(sep,2); /* string up to 127 bytes */
otherwise sep = " ";
end;
select(q);
when(2) qt='"';
when(1) qt="'";
otherwise qt=" ";
end;
quit=0;
do i = 1 by 1 while(NOT quit);
word = scan(str, i, " ");
if NOT missing(word) then
do;
if NOT (qt=" ") then word=quote(strip(word),qt);
result = catX(ifc(sep=" "," ",strip(sep)), result, strip(word));
end;
else quit=1;
end;
return(result);
endfunc;
quit;
options cmplib=work.f;
%let kept=Arne Bert Hansi;
%put %sysfunc(addQuotes(&kept.,C,2));
%put %sysfunc(addQuotes(&kept.,S!,0));
data _null_;
kept="Arne Bert Hansi";
str=addQuotes(kept,"C",2);
put kept= / str=;
str=addQuotes(kept,"SC",0);
put kept= / str=;
run;
Bart
Yet another approach: see Richard DeVenezia's %seplist() macro. It takes a list of items, and allows you to add quotes, or delimiters, or prefixes/suffixes, and more.
https://www.devenezia.com/downloads/sas/macros/index.php?m=seplist
You could also do this, for with and without commas, respectively:
data _null_;
call symputx("w_commas",tranwrd(quote(tranwrd("&kept",' ',quote(', '))),'""','"'));
call symputx("wo_commas",tranwrd(quote(tranwrd("&kept",' ',quote(' '))),'""','"'));
run;
%put &=w_commas;
%put &=wo_commas;
Results:
Multiple spaces can be a bit of a problem here:
%let kept=Arne Bert Hansi;
gives
46 data _null_;
47 call symputx("w_commas",tranwrd(quote(tranwrd("&kept",' ',quote(', '))),'""','"'));
48 call symputx("wo_commas",tranwrd(quote(tranwrd("&kept",' ',quote(' '))),'""','"'));
49 run;
NOTE: DATA statement used (Total process time):
real time 0.01 seconds
cpu time 0.01 seconds
50
51 %put &=w_commas;
W_COMMAS="Arne", "", "", "", "Bert", "", "Hansi"
52 %put &=wo_commas;
WO_COMMAS="Arne" "" "" "" "Bert" "" "Hansi"
Bart
or compbl() function
It's finally time to hack! Remember to visit the SAS Hacker's Hub regularly for news and updates.
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.