<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Reading a RTF table into SAS data set without capturing footnotes in last OBS line in ODS and Base Reporting</title>
    <link>https://communities.sas.com/t5/ODS-and-Base-Reporting/Reading-a-RTF-table-into-SAS-data-set-without-capturing/m-p/304429#M17149</link>
    <description>&lt;P&gt;Dear,&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;My RTF table look like this: I am using the the follwing Macro to convert the table into sas dataset. The ouput sas dataset contains the footnote in the last OBS. Please help where I need to modify in the code to remove the footnote in the last OBS of my output .&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Table Header&lt;/P&gt;&lt;P&gt;____________________________________________________________________&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; one &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; two&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; _______ &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;______________&lt;/P&gt;&lt;P&gt;NS &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;0 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;3&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; n=1 &amp;nbsp; &amp;nbsp; &amp;nbsp; n=2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;n=3 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;n=4&lt;/P&gt;&lt;P&gt;_____________________________________________________________________&lt;/P&gt;&lt;P&gt;ANY &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;3 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;4&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp;Rel &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;0 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp;NotR &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 0 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2&lt;/P&gt;&lt;P&gt;____________________________________________________________________&lt;/P&gt;&lt;P&gt;note1;&lt;/P&gt;&lt;P&gt;note2:&lt;/P&gt;&lt;P&gt;source:&lt;/P&gt;&lt;P&gt;Programname&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;code:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;%macro extract(loc=%str(),file=,subheader_num= 1,out=one);&lt;/P&gt;&lt;P&gt;%local opts;&lt;BR /&gt;%let opts=%sysfunc(getoption(ls,keyword) );&lt;/P&gt;&lt;P&gt;options linesize=200;&lt;BR /&gt;%let rtffile=%lowcase(%sysfunc(tranwrd(%upcase(&amp;amp;rtffile), %str(.RTF), %str())));&lt;BR /&gt;%local _i _params _param _param_exist_err;&lt;BR /&gt;%let _params=.RTFLOC.RTFFILE.DOUT.SUBHEADER_NUM;&lt;BR /&gt;%let _i=1;&lt;/P&gt;&lt;P&gt;%do %while(%scan(&amp;amp;_params,&amp;amp;_i,.)^=%str()) ;&lt;BR /&gt;%let _param=%scan(&amp;amp;_params,&amp;amp;_i,.);&lt;BR /&gt;%if %quote(&amp;amp;&amp;amp;&amp;amp;_param) = %str() %then %do ;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter %upcase(&amp;amp;_param) is required. Please specify it.;&lt;BR /&gt;%let _param_exist_err=%eval(&amp;amp;_param_exist_err+1);&lt;BR /&gt;&lt;BR /&gt;%end;&lt;BR /&gt;%let _i=%eval(&amp;amp;_i+1) ;&lt;/P&gt;&lt;P&gt;%end;&lt;/P&gt;&lt;P&gt;%if &amp;amp;_param_exist_err&amp;gt;=1 %then %do;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%if %datatyp(&amp;amp;SUBHEADER_NUM) ne NUMERIC %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter SUBHEADER_NUM should be a number, please correct it.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%else %if &amp;amp;SUBHEADER_NUM&amp;lt;0 %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter SUBHEADER_NUM should be a positive number, please correct it.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%else %if &amp;amp;SUBHEADER_NUM=0 %then %do;&lt;BR /&gt;%let SUBHEADER_NUM=1;&lt;BR /&gt;%put USER MESSAGE: (macro &amp;amp;SYSMACRONAME): Macro parameter SUBHEADER_NUM cannot be 0. In this case, macro assigned SUBHEADER_NUM=1.;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%if %sysfunc(fileexist(&amp;amp;rtfloc))=0 %then %do ;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): path/directory specified by macro parameter RTFLOC does not exist;&lt;BR /&gt;%put ERROR- (&amp;amp;rtfloc);&lt;BR /&gt;%put ERROR- Please check this path/directory.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%if %sysfunc(fileexist(&amp;amp;rtfloc\&amp;amp;rtffile..rtf))=0 %then %do ;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): File &amp;amp;rtffile..rtf (specified by macro parameter RTFFILE);&lt;BR /&gt;%put ERROR- does not exist in directory RTFLOC (&amp;amp;rtfloc).;&lt;BR /&gt;%put ERROR- Please check this RTF name and specify proper RTFFILE.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%if %length(&amp;amp;dout)&amp;gt;32 %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Length of macro parameter DOUT (i.e. SAS dataset name) is longer 32 char-s.;&lt;BR /&gt;%put ERROR- (&amp;amp;dout);&lt;BR /&gt;%put ERROR- SAS datasets name cannot exceed 32 char-s. Please shorten macro parameter DOUT.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;%local letter1;&lt;BR /&gt;%let letter1= %substr(&amp;amp;dout, 1, 1);&lt;/P&gt;&lt;P&gt;%if %sysfunc(notalpha(&amp;amp;letter1))&amp;gt;0 and &amp;amp;letter1 ne _ %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter DOUT (i.e. SAS dataset name) should ;&lt;BR /&gt;%put ERROR- start with an English letter (A, B, C, . . ., Z) or underscore (_).;&lt;BR /&gt;%put ERROR- Your macro parameter DOUT (&amp;amp;dout) starts with &amp;amp;letter1..;&lt;BR /&gt;%put ERROR- Please make first letter either a letter or an underscore.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%if %index(&amp;amp;dout, %str( )) %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter DOUT (i.e. SAS dataset name) should not have blanks;&lt;BR /&gt;%put ERROR- (according to SAS rules about naming SAS datasets).;&lt;BR /&gt;%put ERROR- Please remove all blanks from DOUT.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%local other_lettes;&lt;BR /&gt;%let other_letters=%substr(&amp;amp;dout, 2, %length(&amp;amp;dout)-1);&lt;BR /&gt;&lt;BR /&gt;%local remove_alphanum_dout;&lt;BR /&gt;%let remove_alphanum_dout=%sysfunc(compress(&amp;amp;dout, , %str(ad)));&lt;BR /&gt;&lt;BR /&gt;%let remove_alphanum_dout=%sysfunc(tranwrd(&amp;amp;remove_alphanum_dout, %str(_), %str()));&lt;/P&gt;&lt;P&gt;%if %length(&amp;amp;remove_alphanum_dout)&amp;gt;0 %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter DOUT (i.e. SAS dataset name) should not should not contain any special char-s.;&lt;BR /&gt;%put ERROR- (according to SAS rules about naming SAS datasets).;&lt;BR /&gt;%put ERROR- Your macro parameter DOUT has the following special char-s: &amp;amp;remove_alphanum_dout..;&lt;BR /&gt;%put ERROR- Please remove all special char-s from DOUT.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;proc datasets library=work nolist nowarn;&lt;BR /&gt;delete &amp;amp;dout ;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%let tmpfile=%sysfunc(pathname(work))\&amp;amp;rtffile..rtf;&lt;BR /&gt;options noxwait;&lt;BR /&gt;x copy "&amp;amp;rtfloc\&amp;amp;RTFFile..rtf" "&amp;amp;tmpfile";&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;data __readrtf0;&lt;BR /&gt;infile "&amp;amp;tmpfile." missover length = l end = lastobs lrecl = 2000;&lt;BR /&gt;input string $varying2000. l;&lt;/P&gt;&lt;P&gt;if not missing(string) then do;&lt;BR /&gt;if index(string, '{\header\') then flag1=1;&lt;BR /&gt;if index(string, '{\footer\') then flag2=1;&lt;BR /&gt;if index(string, '\trowd') then flag3=1;&lt;BR /&gt;if index(string, '\row') then flag4=1;&lt;BR /&gt;if length(string)&amp;gt;=3 and substr(strip(string), 1, 3)='\cl' then flag5=1;&lt;BR /&gt;end;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%local max1 max2 max3 max4 max5;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;create table __check_keywords as&lt;BR /&gt;select max(flag1) as flag1,&lt;BR /&gt;max(flag2) as flag2,&lt;BR /&gt;max(flag3) as flag3,&lt;BR /&gt;max(flag4) as flag4,&lt;BR /&gt;max(flag5) as flag5&lt;BR /&gt;from __readrtf0&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;%local keywords;&lt;BR /&gt;%let keywords=0;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;select count(*) into : keywords&lt;BR /&gt;from __check_keywords&lt;BR /&gt;where flag1=1 and flag2=1 and flag3=1 and flag4=1 and flag5=1&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;BR /&gt;%let keywords=&amp;amp;keywords;&lt;/P&gt;&lt;P&gt;%if &amp;amp;keywords=0 %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): This macro works only on RTF documents created by SAS.;&lt;BR /&gt;%put ERROR- It looks like either of the following happened:;&lt;BR /&gt;%put ERROR- ;&lt;BR /&gt;%put ERROR- (1) This RTF was not created by SAS;&lt;BR /&gt;%put ERROR- (2) This RTF *was* created by SAS, but someone *edited* it after it was generated by SAS.;&lt;BR /&gt;%put ERROR- (3) A Word document was created by SAS and the user converted it to RTF.;&lt;BR /&gt;%put ERROR- ;&lt;BR /&gt;%put ERROR- In either of these cases, macro will not work on this RTF doc and will exit now.;&lt;BR /&gt;%put ERROR- ;&lt;BR /&gt;%put ERROR- FYI: here is an explanation:;&lt;BR /&gt;%put ERROR- When RTF doc is created by SAS, a certain RTF code is generated, and this macro works with this code.;&lt;BR /&gt;%put ERROR- For example, this code will contain such key words as \trowd, \row,{\header\, {\footer\, \cl.;&lt;BR /&gt;%put ERROR- However, if RTF doc is *not* created by SAS, the keywords mentioned above won_t be created, and therefore this macro won_t work.;&lt;BR /&gt;%goto exit;&lt;/P&gt;&lt;P&gt;%end;&lt;/P&gt;&lt;P&gt;data __readrtf1;&lt;BR /&gt;infile "&amp;amp;tmpfile." missover length = l end = lastobs lrecl = 2000;&lt;BR /&gt;input string $varying2000. l;&lt;BR /&gt;rownum = _n_;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;retain c1-c99&lt;BR /&gt;dropme indent;&lt;/P&gt;&lt;P&gt;length c1-c99 $1000;&lt;BR /&gt;if _n_ = 1 then dropme = 0.5;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;array c{99} $;&lt;/P&gt;&lt;P&gt;if index(string, '\trowd') then do;&lt;BR /&gt;count = 0;&lt;BR /&gt;indent = 0;&lt;BR /&gt;do i=1 to dim(c);&lt;BR /&gt;c{i} = '';&lt;BR /&gt;end;&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;{ ...{\line} (Scenario 2)&lt;BR /&gt;RTF control word which signals that a row was split into 2+ lines&lt;BR /&gt;This will be line #1.&lt;BR /&gt;example: ... \cf1{P: Abnormal dreams/{\line}&lt;BR /&gt;Note: cfN=background color&lt;/P&gt;&lt;P&gt;{\line} (Scenario 3)&lt;BR /&gt;Note that there is no left curly bracket { !&lt;BR /&gt;This happens when a line is split into 2+ lines:&lt;BR /&gt;This will be line #2, 3, etc., but NOT the last line.&lt;BR /&gt;example: S: Psychiatric disorders/{\line}&lt;/P&gt;&lt;P&gt;... \cell} (Scenario 4)&lt;BR /&gt;Note that there is no left curly bracket { !&lt;BR /&gt;This happens when a line is split into 2+ lines.&lt;BR /&gt;This will be the last line.&lt;BR /&gt;example: V: VIVID DREAMING\cell}&lt;BR /&gt;;&lt;/P&gt;&lt;P&gt;if ( index(string, '{') and index(string, '\cell'))&lt;BR /&gt;&lt;BR /&gt;or ( count(string,"{")&amp;gt;=2 and index(string, '{\line}' ))&lt;BR /&gt;&lt;BR /&gt;or ( count(string,"{")=1 and index(string, '{\line}'))&lt;BR /&gt;or ( count(string,"{")=0 and index(string, '\cell}'))&lt;BR /&gt;then do;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;BR /&gt;first_bracket=index(string, '{');&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;if ( index(string, '{') and index(string, '\cell'))&lt;BR /&gt;or ( count(string,"{")&amp;gt;=2 and index(string, '{\line}' ))&lt;BR /&gt;then count + 1;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;if (index(string, '{') and index(string, '\cell')) then do;&lt;BR /&gt;prep = substr(string, 1, index(string, '\cell')-1);&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;if first_bracket ne 0 then prep= substr(prep, first_bracket+1);&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;else if count(string,"{")&amp;gt;=2 and index(string, '{\line}') then do;&lt;BR /&gt;%*** extract part of PREP which comes after the first curved/angled bracket {;&lt;BR /&gt;prep= substr(string, first_bracket+1);&lt;BR /&gt;%*** replace '{\line}' with blanks;&lt;BR /&gt;prep=tranwrd(prep, '{\line}', '');&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;%*** (Scenario 1) \cell signals the end of text printed in the cell&lt;BR /&gt;(Scenario 2) a row was split into 2+ lines, and this will be line #1.;&lt;BR /&gt;if ( index(string, '{') and index(string, '\cell'))&lt;BR /&gt;or ( count(string,"{")&amp;gt;=2 and index(string, '{\line}' ))&lt;BR /&gt;then do;&lt;BR /&gt;c{count} = compress(prep, byte(13));&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;%*** (Scenario 3) When a line is split into several lines:&lt;BR /&gt;This will be line #2, 3, etc., but NOT the last line: end with {\line};&lt;BR /&gt;else if ( count(string,"{")=1 and index(string, '{\line}')) then do;&lt;BR /&gt;%*** Do:&lt;BR /&gt;1. take c{count} from Scenario 2&lt;BR /&gt;2. extract part of STRING which comes before {\line}&lt;BR /&gt;3. take 1 + 2 and separate them by blanks;&lt;BR /&gt;c{count}=catx(' ', c{count}, substr(string, 1, index(string, '{\line}')-1) );&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%*** (Scenario 4) When a line is split into several lines:&lt;BR /&gt;This will be the last line: end with \cell};&lt;BR /&gt;else if ( count(string,"{")=0 and index(string, '\cell}')) then do;&lt;BR /&gt;%*** Do:&lt;BR /&gt;1. take c{count} from Scenario 3&lt;BR /&gt;2. extract part of STRING which comes before \cell}&lt;BR /&gt;3. take 1 + 2 and separate them by blanks;&lt;BR /&gt;c{count}=catx(' ', c{count}, substr(string, 1, index(string, '\cell}')-1) );&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;if index(string, '{ ') then do;&lt;BR /&gt;indent_start=index(string, '{ ')+1;&lt;BR /&gt;%** if first symbols after '{ ' are \line then assign indent=0;&lt;BR /&gt;if length(string)&amp;gt;=5 and substr( strip(substr(string, indent_start)), 1,5)= '\line' then indent=0 ;&lt;BR /&gt;else do;&lt;BR /&gt;%*** VERIFY() function: find location of the first char which is not a space (' ');&lt;BR /&gt;other_char_start=verify( substr(string, indent_start), ' ')+indent_start;&lt;BR /&gt;indent=other_char_start-indent_start;&lt;BR /&gt;end;&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;else do;&lt;BR /&gt;%*** if there are any digits/numbers after \li then assign sst;&lt;BR /&gt;sst = substr(string, index(string, '{\li') + 4);&lt;BR /&gt;%*** VERIFY() function: find location of the first char which is not a number;&lt;BR /&gt;if verify(sst, '-0123456789') &amp;gt; 1 then indent=input(substr(sst, 1, verify(sst, '-0123456789') - 1), best.);&lt;BR /&gt;end;&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;%*** if indent&amp;gt; 0 and string has \li240, etc., then then replace it with '';&lt;BR /&gt;if indent&amp;gt;0 and not missing(sst) then do;&lt;BR /&gt;c{count} = strip(tranwrd(c{count}, '\li'||strip(put(indent, best.)), ''));&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;if index(c{count}, '\line') then do;&lt;BR /&gt;if substr(strip(c{count}), 1, 5)='\line' then c{count}=strip(tranwrd(c{count}, '\line', ''));&lt;BR /&gt;else do;&lt;BR /&gt;c{count}=tranwrd(c{count}, '{\line}', '');&lt;BR /&gt;c{count}=tranwrd(c{count}, '\line', '');&lt;BR /&gt;end;&lt;BR /&gt;c{count}=compbl(c{count});&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;c{count}=strip(c{count}); %*** get read of leading and trailing blanks;&lt;BR /&gt;&lt;BR /&gt;end; %*** end of: if index(string, '{') and index(string, '\cell') then do;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;if dropme =999 then dropme = 0; %*** This signals the beginning of table body (i.e. note titles, columns headers or footnotes),&lt;BR /&gt;and only rows with DROPME=0 will be kept.;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%* (1) Find where you encounter a footer (i.e. find '{\footer\' in STRING), set DROPME=1 to signal we_re in the title area.;&lt;BR /&gt;if substr(string, 1, 9)='{\header\' then dropme = 0.6;&lt;BR /&gt;else if index(string, '}}') and dropme=0.6 then dropme = 0.7;&lt;BR /&gt;else if substr(string, 1, 9)='{\footer\' and dropme=0.7 then dropme = 1;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%**********************************************************************************;&lt;BR /&gt;%*** Repeat steps below for each row in column headers (i.e. up to &amp;amp;subheader_num);&lt;BR /&gt;%**********************************************************************************;&lt;/P&gt;&lt;P&gt;%local s;&lt;BR /&gt;%do s=1 %to &amp;amp;subheader_num;&lt;/P&gt;&lt;P&gt;%*put ===&amp;gt; s=&amp;amp;s;&lt;BR /&gt;if index(string, '\trowd' )&lt;BR /&gt;and ( (dropme =1 and index(string, '}}' )) /* For the first iteration, DROPME=1.&lt;BR /&gt;Find next row where footer ends (i.e. find '}}' and '\trowd' in STRING) */&lt;BR /&gt;or&lt;BR /&gt;dropme=4+%sysevalf(&amp;amp;s*10-10) /* If column headers spread over several rows (i.e. if &amp;amp;subheader_num&amp;gt;1) */&lt;BR /&gt;)&lt;BR /&gt;then dropme = 2+%sysevalf(&amp;amp;s*10); %*** DROPME will be 12, 22, 32, etc., depending on &amp;amp;subheader_num;&lt;/P&gt;&lt;P&gt;%*** Find next row where column headers begin (i.e. find '\cl' in STRING) and assign DROPME=13, 23, 33, etc., depending on &amp;amp;subheader_num;&lt;BR /&gt;else if length(string)&amp;gt;=3 and substr(strip(string), 1, 3)='\cl' and dropme = 2+%sysevalf(&amp;amp;s*10) then dropme = 3+%sysevalf(&amp;amp;s*10); %** \clbrdrb, \cltxlrtb, etc.;&lt;/P&gt;&lt;P&gt;%*** If there are no more column headers in the next row (i.e. if &amp;amp;s=&amp;amp;subheader_num ), then assign DROPME=999;&lt;BR /&gt;%if &amp;amp;s=&amp;amp;subheader_num %then %do;&lt;BR /&gt;else if index(string, '\row' ) and dropme = 3+%sysevalf(&amp;amp;s*10) then dropme = 999;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%*** If there are more column headers, spreaded over next row, then assign DROPME=14, 24, 34, etc., depending on &amp;amp;subheader_num;&lt;BR /&gt;%else %do;&lt;BR /&gt;else if index(string, '\row' ) and dropme = 3+%sysevalf(&amp;amp;s*10) then dropme =4+%sysevalf(&amp;amp;s*10);&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;if not dropme and index(string, '\row') then do;&lt;BR /&gt;allblank = 1;&lt;BR /&gt;do i=1 to dim(c);&lt;BR /&gt;if compress(c{i}, ' \') ne '' then allblank = 0;&lt;BR /&gt;end;&lt;BR /&gt;/* %put ERROR: unquote;*/&lt;BR /&gt;if allblank=0 then output;&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;%jump:&lt;/P&gt;&lt;P&gt;run;&lt;/P&gt;&lt;P&gt;/*%goto exit;*/&lt;/P&gt;&lt;P&gt;%*** Delete RTF file which you copied to temporary location;&lt;BR /&gt;x del "&amp;amp;tmpfile";&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;proc transpose data=__readrtf1(drop=count indent_start other_char_start) out=__chk;&lt;BR /&gt;var c:;&lt;BR /&gt;by rownum;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%local dropper;&lt;BR /&gt;%let dropper=;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;select distinct _name_ into: dropper&lt;BR /&gt;separated by ' '&lt;BR /&gt;from __chk&lt;BR /&gt;where _name_ not in (select _name_ from __chk where col1 ne '')&lt;BR /&gt;;&lt;/P&gt;&lt;P&gt;%local numcols;&lt;BR /&gt;%let numcols=0;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;select distinct count(distinct _name_) into: numcols&lt;BR /&gt;from __chk&lt;BR /&gt;where col1 ne ''&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;BR /&gt;%let numcols=&amp;amp;numcols; %*** to get rid of leading blanks;&lt;BR /&gt;&lt;BR /&gt;%if &amp;amp;numcols&amp;gt;1 %then %do;&lt;BR /&gt;data __readrtf1;&lt;BR /&gt;set __readrtf1;&lt;BR /&gt;if missing(c1) then indent=1;&lt;BR /&gt;run;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;proc sort data=__readrtf1(drop=count &amp;amp;dropper indent_start other_char_start string dropme prep first_bracket sst i allblank) out=__readrtf2;&lt;BR /&gt;by indent ;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;data __readrtf4 /*(index=(rownum))*/;&lt;BR /&gt;set __readrtf2;&lt;BR /&gt;by indent;&lt;BR /&gt;if first.indent then order2 + 1;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%*** create order1, etc. from INDENT;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;create table __ind1 as&lt;BR /&gt;select indent, rownum&lt;BR /&gt;from __readrtf4&lt;BR /&gt;order by indent, rownum&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;data __ind2;&lt;BR /&gt;set __ind1;&lt;BR /&gt;by indent rownum;&lt;BR /&gt;retain order;&lt;BR /&gt;if first.indent then order+1;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;proc sort data=__ind2;&lt;BR /&gt;by rownum order;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;data __ind3;&lt;BR /&gt;set __ind2;&lt;BR /&gt;by rownum order;&lt;BR /&gt;retain order1;&lt;BR /&gt;if _n_=1 then order1=1;&lt;BR /&gt;else if order=1 then order1+1;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;proc sort data=__ind3;&lt;BR /&gt;by order1 indent rownum order;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;data __ind4;&lt;BR /&gt;set __ind3 (rename=(order=order_old));&lt;BR /&gt;by order1 indent rownum order_old;&lt;BR /&gt;retain order;&lt;BR /&gt;if first.order1 then order=0;&lt;BR /&gt;if first.indent then order+1;&lt;BR /&gt;drop order_old;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%*** Assign variable level_order1, which is number of unique ORDER within each ORDER1;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;create table __ind5 as&lt;BR /&gt;select *, max(order) as level_order1&lt;BR /&gt;from __ind4&lt;BR /&gt;group by order1&lt;BR /&gt;order by order1, rownum, order&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;%*** how many are there distinct levels of ORDER? Create order1, order2, etc. based on this number of levels;&lt;BR /&gt;%local levels;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;select max(order) into : levels&lt;BR /&gt;from __ind5&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;BR /&gt;%let levels=&amp;amp;levels; %** to get rid of leading blanks;&lt;BR /&gt;%*put levels=&amp;amp;levels;&lt;/P&gt;&lt;P&gt;%*** if there more than 1 levels, do this;&lt;BR /&gt;%if &amp;amp;levels&amp;gt;1 %then %do;&lt;/P&gt;&lt;P&gt;data __ind6;&lt;BR /&gt;set __ind5;&lt;BR /&gt;by order1 rownum order;&lt;BR /&gt;lag_order=lag(order);&lt;BR /&gt;lag_order1=lag(order1);&lt;/P&gt;&lt;P&gt;retain order2-order&amp;amp;levels;&lt;/P&gt;&lt;P&gt;array ord(*) order2- order&amp;amp;levels;&lt;BR /&gt;array lag_ord(*) lag_order2- lag_order&amp;amp;levels;&lt;/P&gt;&lt;P&gt;do i=1 to dim(ord);&lt;/P&gt;&lt;P&gt;if first.order1 then ord(i)=0;&lt;BR /&gt;if i ne 1 then do;&lt;BR /&gt;lag_ord(i-1)=lag(ord(i-1));&lt;BR /&gt;if lag_ord(i-1) ne ord(i-1) then ord(i)=0;&lt;BR /&gt;end;&lt;BR /&gt;if order=i+1 then ord(i)=ord(i)+1;&lt;BR /&gt;if level_order1=i+1 and lag_order &amp;gt; order then ord(i)=0; %*** if level_order1=i+1 means: if there are no more levels after this ord(i);&lt;BR /&gt;end;&lt;BR /&gt;drop i;&lt;/P&gt;&lt;P&gt;drop lag_:;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;proc sort data=__readrtf4;&lt;BR /&gt;by rownum;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;proc sort data=__ind6;&lt;BR /&gt;by rownum;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;data __readrtf5;&lt;BR /&gt;merge __readrtf4 (drop=order2 )&lt;BR /&gt;__ind6 (drop=order indent level_order1);&lt;BR /&gt;by rownum;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%end; %** end of: &amp;amp;levels&amp;gt;1;&lt;/P&gt;&lt;P&gt;%else %do;&lt;BR /&gt;data __readrtf5;&lt;BR /&gt;set __readrtf4 (drop=order2 );&lt;BR /&gt;order1=_n_;&lt;BR /&gt;run;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%*** reassign rownum;&lt;BR /&gt;data &amp;amp;dout;&lt;BR /&gt;set __readrtf5 (drop=rownum);&lt;BR /&gt;rownum=_n_;&lt;BR /&gt;if indent ne 0 then indented='Y';&lt;BR /&gt;drop indent;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%exit:&lt;/P&gt;&lt;P&gt;options &amp;amp;opts; %*** whatever system options you changes - restore them to what they used to be before running this macro;&lt;/P&gt;&lt;P&gt;proc datasets library=work nolist nowarn;&lt;BR /&gt;delete __readrtf: __chk __ind: __check_keywords;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;%mend extract;&lt;/P&gt;</description>
    <pubDate>Thu, 13 Oct 2016 17:23:47 GMT</pubDate>
    <dc:creator>knveraraju91</dc:creator>
    <dc:date>2016-10-13T17:23:47Z</dc:date>
    <item>
      <title>Reading a RTF table into SAS data set without capturing footnotes in last OBS line</title>
      <link>https://communities.sas.com/t5/ODS-and-Base-Reporting/Reading-a-RTF-table-into-SAS-data-set-without-capturing/m-p/304429#M17149</link>
      <description>&lt;P&gt;Dear,&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;My RTF table look like this: I am using the the follwing Macro to convert the table into sas dataset. The ouput sas dataset contains the footnote in the last OBS. Please help where I need to modify in the code to remove the footnote in the last OBS of my output .&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Table Header&lt;/P&gt;&lt;P&gt;____________________________________________________________________&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; one &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; two&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; _______ &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;______________&lt;/P&gt;&lt;P&gt;NS &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;0 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;3&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; n=1 &amp;nbsp; &amp;nbsp; &amp;nbsp; n=2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;n=3 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;n=4&lt;/P&gt;&lt;P&gt;_____________________________________________________________________&lt;/P&gt;&lt;P&gt;ANY &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;3 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;4&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp;Rel &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;0 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2&lt;/P&gt;&lt;P&gt;&amp;nbsp; &amp;nbsp;NotR &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 0 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2&lt;/P&gt;&lt;P&gt;____________________________________________________________________&lt;/P&gt;&lt;P&gt;note1;&lt;/P&gt;&lt;P&gt;note2:&lt;/P&gt;&lt;P&gt;source:&lt;/P&gt;&lt;P&gt;Programname&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;code:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;%macro extract(loc=%str(),file=,subheader_num= 1,out=one);&lt;/P&gt;&lt;P&gt;%local opts;&lt;BR /&gt;%let opts=%sysfunc(getoption(ls,keyword) );&lt;/P&gt;&lt;P&gt;options linesize=200;&lt;BR /&gt;%let rtffile=%lowcase(%sysfunc(tranwrd(%upcase(&amp;amp;rtffile), %str(.RTF), %str())));&lt;BR /&gt;%local _i _params _param _param_exist_err;&lt;BR /&gt;%let _params=.RTFLOC.RTFFILE.DOUT.SUBHEADER_NUM;&lt;BR /&gt;%let _i=1;&lt;/P&gt;&lt;P&gt;%do %while(%scan(&amp;amp;_params,&amp;amp;_i,.)^=%str()) ;&lt;BR /&gt;%let _param=%scan(&amp;amp;_params,&amp;amp;_i,.);&lt;BR /&gt;%if %quote(&amp;amp;&amp;amp;&amp;amp;_param) = %str() %then %do ;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter %upcase(&amp;amp;_param) is required. Please specify it.;&lt;BR /&gt;%let _param_exist_err=%eval(&amp;amp;_param_exist_err+1);&lt;BR /&gt;&lt;BR /&gt;%end;&lt;BR /&gt;%let _i=%eval(&amp;amp;_i+1) ;&lt;/P&gt;&lt;P&gt;%end;&lt;/P&gt;&lt;P&gt;%if &amp;amp;_param_exist_err&amp;gt;=1 %then %do;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%if %datatyp(&amp;amp;SUBHEADER_NUM) ne NUMERIC %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter SUBHEADER_NUM should be a number, please correct it.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%else %if &amp;amp;SUBHEADER_NUM&amp;lt;0 %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter SUBHEADER_NUM should be a positive number, please correct it.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%else %if &amp;amp;SUBHEADER_NUM=0 %then %do;&lt;BR /&gt;%let SUBHEADER_NUM=1;&lt;BR /&gt;%put USER MESSAGE: (macro &amp;amp;SYSMACRONAME): Macro parameter SUBHEADER_NUM cannot be 0. In this case, macro assigned SUBHEADER_NUM=1.;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%if %sysfunc(fileexist(&amp;amp;rtfloc))=0 %then %do ;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): path/directory specified by macro parameter RTFLOC does not exist;&lt;BR /&gt;%put ERROR- (&amp;amp;rtfloc);&lt;BR /&gt;%put ERROR- Please check this path/directory.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%if %sysfunc(fileexist(&amp;amp;rtfloc\&amp;amp;rtffile..rtf))=0 %then %do ;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): File &amp;amp;rtffile..rtf (specified by macro parameter RTFFILE);&lt;BR /&gt;%put ERROR- does not exist in directory RTFLOC (&amp;amp;rtfloc).;&lt;BR /&gt;%put ERROR- Please check this RTF name and specify proper RTFFILE.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%if %length(&amp;amp;dout)&amp;gt;32 %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Length of macro parameter DOUT (i.e. SAS dataset name) is longer 32 char-s.;&lt;BR /&gt;%put ERROR- (&amp;amp;dout);&lt;BR /&gt;%put ERROR- SAS datasets name cannot exceed 32 char-s. Please shorten macro parameter DOUT.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;%local letter1;&lt;BR /&gt;%let letter1= %substr(&amp;amp;dout, 1, 1);&lt;/P&gt;&lt;P&gt;%if %sysfunc(notalpha(&amp;amp;letter1))&amp;gt;0 and &amp;amp;letter1 ne _ %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter DOUT (i.e. SAS dataset name) should ;&lt;BR /&gt;%put ERROR- start with an English letter (A, B, C, . . ., Z) or underscore (_).;&lt;BR /&gt;%put ERROR- Your macro parameter DOUT (&amp;amp;dout) starts with &amp;amp;letter1..;&lt;BR /&gt;%put ERROR- Please make first letter either a letter or an underscore.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%if %index(&amp;amp;dout, %str( )) %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter DOUT (i.e. SAS dataset name) should not have blanks;&lt;BR /&gt;%put ERROR- (according to SAS rules about naming SAS datasets).;&lt;BR /&gt;%put ERROR- Please remove all blanks from DOUT.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%local other_lettes;&lt;BR /&gt;%let other_letters=%substr(&amp;amp;dout, 2, %length(&amp;amp;dout)-1);&lt;BR /&gt;&lt;BR /&gt;%local remove_alphanum_dout;&lt;BR /&gt;%let remove_alphanum_dout=%sysfunc(compress(&amp;amp;dout, , %str(ad)));&lt;BR /&gt;&lt;BR /&gt;%let remove_alphanum_dout=%sysfunc(tranwrd(&amp;amp;remove_alphanum_dout, %str(_), %str()));&lt;/P&gt;&lt;P&gt;%if %length(&amp;amp;remove_alphanum_dout)&amp;gt;0 %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): Macro parameter DOUT (i.e. SAS dataset name) should not should not contain any special char-s.;&lt;BR /&gt;%put ERROR- (according to SAS rules about naming SAS datasets).;&lt;BR /&gt;%put ERROR- Your macro parameter DOUT has the following special char-s: &amp;amp;remove_alphanum_dout..;&lt;BR /&gt;%put ERROR- Please remove all special char-s from DOUT.;&lt;BR /&gt;%put ERROR- Macro will exit now.;&lt;BR /&gt;%goto exit;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;proc datasets library=work nolist nowarn;&lt;BR /&gt;delete &amp;amp;dout ;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%let tmpfile=%sysfunc(pathname(work))\&amp;amp;rtffile..rtf;&lt;BR /&gt;options noxwait;&lt;BR /&gt;x copy "&amp;amp;rtfloc\&amp;amp;RTFFile..rtf" "&amp;amp;tmpfile";&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;data __readrtf0;&lt;BR /&gt;infile "&amp;amp;tmpfile." missover length = l end = lastobs lrecl = 2000;&lt;BR /&gt;input string $varying2000. l;&lt;/P&gt;&lt;P&gt;if not missing(string) then do;&lt;BR /&gt;if index(string, '{\header\') then flag1=1;&lt;BR /&gt;if index(string, '{\footer\') then flag2=1;&lt;BR /&gt;if index(string, '\trowd') then flag3=1;&lt;BR /&gt;if index(string, '\row') then flag4=1;&lt;BR /&gt;if length(string)&amp;gt;=3 and substr(strip(string), 1, 3)='\cl' then flag5=1;&lt;BR /&gt;end;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%local max1 max2 max3 max4 max5;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;create table __check_keywords as&lt;BR /&gt;select max(flag1) as flag1,&lt;BR /&gt;max(flag2) as flag2,&lt;BR /&gt;max(flag3) as flag3,&lt;BR /&gt;max(flag4) as flag4,&lt;BR /&gt;max(flag5) as flag5&lt;BR /&gt;from __readrtf0&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;%local keywords;&lt;BR /&gt;%let keywords=0;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;select count(*) into : keywords&lt;BR /&gt;from __check_keywords&lt;BR /&gt;where flag1=1 and flag2=1 and flag3=1 and flag4=1 and flag5=1&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;BR /&gt;%let keywords=&amp;amp;keywords;&lt;/P&gt;&lt;P&gt;%if &amp;amp;keywords=0 %then %do;&lt;BR /&gt;%put ERROR: (macro &amp;amp;SYSMACRONAME): This macro works only on RTF documents created by SAS.;&lt;BR /&gt;%put ERROR- It looks like either of the following happened:;&lt;BR /&gt;%put ERROR- ;&lt;BR /&gt;%put ERROR- (1) This RTF was not created by SAS;&lt;BR /&gt;%put ERROR- (2) This RTF *was* created by SAS, but someone *edited* it after it was generated by SAS.;&lt;BR /&gt;%put ERROR- (3) A Word document was created by SAS and the user converted it to RTF.;&lt;BR /&gt;%put ERROR- ;&lt;BR /&gt;%put ERROR- In either of these cases, macro will not work on this RTF doc and will exit now.;&lt;BR /&gt;%put ERROR- ;&lt;BR /&gt;%put ERROR- FYI: here is an explanation:;&lt;BR /&gt;%put ERROR- When RTF doc is created by SAS, a certain RTF code is generated, and this macro works with this code.;&lt;BR /&gt;%put ERROR- For example, this code will contain such key words as \trowd, \row,{\header\, {\footer\, \cl.;&lt;BR /&gt;%put ERROR- However, if RTF doc is *not* created by SAS, the keywords mentioned above won_t be created, and therefore this macro won_t work.;&lt;BR /&gt;%goto exit;&lt;/P&gt;&lt;P&gt;%end;&lt;/P&gt;&lt;P&gt;data __readrtf1;&lt;BR /&gt;infile "&amp;amp;tmpfile." missover length = l end = lastobs lrecl = 2000;&lt;BR /&gt;input string $varying2000. l;&lt;BR /&gt;rownum = _n_;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;retain c1-c99&lt;BR /&gt;dropme indent;&lt;/P&gt;&lt;P&gt;length c1-c99 $1000;&lt;BR /&gt;if _n_ = 1 then dropme = 0.5;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;array c{99} $;&lt;/P&gt;&lt;P&gt;if index(string, '\trowd') then do;&lt;BR /&gt;count = 0;&lt;BR /&gt;indent = 0;&lt;BR /&gt;do i=1 to dim(c);&lt;BR /&gt;c{i} = '';&lt;BR /&gt;end;&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;{ ...{\line} (Scenario 2)&lt;BR /&gt;RTF control word which signals that a row was split into 2+ lines&lt;BR /&gt;This will be line #1.&lt;BR /&gt;example: ... \cf1{P: Abnormal dreams/{\line}&lt;BR /&gt;Note: cfN=background color&lt;/P&gt;&lt;P&gt;{\line} (Scenario 3)&lt;BR /&gt;Note that there is no left curly bracket { !&lt;BR /&gt;This happens when a line is split into 2+ lines:&lt;BR /&gt;This will be line #2, 3, etc., but NOT the last line.&lt;BR /&gt;example: S: Psychiatric disorders/{\line}&lt;/P&gt;&lt;P&gt;... \cell} (Scenario 4)&lt;BR /&gt;Note that there is no left curly bracket { !&lt;BR /&gt;This happens when a line is split into 2+ lines.&lt;BR /&gt;This will be the last line.&lt;BR /&gt;example: V: VIVID DREAMING\cell}&lt;BR /&gt;;&lt;/P&gt;&lt;P&gt;if ( index(string, '{') and index(string, '\cell'))&lt;BR /&gt;&lt;BR /&gt;or ( count(string,"{")&amp;gt;=2 and index(string, '{\line}' ))&lt;BR /&gt;&lt;BR /&gt;or ( count(string,"{")=1 and index(string, '{\line}'))&lt;BR /&gt;or ( count(string,"{")=0 and index(string, '\cell}'))&lt;BR /&gt;then do;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;BR /&gt;first_bracket=index(string, '{');&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;if ( index(string, '{') and index(string, '\cell'))&lt;BR /&gt;or ( count(string,"{")&amp;gt;=2 and index(string, '{\line}' ))&lt;BR /&gt;then count + 1;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;if (index(string, '{') and index(string, '\cell')) then do;&lt;BR /&gt;prep = substr(string, 1, index(string, '\cell')-1);&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;if first_bracket ne 0 then prep= substr(prep, first_bracket+1);&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;else if count(string,"{")&amp;gt;=2 and index(string, '{\line}') then do;&lt;BR /&gt;%*** extract part of PREP which comes after the first curved/angled bracket {;&lt;BR /&gt;prep= substr(string, first_bracket+1);&lt;BR /&gt;%*** replace '{\line}' with blanks;&lt;BR /&gt;prep=tranwrd(prep, '{\line}', '');&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;%*** (Scenario 1) \cell signals the end of text printed in the cell&lt;BR /&gt;(Scenario 2) a row was split into 2+ lines, and this will be line #1.;&lt;BR /&gt;if ( index(string, '{') and index(string, '\cell'))&lt;BR /&gt;or ( count(string,"{")&amp;gt;=2 and index(string, '{\line}' ))&lt;BR /&gt;then do;&lt;BR /&gt;c{count} = compress(prep, byte(13));&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;%*** (Scenario 3) When a line is split into several lines:&lt;BR /&gt;This will be line #2, 3, etc., but NOT the last line: end with {\line};&lt;BR /&gt;else if ( count(string,"{")=1 and index(string, '{\line}')) then do;&lt;BR /&gt;%*** Do:&lt;BR /&gt;1. take c{count} from Scenario 2&lt;BR /&gt;2. extract part of STRING which comes before {\line}&lt;BR /&gt;3. take 1 + 2 and separate them by blanks;&lt;BR /&gt;c{count}=catx(' ', c{count}, substr(string, 1, index(string, '{\line}')-1) );&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%*** (Scenario 4) When a line is split into several lines:&lt;BR /&gt;This will be the last line: end with \cell};&lt;BR /&gt;else if ( count(string,"{")=0 and index(string, '\cell}')) then do;&lt;BR /&gt;%*** Do:&lt;BR /&gt;1. take c{count} from Scenario 3&lt;BR /&gt;2. extract part of STRING which comes before \cell}&lt;BR /&gt;3. take 1 + 2 and separate them by blanks;&lt;BR /&gt;c{count}=catx(' ', c{count}, substr(string, 1, index(string, '\cell}')-1) );&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;if index(string, '{ ') then do;&lt;BR /&gt;indent_start=index(string, '{ ')+1;&lt;BR /&gt;%** if first symbols after '{ ' are \line then assign indent=0;&lt;BR /&gt;if length(string)&amp;gt;=5 and substr( strip(substr(string, indent_start)), 1,5)= '\line' then indent=0 ;&lt;BR /&gt;else do;&lt;BR /&gt;%*** VERIFY() function: find location of the first char which is not a space (' ');&lt;BR /&gt;other_char_start=verify( substr(string, indent_start), ' ')+indent_start;&lt;BR /&gt;indent=other_char_start-indent_start;&lt;BR /&gt;end;&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;else do;&lt;BR /&gt;%*** if there are any digits/numbers after \li then assign sst;&lt;BR /&gt;sst = substr(string, index(string, '{\li') + 4);&lt;BR /&gt;%*** VERIFY() function: find location of the first char which is not a number;&lt;BR /&gt;if verify(sst, '-0123456789') &amp;gt; 1 then indent=input(substr(sst, 1, verify(sst, '-0123456789') - 1), best.);&lt;BR /&gt;end;&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;%*** if indent&amp;gt; 0 and string has \li240, etc., then then replace it with '';&lt;BR /&gt;if indent&amp;gt;0 and not missing(sst) then do;&lt;BR /&gt;c{count} = strip(tranwrd(c{count}, '\li'||strip(put(indent, best.)), ''));&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;if index(c{count}, '\line') then do;&lt;BR /&gt;if substr(strip(c{count}), 1, 5)='\line' then c{count}=strip(tranwrd(c{count}, '\line', ''));&lt;BR /&gt;else do;&lt;BR /&gt;c{count}=tranwrd(c{count}, '{\line}', '');&lt;BR /&gt;c{count}=tranwrd(c{count}, '\line', '');&lt;BR /&gt;end;&lt;BR /&gt;c{count}=compbl(c{count});&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;c{count}=strip(c{count}); %*** get read of leading and trailing blanks;&lt;BR /&gt;&lt;BR /&gt;end; %*** end of: if index(string, '{') and index(string, '\cell') then do;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;if dropme =999 then dropme = 0; %*** This signals the beginning of table body (i.e. note titles, columns headers or footnotes),&lt;BR /&gt;and only rows with DROPME=0 will be kept.;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%* (1) Find where you encounter a footer (i.e. find '{\footer\' in STRING), set DROPME=1 to signal we_re in the title area.;&lt;BR /&gt;if substr(string, 1, 9)='{\header\' then dropme = 0.6;&lt;BR /&gt;else if index(string, '}}') and dropme=0.6 then dropme = 0.7;&lt;BR /&gt;else if substr(string, 1, 9)='{\footer\' and dropme=0.7 then dropme = 1;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%**********************************************************************************;&lt;BR /&gt;%*** Repeat steps below for each row in column headers (i.e. up to &amp;amp;subheader_num);&lt;BR /&gt;%**********************************************************************************;&lt;/P&gt;&lt;P&gt;%local s;&lt;BR /&gt;%do s=1 %to &amp;amp;subheader_num;&lt;/P&gt;&lt;P&gt;%*put ===&amp;gt; s=&amp;amp;s;&lt;BR /&gt;if index(string, '\trowd' )&lt;BR /&gt;and ( (dropme =1 and index(string, '}}' )) /* For the first iteration, DROPME=1.&lt;BR /&gt;Find next row where footer ends (i.e. find '}}' and '\trowd' in STRING) */&lt;BR /&gt;or&lt;BR /&gt;dropme=4+%sysevalf(&amp;amp;s*10-10) /* If column headers spread over several rows (i.e. if &amp;amp;subheader_num&amp;gt;1) */&lt;BR /&gt;)&lt;BR /&gt;then dropme = 2+%sysevalf(&amp;amp;s*10); %*** DROPME will be 12, 22, 32, etc., depending on &amp;amp;subheader_num;&lt;/P&gt;&lt;P&gt;%*** Find next row where column headers begin (i.e. find '\cl' in STRING) and assign DROPME=13, 23, 33, etc., depending on &amp;amp;subheader_num;&lt;BR /&gt;else if length(string)&amp;gt;=3 and substr(strip(string), 1, 3)='\cl' and dropme = 2+%sysevalf(&amp;amp;s*10) then dropme = 3+%sysevalf(&amp;amp;s*10); %** \clbrdrb, \cltxlrtb, etc.;&lt;/P&gt;&lt;P&gt;%*** If there are no more column headers in the next row (i.e. if &amp;amp;s=&amp;amp;subheader_num ), then assign DROPME=999;&lt;BR /&gt;%if &amp;amp;s=&amp;amp;subheader_num %then %do;&lt;BR /&gt;else if index(string, '\row' ) and dropme = 3+%sysevalf(&amp;amp;s*10) then dropme = 999;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%*** If there are more column headers, spreaded over next row, then assign DROPME=14, 24, 34, etc., depending on &amp;amp;subheader_num;&lt;BR /&gt;%else %do;&lt;BR /&gt;else if index(string, '\row' ) and dropme = 3+%sysevalf(&amp;amp;s*10) then dropme =4+%sysevalf(&amp;amp;s*10);&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;if not dropme and index(string, '\row') then do;&lt;BR /&gt;allblank = 1;&lt;BR /&gt;do i=1 to dim(c);&lt;BR /&gt;if compress(c{i}, ' \') ne '' then allblank = 0;&lt;BR /&gt;end;&lt;BR /&gt;/* %put ERROR: unquote;*/&lt;BR /&gt;if allblank=0 then output;&lt;BR /&gt;end;&lt;/P&gt;&lt;P&gt;%jump:&lt;/P&gt;&lt;P&gt;run;&lt;/P&gt;&lt;P&gt;/*%goto exit;*/&lt;/P&gt;&lt;P&gt;%*** Delete RTF file which you copied to temporary location;&lt;BR /&gt;x del "&amp;amp;tmpfile";&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;proc transpose data=__readrtf1(drop=count indent_start other_char_start) out=__chk;&lt;BR /&gt;var c:;&lt;BR /&gt;by rownum;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%local dropper;&lt;BR /&gt;%let dropper=;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;select distinct _name_ into: dropper&lt;BR /&gt;separated by ' '&lt;BR /&gt;from __chk&lt;BR /&gt;where _name_ not in (select _name_ from __chk where col1 ne '')&lt;BR /&gt;;&lt;/P&gt;&lt;P&gt;%local numcols;&lt;BR /&gt;%let numcols=0;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;select distinct count(distinct _name_) into: numcols&lt;BR /&gt;from __chk&lt;BR /&gt;where col1 ne ''&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;BR /&gt;%let numcols=&amp;amp;numcols; %*** to get rid of leading blanks;&lt;BR /&gt;&lt;BR /&gt;%if &amp;amp;numcols&amp;gt;1 %then %do;&lt;BR /&gt;data __readrtf1;&lt;BR /&gt;set __readrtf1;&lt;BR /&gt;if missing(c1) then indent=1;&lt;BR /&gt;run;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;proc sort data=__readrtf1(drop=count &amp;amp;dropper indent_start other_char_start string dropme prep first_bracket sst i allblank) out=__readrtf2;&lt;BR /&gt;by indent ;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;data __readrtf4 /*(index=(rownum))*/;&lt;BR /&gt;set __readrtf2;&lt;BR /&gt;by indent;&lt;BR /&gt;if first.indent then order2 + 1;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%*** create order1, etc. from INDENT;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;create table __ind1 as&lt;BR /&gt;select indent, rownum&lt;BR /&gt;from __readrtf4&lt;BR /&gt;order by indent, rownum&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;data __ind2;&lt;BR /&gt;set __ind1;&lt;BR /&gt;by indent rownum;&lt;BR /&gt;retain order;&lt;BR /&gt;if first.indent then order+1;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;proc sort data=__ind2;&lt;BR /&gt;by rownum order;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;data __ind3;&lt;BR /&gt;set __ind2;&lt;BR /&gt;by rownum order;&lt;BR /&gt;retain order1;&lt;BR /&gt;if _n_=1 then order1=1;&lt;BR /&gt;else if order=1 then order1+1;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;proc sort data=__ind3;&lt;BR /&gt;by order1 indent rownum order;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;data __ind4;&lt;BR /&gt;set __ind3 (rename=(order=order_old));&lt;BR /&gt;by order1 indent rownum order_old;&lt;BR /&gt;retain order;&lt;BR /&gt;if first.order1 then order=0;&lt;BR /&gt;if first.indent then order+1;&lt;BR /&gt;drop order_old;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%*** Assign variable level_order1, which is number of unique ORDER within each ORDER1;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;create table __ind5 as&lt;BR /&gt;select *, max(order) as level_order1&lt;BR /&gt;from __ind4&lt;BR /&gt;group by order1&lt;BR /&gt;order by order1, rownum, order&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;%*** how many are there distinct levels of ORDER? Create order1, order2, etc. based on this number of levels;&lt;BR /&gt;%local levels;&lt;BR /&gt;proc sql noprint;&lt;BR /&gt;select max(order) into : levels&lt;BR /&gt;from __ind5&lt;BR /&gt;;&lt;BR /&gt;quit;&lt;BR /&gt;%let levels=&amp;amp;levels; %** to get rid of leading blanks;&lt;BR /&gt;%*put levels=&amp;amp;levels;&lt;/P&gt;&lt;P&gt;%*** if there more than 1 levels, do this;&lt;BR /&gt;%if &amp;amp;levels&amp;gt;1 %then %do;&lt;/P&gt;&lt;P&gt;data __ind6;&lt;BR /&gt;set __ind5;&lt;BR /&gt;by order1 rownum order;&lt;BR /&gt;lag_order=lag(order);&lt;BR /&gt;lag_order1=lag(order1);&lt;/P&gt;&lt;P&gt;retain order2-order&amp;amp;levels;&lt;/P&gt;&lt;P&gt;array ord(*) order2- order&amp;amp;levels;&lt;BR /&gt;array lag_ord(*) lag_order2- lag_order&amp;amp;levels;&lt;/P&gt;&lt;P&gt;do i=1 to dim(ord);&lt;/P&gt;&lt;P&gt;if first.order1 then ord(i)=0;&lt;BR /&gt;if i ne 1 then do;&lt;BR /&gt;lag_ord(i-1)=lag(ord(i-1));&lt;BR /&gt;if lag_ord(i-1) ne ord(i-1) then ord(i)=0;&lt;BR /&gt;end;&lt;BR /&gt;if order=i+1 then ord(i)=ord(i)+1;&lt;BR /&gt;if level_order1=i+1 and lag_order &amp;gt; order then ord(i)=0; %*** if level_order1=i+1 means: if there are no more levels after this ord(i);&lt;BR /&gt;end;&lt;BR /&gt;drop i;&lt;/P&gt;&lt;P&gt;drop lag_:;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;proc sort data=__readrtf4;&lt;BR /&gt;by rownum;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;proc sort data=__ind6;&lt;BR /&gt;by rownum;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;data __readrtf5;&lt;BR /&gt;merge __readrtf4 (drop=order2 )&lt;BR /&gt;__ind6 (drop=order indent level_order1);&lt;BR /&gt;by rownum;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%end; %** end of: &amp;amp;levels&amp;gt;1;&lt;/P&gt;&lt;P&gt;%else %do;&lt;BR /&gt;data __readrtf5;&lt;BR /&gt;set __readrtf4 (drop=order2 );&lt;BR /&gt;order1=_n_;&lt;BR /&gt;run;&lt;BR /&gt;%end;&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;%*** reassign rownum;&lt;BR /&gt;data &amp;amp;dout;&lt;BR /&gt;set __readrtf5 (drop=rownum);&lt;BR /&gt;rownum=_n_;&lt;BR /&gt;if indent ne 0 then indented='Y';&lt;BR /&gt;drop indent;&lt;BR /&gt;run;&lt;/P&gt;&lt;P&gt;%exit:&lt;/P&gt;&lt;P&gt;options &amp;amp;opts; %*** whatever system options you changes - restore them to what they used to be before running this macro;&lt;/P&gt;&lt;P&gt;proc datasets library=work nolist nowarn;&lt;BR /&gt;delete __readrtf: __chk __ind: __check_keywords;&lt;BR /&gt;quit;&lt;/P&gt;&lt;P&gt;%mend extract;&lt;/P&gt;</description>
      <pubDate>Thu, 13 Oct 2016 17:23:47 GMT</pubDate>
      <guid>https://communities.sas.com/t5/ODS-and-Base-Reporting/Reading-a-RTF-table-into-SAS-data-set-without-capturing/m-p/304429#M17149</guid>
      <dc:creator>knveraraju91</dc:creator>
      <dc:date>2016-10-13T17:23:47Z</dc:date>
    </item>
    <item>
      <title>Re: Reading a RTF table into SAS data set without capturing footnotes in last OBS line</title>
      <link>https://communities.sas.com/t5/ODS-and-Base-Reporting/Reading-a-RTF-table-into-SAS-data-set-without-capturing/m-p/304442#M17151</link>
      <description>Hi:&lt;BR /&gt;  I would really recommend that you ask the author of this macro program for help in modifying the program.&lt;BR /&gt; &lt;BR /&gt;cynthia</description>
      <pubDate>Thu, 13 Oct 2016 18:34:43 GMT</pubDate>
      <guid>https://communities.sas.com/t5/ODS-and-Base-Reporting/Reading-a-RTF-table-into-SAS-data-set-without-capturing/m-p/304442#M17151</guid>
      <dc:creator>Cynthia_sas</dc:creator>
      <dc:date>2016-10-13T18:34:43Z</dc:date>
    </item>
    <item>
      <title>Re: Reading a RTF table into SAS data set without capturing footnotes in last OBS line</title>
      <link>https://communities.sas.com/t5/ODS-and-Base-Reporting/Reading-a-RTF-table-into-SAS-data-set-without-capturing/m-p/604042#M23508</link>
      <description>&lt;P&gt;Hi:&lt;/P&gt;&lt;P&gt;&amp;nbsp; I wonder if the whole macro is as long as you show it. And it's so complex.&lt;/P&gt;&lt;BLOCKQUOTE&gt;&lt;HR /&gt;&lt;BLOCKQUOTE&gt;&lt;HR /&gt;&lt;/BLOCKQUOTE&gt;&lt;HR /&gt;&lt;/BLOCKQUOTE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Thu, 14 Nov 2019 08:30:53 GMT</pubDate>
      <guid>https://communities.sas.com/t5/ODS-and-Base-Reporting/Reading-a-RTF-table-into-SAS-data-set-without-capturing/m-p/604042#M23508</guid>
      <dc:creator>yh_zhang</dc:creator>
      <dc:date>2019-11-14T08:30:53Z</dc:date>
    </item>
    <item>
      <title>Re: Reading a RTF table into SAS data set without capturing footnotes in last OBS line</title>
      <link>https://communities.sas.com/t5/ODS-and-Base-Reporting/Reading-a-RTF-table-into-SAS-data-set-without-capturing/m-p/605349#M23520</link>
      <description>&lt;P&gt;It would be better to add a step and remove the last row :&lt;/P&gt;
&lt;P&gt;&lt;FONT face="courier new,courier"&gt;data TAB;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="courier new,courier"&gt;&amp;nbsp; set TAB obs=NOBS;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="courier new,courier"&gt;&amp;nbsp; if _N_ ne NOBS;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="courier new,courier"&gt;run;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Tue, 19 Nov 2019 10:05:54 GMT</pubDate>
      <guid>https://communities.sas.com/t5/ODS-and-Base-Reporting/Reading-a-RTF-table-into-SAS-data-set-without-capturing/m-p/605349#M23520</guid>
      <dc:creator>ChrisNZ</dc:creator>
      <dc:date>2019-11-19T10:05:54Z</dc:date>
    </item>
  </channel>
</rss>

