/*
* Copyright 2004 Richard A. DeVenezia
* All Rights Reserved.
*
* http://www.devenezia.com
*
* This code may only be distributed upon consent of
* the copyright holder. All distributions must
* contain this notice.
*/
/*
* configuration data
*/
$source_folder = "./"; // location of the SAS sources
$item_pattern = "/(sas|ora)$/"; // file matching criteria
$navdata_file = "./navdata.php"; // location where generated navigation data should be saved to and loaded from
$lnkdata_file = "./links4hmp.html"; // location where generated list of links should go (if HotMetalPro project links to this location, then the size of the orphan file report is smaller)
$nav_js_file = "macros.js"; // location where javascript navigation source written to and client will read from
$css_file = "macros.css"; // location where client will read some css specifiers from (css is static)
/*
* 031117 rad initial issue
* 031120 rad handle ?m=
* 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$items[$j]\n
Purpose:
".$iteminfo['purpose']."
";
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
"
. "
" // note: event handlers will be installed by body onload='load(...)'
. "$group (".count($items).")"
. "
$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]+""+h+">
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;iSAS Macros
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.