* 031127 rad handle oldie, add DOCTYPE * 031209 rad add search * 031213 rad add purpose and notes to generated code (so as to be indexed by search engines) */ /* * This code examines SAS macro source files for information tags which are SAS comments of construct slash star dash stuff star slash * stuff should contain separate lines with construct group: ... purpose: ... notes: ... * The information data ends up as category headers and * purpose and notes are shown when a macro is rolled over or selected * The data is stored in nested associative arrays, a typical hierarchical structure * * All special chars are changed to html entities when displaying the SAS * source code as HTML. This is necessary since the SAS code might contain * the character "greater than", etc... * * However, in the spirit of javadoc, we may actually want to embed html in a SAS * comment, so that some post processor (such as myself) can show the source as * well as some extra "juice". * * Any SAS comment of construct slash star star H T M L html star slash * When rendering the source code as HTML, any thing interior to this comment * will be emitted unchanged (instead of being escaped to HTML entities) * * * Future design considerations: * - reduce extraneous macro payload by placing sample code in a separate file * samples of filename.sas would be in filename-sample.sas, or sample-filename.sas * source rendering portion would seek out and render such sources as appropriate */ /* * This script handles these requests * m= * search= * search2= * * Note: The html forms generated by this script are method=post, which * means you will not see search=searchTerm in any urls */ /* * m= is the requested macro, either by name (best way) or * groupnumber.itemnumber (worse way since the numbers can * change as content is added */ $selection = isset ($_REQUEST ['m']) ? $_REQUEST ['m'] : 0; $search_term = isset ($_REQUEST ['search2']) ? $_REQUEST ['search2'] : ( isset ($_REQUEST ['search']) ? $_REQUEST ['search'] : '' ); /* * split requested macro into group and item (may not be applicable) */ list ($group_num, $item_num) = split ("[.]", "$selection.-1"); $item_nam = $selection; /* * navigation data needs to be regenerated * - if this script is newer than the navdata * - if any .sas or .ora source files are newer than the navdata */ $navdata_need_refresh = filemtime ( $_SERVER['SCRIPT_FILENAME'] ) > @filemtime ( $navdata_file ); $dir = opendir ( $source_folder ); $navdata_time = @filemtime ( $navdata_file ); while (($file = readdir($dir)) !== false && !$navdata_need_refresh) { if (! preg_match ($item_pattern, $file)) continue; $navdata_need_refresh = filemtime ( $file ) > ( $navdata_time ); } /* * if necessary, store navdata in navadata file */ if ( $navdata_need_refresh ) { $navdata = array(); $lnkdata = ""; rewinddir($dir); while (($file = readdir($dir)) !== false) { /* * skip files not of concern */ if (! preg_match ($item_pattern, $file)) continue; /* * read entire file into a string and * convert DOS line terminators to UNIX line terminators */ $contents = join ("", file($file)); $contents = preg_replace ("/\r\n/", "\n", $contents); /* * skip files not having the special comment * slash star dash * stuff * star slash */ if (! preg_match ("/\/\*-+\n(.*?)\*\//s", $contents, $info)) continue; /* * locate and extract information from the special comment */ preg_match ( "/group:\s*(.*)/", $info[1], $group ); preg_match ( "/purpose:\s*(.*)/", $info[1], $purpose ); preg_match ( "/notes:\s*(.*)/", $info[1], $notes ); /* * extract information string from regex match */ $group = ($group) ? $group[1] : 'Group unknown'; $purpose = ($purpose) ? $purpose[1] : ''; $notes = ($notes) ? $notes[1] : ''; /* * create an information data 'record' */ $info = array(); $info["purpose"] = $purpose; $info["notes"] = $notes; /* * each group gets its own array to store its items */ if (!array_key_exists($group, $navdata)) $navdata [ $group ] = array(); /* * store the info data in the group, keyed by filename */ $navdata[$group][$file] = $info; /* * append to string containing html that references * each file in the navdata. the lnkdata ends up in an * 'invisible' stub file. the stub exists only for * tweaking unreferenced file reporting. */ $lnkdata .= ""; } /* * key sorting the navdata ensures the groups appear in * alphabetical order */ ksort ($navdata); /* * store the navdata in a php file. the file is later * required, so as to load the navdata (recall that this * section only runs when navdata has to be recreated */ $f = fopen ($navdata_file, "wb"); fwrite ($f, '"); fclose ($f); /* * write an html file having links to the sources processed */ $f = fopen ($lnkdata_file, "wb"); fwrite ($f, "Links for HotMetalPro$lnkdata

Links are in the head.

"); fclose ($f); } closedir ($dir); //----- /* * load the navigation data */ require $navdata_file; /* * determine the group num and item number that corresponds to the requested item * also determine the requested items information * note the use of class=. CSS lets us alter rendering style * without changing anything in this script. */ $item_found = 0; $info = ""; if ($item_nam != "") { $groups = array_keys($navdata); for ($i=0;$i"; if ($iteminfo['notes'] != '') $info .= "\n

Notes:

".$iteminfo['notes']."

"; $item_found = 1; } } } /* * if item not found, perhaps the request was of the deprecated construct groupNum.itemNum * if not of such construct, group and item number are set to invalid values */ if (! $item_found) { $group_num += 0; if ($group_num<0 || $group_num>=count($navdata)) { $group_num = 0; $item_num = -1; } else if (isset($item_num)) { $groups = array_keys($navdata); $group = $groups[$group_num]; $items = $navdata[$group]; $item_num += 0; if ($item_num<0 || $item_num>=count($items)) $item_num = -1; } else $item_num = -1; } } else { $group_num = 0; $item_num = -1; } /* * this function generates the html lists that enumerate the groups and items in the groups * at client browse time, careful dhtml manipulation will give the effect of things opening and closing * blank items at the end of short lists causes the 'cool bar' effect, that of thing X opening to * reveal its innards, while the thing Y which the thing X is in does not change size */ function selection_menu () { global $navdata, $group_num, $item_num; //----- determine max number of items in a group $maxi = 0; foreach ($navdata as $group => $items) { $maxi = max ($maxi, count($items)); } //----- generate html that functions like a cool bar $groupCount = 0; foreach ($navdata as $group => $items) { print "\n"; if ($groupCount == $group_num && 0 <= $item_num && $item_num < count($items)) $ulstyle = " style='display:block'"; else $ulstyle = ""; print "\n"; $itemCount = 0; foreach ($items as $level2 => $level2data) { $level2 = preg_replace ("/\.sas$/","",$level2); if ($groupCount == $group_num && $itemCount == $item_num) $liclass = " class='showing'"; else $liclass = ""; if (preg_match ("/MSIE 5\.(01|5); /", getenv("HTTP_USER_AGENT"))) $aclass = " class='oldie'"; else $aclass = ""; print "\n$level2"; $itemCount++; } $groupCount++; /* * ensure every group has the same total number of items */ print "\n"; for (;$itemCount<$maxi;$itemCount++) { print "
  •  
  • "; } print ""; } } /* * if necessary, generate javascript that contains every items information * while a little redundant having navdata in php and javascript, it makes * the pages more amenable to search engine processing. (If the nav system * [the stuff in selection_menu()] was generated dynamically in the client * via javascript at client browse time, then it would be likely the search * engines would not spider off to all the macro) * * the javascript is a whole independent system that gets its data * from the state determined by this php script */ if ( $navdata_need_refresh ) { /* * generate javascript array assignment statement * * javascript associative arrays * M - filenames * P - purpose * N - notes */ $M = $P = $N = ""; $comma1=' '; foreach ($navdata as $level1 => $level1data) { $M .= "\n".$comma1."["; $P .= "\n".$comma1."["; $N .= "\n".$comma1."["; $comma1=','; $comma2=' '; foreach ($level1data as $level2 => $level2data) { $M .= $comma2.'"' . $level2 . '"'; $P .= $comma2.'"' . str_replace('"','\"',$level2data['purpose']) . '"'; $N .= $comma2.'"' . str_replace('"','\"',$level2data['notes']) . '"'; $comma2=','; } $M .= ']'; $P .= ']'; $N .= ']'; } /* * generate javascript source which will be written to nav_js_file. * when run at browse time this is the careful dhtml manipulation */ $javascript = <<"+M[x][y]+"

    Purpose:

    "+P[x][y]+"

    " if (N[x][y] != "") html += "

    Notes:

    "+N[x][y]+"

    " return html } function mover(e){var tg=e?e.target?e.target:this:this document.getElementById('info').innerHTML = infoHTML (tg.id.split('_'),'h3') document.getElementById('info').style.display='block' } function mout(e){var tg=e?e.target?e.target:this:this document.getElementById('info').style.display='none' if(moff)mover(moff) } function grover(e){var tg=e?e.target?e.target:this:this;if(tg!=gropen)tg.style.backgroundColor="#ccc"} function grout (e){var tg=e?e.target?e.target:this:this;if(tg!=gropen)tg.style.backgroundColor="#ddd"} function grick (e){var tg=e?e.target?e.target:this:this if (typeof gropen != 'undefined' && tg != gropen) { gropen.style.backgroundColor='#ddd' gropen.style.fontWeight='normal' gropen.style.borderColor='#888' itopen.style.display='none' } gropen = tg gropen.style.backgroundColor='#88BBFF' gropen.style.fontWeight='bold' gropen.style.borderColor='#6699FF' var itgid = gropen.id.replace(/group/,'items') itopen = document.getElementById(itgid) itopen.style.display='block' } function load(x,y) { // hash part of url (stuff after #) is for positioning web page to a named A tag // hash part is not available to web server side // it is strictly client side, thus process hash requests here // while setting up event handlers, check if the hash part matches a known macro var hash = location.hash.substring(1) var re = new RegExp (); re.compile ("^"+hash+"[.]", "i") var obj1, obj2 for (var i=0;i SAS Macros

    SAS Macros

    by Richard A. DeVenezia

    This collection of SAS macros is part of my SAS programming toolbox. They have been categorized into groups based on typical usage.

    They have not been peer tested for quality assurance and are presented as-is. Some macros may rely on host specific features. Use at your own risk.

    Send questions or feedback to Richard A. DeVenezia.

    How to use this page:

    In the selection zone on the left side, a series of category headers are displayed. Each header lists how many items it contains. Click on a header to open the category and see its items.

    When you roll the mouse over an item, some information about the item (purpose and notes) will appear in an information zone beneath the selection zone.

    Click on an item to see the source code of the item. There will also be a download link.

    What if the information zone contains a link you want to follow? Click on the item to lock the information. Then you can follow the links if desired.

    The original index page is still available, but will not be updated.

    This page generator last updated 08 December 2003.
    DEFAULT; if ($search_term != "") { /* * perform searh and display the results */ print "

    Search results

    "; $results = ''; $n_found = 0; foreach ($navdata as $group => $items) { foreach ($items as $file => $info) { $contents = join ("", file($file)); if (@preg_match ("/$search_term/i", $contents)) { $n_found ++; $results .= "
  • $file"; } } } if ($n_found==0) { print "

    No macros"; } else if ($n_found==1) { print "

    One macro"; } else { print "

    $n_found macros"; } print " matched the regular expression pattern.

    " . "" . "" . "

    "; print "
      $results
    "; } else if ($item_num == -1) { // display welcome mat print $default; } else { /* * read in the macro source and prepare it for * being rendered as html */ $groups = array_keys($navdata); $group = $groups[$group_num]; $items = array_keys($navdata[$group]); $file = $items[$item_num]; $contents = join ("", file($file)); $contents = preg_replace ("/\r\n/", "\n", $contents); $contents = preg_replace ("/\n*\/\*-+\n(.*?)\*\/\n*/s", "\n\n", $contents); // strip macro info tags /* * split source on HTML comments, * emit HTML portion of special SAS HTML comment between splits */ $split = preg_split ("/\/\*\*HTML(.*?)\*\//si", $contents, -1, PREG_SPLIT_DELIM_CAPTURE); $pre = ''; for ($i=0; $i 1) { $pre = preg_replace ('/([^>])%macro /', '
    $1%macro ', $pre);
    		}
    
    		/*
    		 * embolden the macro name in it's declaration
    		 */
    
    		$pre = preg_replace ('/(%macro\s+)(\w+)(\s+|\(|\s*;)/', '$1$2$3', $pre);
    
    		/*
    		 * render a download icon
    		 */
    
    		print "

    Download $file $file

    "; /* * render the macro soure */ print $pre; } ?>