I am new to the metadata concept and SAS DI. I need to query the foundation repository metadata and list out all the objects from it. Can someone please suggest how to achieve that?
Thanks
Update - I wrote a macro lately that will download all objects from a metadata repository, so does exactly what you need.
/* compile macros*/
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
/* run code */
%mm_tree()
Full source code below.
* use a temporary fileref to hold the response;
filename response temp;
/* get list of libraries */
proc metadata in=
'<GetMetadataObjects><Reposid>$METAREPOSITORY</Reposid>
<Type>Tree</Type><Objects/><NS>SAS</NS>
<Flags>384</Flags>
<XMLSelect search="*[#TreeType='BIP Folder']"/>
<Options/></GetMetadataObjects>'
out=response;
run;
/*
data _null_;
infile response;
input;
put _infile_;
run;
*/
/* create an XML map to read the response */
filename sxlemap temp;
data _null_;
file sxlemap;
put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree</TABLE-PATH>";
put '<COLUMN name="pathuri">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree/#Id</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>64</LENGTH>";
put '</COLUMN><COLUMN name="name">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree/#Name</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>256</LENGTH>";
put '</COLUMN></TABLE></SXLEMAP>';
run;
libname _XML_ xml xmlfileref=response xmlmap=sxlemap;
data &outds;
length metauri pathuri $64 name $256 path $1024
publictype MetadataUpdated MetadataCreated $32;
set _XML_.SASObjects;
keep metauri name publictype MetadataUpdated MetadataCreated path;
length parenturi pname $128 ;
call missing(parenturi,pname);
path=cats('/',name);
/* get parents */
tmpuri=pathuri;
do while (metadata_getnasn(tmpuri,"ParentTree",1,parenturi)>0);
rc=metadata_getattr(parenturi,"Name",pname);
path=cats('/',pname,path);
tmpuri=parenturi;
end;
if path=:"&root";
%if "&types"="ALL" or ("&types" ne "ALL" and "&types" ne "Folder") %then %do;
n=1;
do while (metadata_getnasn(pathuri,"Members",n,metauri)>0);
n+1;
call missing(name,publictype,MetadataUpdated,MetadataCreated);
rc=metadata_getattr(metauri,"Name", name);
rc=metadata_getattr(metauri,"MetadataUpdated", MetadataUpdated);
rc=metadata_getattr(metauri,"MetadataCreated", MetadataCreated);
rc=metadata_getattr(metauri,"PublicType", PublicType);
%if "&types" ne "ALL" %then %do;
if publictype in (%mf_getquotedstr(&types)) then output;
%end;
%else output; ;
end;
%end;
rc=metadata_resolve(pathuri,pname,tmpuri);
metauri=cats('OMSOBJ:',pname,'\',pathuri);
rc=metadata_getattr(metauri,"Name", name);
rc=metadata_getattr(pathuri,"MetadataUpdated", MetadataUpdated);
rc=metadata_getattr(pathuri,"MetadataCreated", MetadataCreated);
rc=metadata_getattr(pathuri,"PublicType", PublicType);
path=substr(path,1,length(path)-length(name)-1);
if publictype ne '' then output;
run;
proc sort;
by path;
run;
Related
i found a code that list all directories and sub directories from a path.
but it brings only the directory and the name of the file.
could you guys please help me how to bring the owner and the file size?
%macro list_files(dir);
%local filrf rc did memcnt name i;
%let rc=%sysfunc(filename(filrf,&dir));
%let did=%sysfunc(dopen(&filrf));
%if &did eq 0 %then %do;
%put Directory &dir cannot be open or does not exist;
%return;
%end;
%do i = 1 %to %sysfunc(dnum(&did));
%let name=%qsysfunc(dread(&did,&i));
%if %index(%qscan(&name,-1,'/'),.) gt 0 %then %do;
data _tmp;
length dir $512 name $100;
dir=symget("dir");
name=symget("name");
run;
proc append base=want data=_tmp;
run;quit;
%end;
%else %if %qscan(&name,2,.) = %then %do;
%list_files(&dir/&name)
%end;
%end;
%let rc=%sysfunc(dclose(&did));
%let rc=%sysfunc(filename(filrf));
%mend list_files;
DOPTNAME is your friend here.
Read SAS documentation for "Example 1: Using DOPTNAME to Retrieve Directory Attribute Information"
This example opens the directory with the fileref MYDIR, retrieves all system-dependent directory information items, writes them to the SAS log, and closes the directory:
%let rc=%sysfunc(filename(filrf, physical-name));
%let did=%sysfunc(dopen(&filrf));
%let infocnt=%sysfunc(doptnum(&did));
%do j=1 %to &infocnt;
%let opt=%sysfunc(doptname(&did, &j));
%put Directory information=&opt;
%end;
%let rc=%sysfunc(dclose(&did));
%macro test;
%let filrf=mydir;
%let rc=%sysfunc(filename(filrf, physical-name));
%let did=%sysfunc(dopen(&filrf));
%let infocnt=%sysfunc(doptnum(&did));
%do j=1 %to &infocnt;
%let opt=%sysfunc(doptname(&did, &j));
%put Directory information=&opt;
%end;
%let rc=%sysfunc(dclose(&did));
%mend test;
%test
Use the finfo() function. You can do this all in a single data step.
Documentation
/* Macro variable to store the directory. Do not keep ending slash. */
%let directory = /my/directory;
filename mydir "&directory";
data file_list;
length directory
filetype $15.
filename $1000.
owner $100.
size 8.
;
directory = "&directory/";
/* Open the directory */
did = dopen("mydir");
/* If the directory exists, loop through all files in the directory */
if(did) then do;
do i = 1 to dnum(did);
/* Get the filename */
filename = dread(did, i);
/* Create a filename variable and create a file ID to read its attributes */
rc = filename('infile', cats(directory,filename));
fid = fopen('infile');
owner = finfo(fid, 'Owner Name');
size = finfo(fid, 'File Size (bytes)');
/* Flag if it's a directory or file */
if(missing(size)) then filetype = 'Directory';
else filetype = 'File';
/* Close the file */
rc = fclose(fid);
output;
end;
end;
/* Close the directory */
rc = close(did);
keep directory filename owner size filetype;
run;
I have a SAS dataset where I keep 50 diagnoses codes and 50 diagnoses descriptions.
It looks something like this:
data diags;
set diag_list;
keep claim_id diagcode1-diagcode50 diagdesc1-diagdesc50;
run;
I need to print all of the variables but I need diagnosis description right next to corresponding diagnosis code. Something like this:
proc print data=diags;
var claim_id diagcode1 diagdesc1 diagcode2 diagdesc2 diagcode3 diagdesc3; *(and so on all the way to 50);
run;
Is there a way to do this (possibly using arrays) without having to type it all up?
Here's one approach then, using Macros. If you have other variables make sure to include them BEFORE the %loop_names(n=50) portion in the VAR statement.
*generate fake data to test/run solution;
data demo;
array diag(50);
array diagdesc(50);
do claim_id=1 to 100;
do i=1 to 50;
diag(i)=rand('normal');
diagdesc(i)=rand('uniform');
end;
output;
end;
run;
%macro loop_names(n=);
%do i=1 %to &n;
diag&i diagdesc&i.
%end;
%mend;
proc print data=demo;
var claim_ID %loop_names(n=20);
run;
Here is some example SAS code that uses actual ICD 10 CM codes and their descriptions and #Reeza proc print:
%* Copy government provided Medicare code data zip file to local computer;
filename cms_cm url 'https://www.cms.gov/Medicare/Coding/ICD10/Downloads/2020-ICD-10-CM-Codes.zip' recfm=s;
filename zip_cm "%sysfunc(pathname(work))/2020-ICD-10-CM-Codes.zip" lrecl=200000000 recfm=n ;
%let rc = %sysfunc(fcopy(cms_cm, zip_cm));
%put %sysfunc(sysmsg());
%* Define fileref to the zip file member that contains ICD 10 CM codes and descriptions;
filename cm_codes zip "%sysfunc(pathname(zip_cm))" member="2020 Code Descriptions/icd10cm_codes_2020.txt";
%* input the codes and descriptions, there are 72,184 of them;
%* I cheated and looked at the data (more than once) in order
%* to determine the variable sizes needed;
data icd10cm_2020;
infile cm_codes lrecl=250 truncover;
attrib
code length=$7
desc length=$230
;
input
code 1-7 desc 9-230;
;
run;
* simulate claims sample data with mostly upto 8 diagnoses, and
* at least one claim with 50 diagnoses;
data have;
call streaminit(123);
do claim_id = 1 to 10;
array codes(50) $7 code1-code50;
array descs(50) $230 desc1-desc50;
call missing(of code:, of desc:);
if mod(claim_id, 10) = 0
then top = 50;
else top = rand('uniform', 8);
do _n_ = 1 to top;
p = ceil(rand('uniform', n)); %* pick a random diagnosis code, 1 of 72,184;
set icd10cm_2020 nobs=n point=p; %* read the data for that random code;
codes(_n_) = code;
descs(_n_) = desc;
end;
output;
end;
stop;
drop top;
run;
%macro loop_names(n=);
%do i=1 %to &n;
code&i desc&i.
%end;
%mend;
ods _all_ close;
ods html;
proc print data=have;
var claim_id %loop_names(n=50);
run;
This is a follow up question to this question.
I'm trying to simplify the way we embed images into our HTML results. The idea for this was inspired by this other question .
Basically what I am trying to do is to write a function-style macro (called %html_embed_image()) that takes an image, and converts it into a base64 format suitable for use in an HTML <img src=""> block.
Given an image such as this:
The usage would be:
data _null_;
file _webout;
put "<img src=""%html_embed_image(iFileName=hi.png)"" />";
run;
And the final output would be:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABaSURBVDhP5YxbCsAgDAS9/6XTvJTWNUSIX3ZAYXcdGxW4QW6Khw42Axne81LG0shlRvVVLyeTI2aZ2fcPyXwPdBI8B999NK/gKTaGyxaMX8gTJRkpyREFmegBTt8lFJjOey0AAAAASUVORK5CYII=" />
The question linked above shows how to do this in regular datastep code, but I am having issues getting this working in a function style macro. I posted a simplified problem I was having earlier and Tom was able to solve that simplified issue, but it doesn't seem to be working in the greater context of the function style macro.
Here is my code so far (the line causing issues is wrapped with two put statements indicating that it is the problem):
option mprint symbolgen;
%macro html_embed_image(iFileName=);
%local rc fid rc2 str str_length format_length format_mod base64_format base64_string;
/* ONLY READ IN 16K CHUNKS AS CONVERTING TO BASE64 */
/* INCREASES SIZE AND DONT WANT TO EXCEED 32K. */
%let rc = %sysfunc(filename(filrf, &iFileName, , lrecl=16000));
%let fid = %sysfunc(fopen(&filrf, i, 16000, b));
%if &fid > 0 %then %do;
%let rc = %sysfunc(fread(&fid));
%do %while(&rc eq 0);
%let rc2 = %sysfunc(fget(&fid,str,16000));
%let str = %superq(str);
/* FORMAT LENGTH NEEDS TO BE 4n/3 ROUNDED UP TO NEAREST MULTIPLE OF 4 */
%let str_length = %length(&str);
%let format_length = %sysevalf(4*(&str_length/3));
%let format_mod = %sysfunc(mod(&format_length,4));
%if &format_mod ne 0 %then %do;
%let format_length = %sysevalf(&format_length - &format_mod + 4);
%end;
%let base64_format = %sysfunc(cats($base64x,&format_length,.));
%put &=base64_format;
/* CONVERT THE BINARY DATA TO BASE64 USING THE CALCULATED FORMAT */
%put PROBLEM START;
%let base64_string = %sysfunc(putc(&str,&base64_format));
%put PROBLEM END;
%put &=base64_string;
/*&base64_string*/ /* RETURN RESULT HERE - COMMENTED OUT UNTIL WORKING */
%let rc = %sysfunc(fread(&fid));
%end;
%end;
%else %do;
%put %sysfunc(sysmsg());
%end;
%let rc=%sysfunc(fclose(&fid));
%let rc=%sysfunc(filename(filrf));
%mend;
Test the code:
%put %html_embed_image(iFileName=hi.png);
Results in:
ERROR: Expected close parenthesis after macro function invocation not found.
Any tips on how to fix this, or suggestions for workarounds would be great.
Just write the text using a data step.
%let fname=hi.png;
data _null_;
file _webout recfm=n;
if _n_=1 then put '<img src="data:image/png;base64,';
length str $60 coded $80 ;
infile "&fname" recfm=n eof=eof;
do len=1 to 60;
input ch $char1.;
substr(str,len,1)=ch;
end;
put str $base64x80.;
return;
eof:
len=len-1;
clen=4*ceil(len/3);
coded = putc(substr(str,1,len),cats('$base64x',clen,'.'));
put coded $varying80. clen ;
put '" />';
run;
If you really want to generate text in-line it might be best to add quotes so that you could call the macro in the middle of a PUT statement and not worry about hitting maximum string length.
%macro base64(file);
%local filerc fileref rc fid text len ;
%*----------------------------------------------------------------------
Assign fileref and open the file.
-----------------------------------------------------------------------;
%let fileref = _fread;
%let filerc = %sysfunc(filename(fileref,&file));
%let fid = %sysfunc(fopen(&fileref,s,60,b));
%*----------------------------------------------------------------------
Read file and dump as quoted BASE64 text.
-----------------------------------------------------------------------;
%if (&fid > 0) %then %do;
%do %while(%sysfunc(fread(&fid)) = 0);
%do %while(not %sysfunc(fget(&fid,text,60)));
%let len = %eval(4*%sysfunc(ceil(%length(%superq(text))/3)));
%if (&len) %then "%sysfunc(putc(%superq(text),$base64x&len..))" ;
%end;
%end;
%let rc = %sysfunc(fclose(&fid));
%end;
%*----------------------------------------------------------------------
Clear fileref assigned by macro,
-----------------------------------------------------------------------;
%if ^(&filerc) %then %let rc = %sysfunc(filename(fileref));
%mend base64;
So then your example data step becomes something like this:
%let fname=hi.png;
data _null_;
file _webout recfm=n;
put '<img src="data:image/png;base64,' %base64(&fname) '" />' ;
run;
I am trying to parse a delimited dataset with over 300 fields. Instead of listing all the input fields like
data test;
infile "delimited_filename.txt"
DSD delimiter="|" lrecl=32767 STOPOVER;
input field_A:$200.
field_B :$200.
field_C:$200.
/*continues on */
;
I am thinking I can dump all the field names into a file, read in as a sas dataset, and populate the input fields - this also gives me the dynamic control if any of the field names changes (add/remove) in the dataset. What would be some good ways to accomplish this?
Thank you very much - I just started sas, still trying to wrap my head around it.
This worked for me - Basically "write" data open code using macro language and run it.
Note: my indata_header_file contains 5 columns: Variable_Name, Variable_Length, Variable_Type, Variable_Label, and Notes.
%macro ReadDsFromFile(filename_to_process, indata_header_file, out_dsname);
%local filename_to_process indata_header_file out_dsname;
/* This macro var contain code to read data file*/
%local read_code input_in_line;
%put *** Processing file: &filename_to_process ...;
/* Read in the header file */
proc import OUT = ds_header
DATAFILE = &indata_header_file.
DBMS = EXCEL REPLACE; /* REPLACE flag */
SHEET = "Names";
GETNAMES = YES;
MIXED = NO;
SCANTEXT = YES;
run;
%let id = %sysfunc(open(ds_header));
%let NOBS = %sysfunc(attrn(&id.,NOBS));
%syscall set(id);
/*
Generates:
data &out_dsname.;
infile "&filename_to_process."
DSD delimiter="|" lrecl=32767 STOPOVER FIRSTOBS=3;
input
'7C'x
*/
%let read_code = data &out_dsname. %str(;)
infile &filename_to_process.
DSD delimiter=%str("|") lrecl=32767 STOPOVER %str(;)
input ;
/*
Generates:
<field_name> : $<field_length>;
*/
%do i = 1 %to &NObs;
%let rc = %sysfunc(fetchobs(&id., &i));
%let VAR_NAME = %sysfunc(getvarc(&id., %sysfunc(varnum(&id., Variable_Name)) ));
%let VAR_LENGTH = %sysfunc(getvarn(&id., %sysfunc(varnum(&id., Variable_Length)) ));
%let VAR_TYPE = %sysfunc(getvarc(&id., %sysfunc(varnum(&id., Variable_Type)) ));
%let VAR_LABEL = %sysfunc(getvarc(&id., %sysfunc(varnum(&id., Variable_Label)) ));
%let VAR_NOTES = %sysfunc(getvarc(&id., %sysfunc(varnum(&id., Notes)) ));
%if %upcase(%trim(&VAR_TYPE.)) eq CHAR %then
%let input_in_line = &VAR_NAME :$&VAR_LENGTH..;
%else
%let input_in_line = &VAR_NAME :&VAR_LENGTH.;
/* append in_line statment to main macro var*/
%let read_code = &read_code. &input_in_line. ;
%end;
/* Close the fid */
%let rc = %sysfunc(close(&id));
%let read_code = &read_code. %str(;) run %str(;) ;
/* Run the generated code*/
&read_code.
%mend ReadDsFromFile;
Sounds like you want to generate code based on metadata. A data step is actually a lot easier to code and debug than a macro.
Let's assume you have metadata that describes the input data. For example let's use the metadata about the SASHELP.CARS. We can build our metadata from the existing DICTIONARY.COLUMNS metadata on the existing dataset. Let's set the INFORMAT to the FORMAT since that table does not have INFORMAT value assigned.
proc sql noprint ;
create table varlist as
select memname,varnum,name,type,length,format,format as informat,label
from dictionary.columns
where libname='SASHELP' and memname='CARS'
;
quit;
Now let's make a sample text file with the data in it.
filename mydata temp;
data _null_;
set sashelp.cars ;
file mydata dsd ;
put (_all_) (:);
run;
Now we just need to use the metadata to write a program that could read that data. All we really need to do is define the variables and then add a simple INPUT firstvar -- lastvar statement to read the data.
filename code temp;
data _null_;
set varlist end=eof ;
by varnum ;
file code ;
if _n_=1 then do ;
firstvar=name ;
retain firstvar ;
put 'data ' memname ';'
/ ' infile mydata dsd truncover lrecl=1000000;'
;
end;
put ' attrib ' name 'length=' #;
if type = 'char' then put '$'# ;
put length ;
if informat ne ' ' then put #10 informat= ;
if format ne ' ' then put #10 format= ;
if label ne ' ' then put #10 label= :$quote. ;
put ' ;' ;
if eof then do ;
put ' input ' firstvar '-- ' name ';' ;
put 'run;' ;
end;
run;
Now we can just run the generated code using %INCLUDE.
%include code / source2 ;
This problem might be trivial but I got stuck.
My problem is I've to go to each folder for a dataset and transpose the data.
I wrote the following code and it works fine.
OPTIONS MPRINT MLOGIC SYMBOLGEN;
%LET LOC=E:\folder;
%macro test1(k,l);
libname libary "&loc.\&k\&l.";
data dataset_&l.;
set libary.dataset_original;
run;
proc transpose data=dataset_&l. out=dataset_&l._T;
run;
%mend;
%test1(var_1,var'_1);
%test1(var_2,var'_2);
%test1(var_3,var'_3);
The issue with this code is it's not dynamic in terms of folder structure. E.g. if there's another 4 extra folders, I've to write "%test1(var_3,var'_3);"4 times.
So I tried writing the following code to make it more dynamic. But unfortunately it's not working. Can anybody please tell me where I'm making the mistake.
OPTIONS MPRINT MLOGIC SYMBOLGEN;
%LET LOC=E:\folder;
%let k=var_1 var_2 var_3;
%let l=var'_1 var'_2 var'_3;
%macro words(string);
%local count word;
%let count=1;
/* The third argument of the %QSCAN function specifies the delimiter */
%let word=%qscan(&string,&count,%str( ));
%do %while(&word ne);
%let count=%eval(&count+1);
%let word=%qscan(&string,&count,%str( ));
%end;
%eval(&count-1)
%mend words;
%macro test1(k,l);
libname libary "&loc.\&k\&l.";
data dataset_&l.;
set libary.dataset_original;
run;
proc transpose data=dataset_&l. out=dataset_&l._T;
run;
%mend;
%macro test();
%do i=1 %to %words(&k.);
%do j=1 %to %words(&l.);
%let var=%scan(&k.,&i.,str());
%let var1=%scan(&l.,&j.,str());
%test1(&var.,&var1.);
%end;
%end;
%mend;
%test();
Thanks!
Try this:
/* Set your base directory */
%let base = E:\Folder;
/* Pipe output from dir */
filename flist pipe "dir /s /b /a:-h &base";
/* Read files from pipe */
data files;
length file dir $ 200 name $ 50 ext $ 10;
infile flist;
input #1 file $ &;
/* File extension */
ext = scan(file, -1, ".");
/* File name */
name = scan(scan(file, -1, "\"), 1, ".");
/* Directory */
rfile = reverse(file);
dir = reverse(substr(rfile, index(rfile, "\") + 1));
/* Select only SAS datasets */
if upcase(ext) = "SAS7BDAT" then output;
drop rfile;
run;
/* Define a macro to process each file */
%macro trans_file(dir, name);
libname d "&dir";
proc transpose data = d.&name out = d.&name._t;
run;
libname d clear;
%mend trans_file;
/* Run on all files */
data _null_;
set files;
call execute(cats('%trans_file(', dir, ",", name, ");"));
run;
This gets the file list by submitting the Windows command dir. It gets all files in the specified directory and its subdirectories.
This approach then uses a simple macro that defines a data library, reads a dataset from the library, writes a transposed dataset to it, then clears it. The macro is called for each file in the list using call execute.