=head1 NAME

LoadHtml - Dynamic HTML Generation Library

=head1 SYNOPSIS

     #!/usr/bin/perl

     use LoadHtml;
     print "Content-type: text/html", "\n";
     &loadhtml('mytemplate_file.html',
        -ascalararg => 'Display this string',
        -anarrayarg => ['string1', 'string2', 'string3'],
        -ahasharg => {'key1' => 10, 'key2' => 20, 'key3' => 30},
        -amatrix => [[1, 2, 3], [4, 5, 6]]
     );

=head1 DESCRIPTION AND EXAMPLES

The LoadHtml Home Page

LoadHtml - Dynamic HTML Generation Library, by Jim Turner (turnerjw784 att yahoo .dot. com).
LoadHtml is a Perl library to enable Perl CGI programs to dynamically generate HTML pages from HTML page templates.  LoadHtml includes the following special features:

          o I) Special, nestable HTML control statements (IF-THEN-ELSE, LOOP, INCLUDE, SELECTLIST, etc.)!
          o II) HTML templates can be valid stand-alone HTML pages (with default values) for rapid prototyping.
          o III) Perl code and variables can be optionally embedded or prohibited (for security reasons) on a page-by-page basis.
          o IV) HTML pages can be generated and later displayed or saved using the "buildhtml" or "dohtml" functions.
          o V) Easy, dynamic table, select, checkbox and radio-button construct- creation using Perl variables, lists and hashes.
          o VII) Multiple ways to drive loops of HTML generation (by arrays, hashes, and traditional Perl/C FOR loops, ie. "1 to 100 by 5".

LoadHtml is written completely in Perl, a modern, high-performance scripting language.  CGI web applications are completely portable across all platforms which support Perl and CGI, including Unix and Windows.

Click for System Requirements.
I) Overview / User-callable Function List.
The following functions are user-callable:

&loadhtml($htmlfile,@args)
my $htmlstring = &buildhtml($htmlfile,@args)
my $htmlstring = &dohtml($htmltemplate_string,@args)
&AllowEvals(1|0)
&set_poc($poc_name)
&SetRegices()
&SetListSeperator($separator_string)
&SetHtmlHome($htmlhome, $roothtmlhome, $hrefhtmlhome, $hrefcase)
&loadhtml_package($package_name)

The Perl CGI program calls loadhtml() when it is ready to generate and display an HTML page.  The 1st argument is the path and file-name of the html template file to be used to generate the page.  The remaining arguments are the data values to be substituted in the html page based on special HTML codes within the template page before the final page is displayed.  When loadhtml is called, the template html file is loaded and parsed, all argument/parameter substitutions are made, all dynamic html is generated, and the final html is sent to the browser to be displayed.  NOTE:  It is the calling program's responisibility to print any needed HTML headers BEFORE calling loadhtml.

By default, embedded perl code and variables in HTML templates are not evaluated for security reasons.  To enable loadhtml to process these, first call "AllowEvals(1)".  To turn back off, call "AllowEvals(0)".

Call "set_poc" at the beginning of your CGI script to set a point-of-contact name to be displayed on any error screens generated by LoadHtml.

By default, any Perl list arguments passed to loadhtml, where the corresponding html code in the template file is not within a "LOOP" or "SELECTLIST" construct; will print out all values of the list separated by a comma, followed by a space.  Call "SetListSeperator" to change this string to something else.  Within the "LOOP" and "SELECTLIST" constructs, html is dynamically generated for each element within the resulting list.
II) Basic Parameter Substitution:
loadhtml is called with the 1st argument being the filename of the HTML template file to load.  Each subsequent argument corresponds to a data-value to be added to the HTML via parameter substitution.  The simplest parameter substitution is accomplished by placing the argument number preceeded by a colon in the desired location for the corresponding data-value argument in the HTML template file.  For example, if an HTML template file named "myhtml.htm" in directory "/usr/htdocs/" looked like the following:

<HTML>
<BR>Roses are :1, violets are :2.<BR>
</HTML>

The following call to loadhtml would supply the proper values:

    loadhtml('/usr/htdocs/myhtml.htm','red','blue');

and would display the following HTML page:

<HTML>
<BR>Roses are red, violets are blue.<BR>
</HTML>

":1" is replaced by the 1st argument after the file-name, and ":2" with the second one.

NOTE: It is now possible and preferrable to call loadhtml with NAMED parameters as follows:

The above example using Named Parameters:

<HTML>
<BR>Roses are <!:roses>, violets are <!:violets:>blue normally<!:/violets>.<BR>
</HTML>

The following call to loadhtml would supply the proper values:

    loadhtml('/usr/htdocs/myhtml.htm', -roses => 'red', -violets => 'blue');

and would display the same results. NOTE: If data is not substituted using named parameters, try enclosing each "-parametername" part in single quotes. Also, the format ":{name}" is used in lieu of ":number" in the HTML whenever a value is to be substituted OUTSIDE of a tag, OR within the 'value=":{name}"' part of a tag. Otherwise (within tags), just use the format ":name".

NOTE: In the above example, we show "roses" as a single, unmatched tag. "violets" is shown as a matching tag (note the colon before the closeing >). The text in between ("blue normally") is the default text and is shown if the page is not browsed via LoadHtml.

Now, suppose we want the HTML page to function as a stand-alone page without being called by a CGI script, to demo to a customer before writing the script, you could write:

<HTML>
<BR>Roses are <:1:>red<:/1>, violets are <:violets:>blue<:/violets>.<BR>
</HTML>

This would display the same results as the previous example (note the mixing of numbered and named parameters), if the page is loaded stand-alone directly into the browser, but, if called with:

    loadhtml('/usr/htdocs/myhtml.htm','here', -violets => 'there');

would produce the following dynamically-generated page:

<HTML>
<BR>Roses are here, violets are there.<BR>
</HTML>

If no default values are desired, the template file could be written as:

<HTML>
<BR>Roses are <!:roses>, violets are <!:violets>.<BR>
</HTML>

If a different default value is desired, as when the page is loaded via LoadHtml, but without a value for that specific argument, the template file could be written as:

<HTML>
<BR>Roses are <:roses=pink:>red<:/roses>, violets are <:violets=violet:>blue<:/violets>.<BR>
</HTML>

Now if LoadHtml is called as:

    loadhtml('/usr/htdocs/myhtml.htm', -roses => 'scarlet');

The following page would display:

<HTML>
<BR>Roses are scarlet, violets are violet.<BR>
</HTML>
 
Formatting

LoadHtml also supports the "printf" function familiar to C and Perl programmers for formatting parameter as they are displayed.  If this is not sufficient, user-defined formatting functions are also supported.  For example, to right-justify numeric parameters, one could use the "printf" formatting characters:  "-10.2f" as shown below:

<BR>The results are <!:roses%-10.2f%0.00:>0.00<!:/roses>

This provides that ":roses" will be displayed using "printf" formatting, with defaults of "0.00".

To format currency, one could define a formatting function within the CGI script to place commas every 3 digits, add parenthesis if negative, etc.  For example:

sub cashit
{
    my ($val) = shift;
    my ($iter) = shift;
    my ($lastrow) = shift;

    $val = sprintf('%.2f',$val);
    $val =~ s/(\d)(\d\d\d)$/$1,$2/;
    $val =~ s/(\d)(\d\d\d),/$1,$2,/g;
    $val = '(' . $val . ')'  if ($val =~ s/^\-//);
    return ("$val");
}

Then include the following in the HTML template:

$<!:amount%&main::cashit% 0:>0<!:/amount>

This formats the dollar amount with commas every three digits and adds parenthesis if negative.  Two decimal places are also displayed.

Sometimes, simple parameter substitution is not sufficient.  LoadHtml provides several special control structures to handle more complex dynamic HTML generation.

An alternate way of specifying parameters (namely, within HTML tags) is to enclose the parameter name between ":{ and "}". For example:

<A HREF="<!:roses>">Link to Rose colors</A>

would fail since the closing ">" of the parameter would close the <A> tag! To avoid this, specify the parameter as:

<A HREF=":{roses}">Link to Rose colors</A>

Substituting parameters in places where HTML does not allow special tags:

Sometimes it is necessary to embed a parameter substitution where HTML does not permit a special tag, ie a button with a default name. For example, suppose a page should have a form submit button with a default value of "Create Record":

<input type="submit" value="Create Record">

Now, if the page is loaded via LoadHTML, it is to be set to ":{arg} Record" if parameter "arg" is specified, otherwise, it is to be set to "Add Record". This can be accomplished with the following code:

<!:arg=Add[<input type="submit" value=":{arg} Record"&gt;]:><input type="submit" value="Create Record"><!:/arg>

This hyroglyphics will cause a submit button with the words "Create Record" to be displayed if page is just displayed without LoadHTML, It will be created with the "default" value "Add Record" if loaded by loadhtml() (cgi) but no value for "arg" is passed. If a value is passed to the "arg" parameter, then that value is used, ie. "arg => 'Update'" would yield a button with the displayed value of "Update Record". NOTE the use of "&gt" instead of ">" since html terminates comments with ">". The way this works is the html enclosed in the <!:arg...>...<!:/arg> tag is replaced by what's between the [ ] within the tag. The string "Add" (between the "=" sign and the "[" is the "default" value used in leau of ":{arg}" if no value is passed to that parameter (-arg => 'some value').
III) Control Statement Tags:

"IF-THEN-ELSE" statement:

<!IF[statement_name] condition>
    -body-
<ELSE[statement_name]>
    -body-
<!/IF[statement_name]>

The "ELSE" part is optional. The "statement_name" is optional, but should always be used if nesting IF statements. Consider the following HTML template file:

<HTML>
<H3>Jim's Joke Page!</H3>
<BR>
<!IF :flora =~ /flowers/i>
    <BR>Roses are <!:arg1>, violets are <!:arg2>.<BR>
<!ELSE>
    <BR>Knock Knock, who's there? <!:arg1>, <!:arg1> who?, <!:arg2>, that's who!
<!/IF>
</HTML>

This example will generate two different joke-lines, depending on the value passed as argument #1.

    loadhtml('/usr/htdocs/myhtml.htm', -flora => 'FLOWERS', -arg1 => 'red', -arg2 => 'blue');

will produce:

<HTML>
<H3>Jim's Joke Page!</H3>
<BR>
<BR>Roses are red, violets are blue.
</HTML>

whereas:

    loadhtml('/usr/htdocs/myhtml.htm', -flora => 'VEGETABLES', -arg1 => 'Foold', -arg2 => 'Fooled You!');

will produce:
<HTML>
<H3>Jim's Joke Page!</H3>
<BR>
<BR>Knock Knock, who's there? Foold, Foold who?, Fooled You!, that's who!
</HTML>

NOTE:  The "ELSE" portion is not required.

If one of the parts is desired for a standalone (no CGI) default, the other can be commented out with HTML comments, for example (NOTE: the "statement_name" is included and is "_STMT1":

<!IF_STMT1 :condition>normal text<!ELSE_STMT1><!-- special-case text --><!/IF_STMT1>

If invoked as a stand-alone HTML page or if ":condition" is non-null and non-zero, "normal text" will print, otherwise, "special-case text" will print.  The HTML comments will be removed automatically for the text, if the corresponding condition evaluates to true.

"LOOP" Statement:

Another, more powerful construct is the "LOOP".  A LOOP repeatedly generates its HTML body for each value in a Perl list.  The LOOP construct has the following general format:

<!LOOP[loopname] [first..last[|increment]] [index-list] [list-parm [, listparm...]]>
    -body-
<!/LOOP[loopname]>

For example:

<HTML>
<H3>Dallas Cowboy's Star Roster</H3>
<P><TABLE>
<TR><TH>No.</TH><TH>Name</TH><TH>Jersey</TH></TR>
<!LOOP names, numbers>
    <TR><TD>:#+1</TD><TD><!:names></TD><TD><!:numbers></TD></TR>
<!/LOOP>
</TABLE>
</HTML>

If called with:

  loadhtml('/usr/htdocs/myhtml.htm',
    -names => ['Troy Ackman', 'Emmit Smith', 'Michael Irvin'],
    -numbers => [8,22,88]);

would produce:

<HTML>
<H3>Dallas Cowboy's Star Roster</H3>
<P><TABLE>
<TR><TH>Name</TH><TH>Jersey</TH></TR>
    <TR><TD>1</TD><TD>Troy Ackman</TD><TD>8</TD></TR>
    <TR><TD>2</TD><TD>Emmit Smith</TD><TD>22</TD></TR>
    <TR><TD>3</TD><TD>Michael Irvin</TD><TD>88</TD></TR>
</TABLE>
</HTML>

The values: names, and numbers in the "LOOP" statement refer to those parameters which refer to perl list references instead of scaler values.  The ":#" represents a special value -- the iteration number of the loop being processed (starting with zero).  We use ":#+1" (":#_LOOPNAME+1) to cause this value to start with one instead of zero). If loops are nested (and thus named, the name can be appended to the ":# variable, ie:

<!LOOP_LOOPNAME argument1>
    <BR>Now in iteration:  :#_LOOPNAME+1; next arg1 value=<!argument1>
<!/LOOP_LOOPNAME>

By default, the loop executes with ":#" starting with zero, incrementing by one and continuing through the last value of the 1st list parameter specified.  This can be overridden by specifying an increment expression with starting and ending values and optionally, an increment value; -AND/OR- an index-list.  For example (start with 10, stop at 100, and increment by 5):

<!LOOP 10..100|5 argument1>
    <BR>The list value for argument1[:#] is: <!:argument1>.
<!/LOOP>

This would produce 19 lines of output, the value printed for ":#" would be 10, then 15, 20, ...100.  The tenth, 15th, 20th, 25th, ... and 100th elements of the list passed as argument 2 to LoadHtml() would be displayed.  If that list contained less than 100 elements, empty strings would print for the missing elements.  This is also useful to reverse the order of a list, for example:

<!LOOP 20..1|-1>
    ...
<!/LOOP>
 

<!LOOP 1..:argument1 argument2, argument3>

This specifies that the loop should execute argument1 times. Each iteration will correspond to a value of argument2 and argument3 starting with element [1]. argument1 should contain a scaler integer and argument2 and argument3 should be references to arrays with at least "argument1" + 1 elements.

<!LOOP  5..  argument2, argument3>

This specifies that the loop should execute once for each element of argument2 starting with the 6th one ([5]) and continuing through the last one.

<!LOOP 1,5,2,7 argument1, argument2>

This specifies that the loop should execute 4 times using the 2nd, 6th, 3rd, and 8th values of argument1 and argument2.

<!LOOP index-list argument1, argument2>

This specifies that the loop should execute once for each element in the array-reference passed to "index-list". Each value of index-list will become the subscript to use for argument1 and argument2 in it's respective iteration.

NOTE: If argument1 is a hash-reference instead of an array-reference, then the keys used for argument1 will be based on the relative position within an imaginary array built on the fly as "sort(keys(%{$argument1))". For example if the keys for argument1 (sorted) were "AA", "BB", "CC", and "DD"; and array referenced by index-list contained the values (in this order): (1, 3, 2, 0), then the loop would iterate through the keys in the order of: "BB", then "DD", then "CC", and finally "AA". This allows hashes to be iterated through in an order other than sorted by key!

LoadHTML can also emulate Template::Toolkit's ability to reference subcomponents of a reference by name. For example:

    my @v ;
    push (@v, {id => 100, name => 'Jack'});
    push (@v, {id => 101, name => 'Jill'});
    push (@v, {id => 102, name => 'Jerry'});
    &loadhtml('template.html', -hashref => \@v);

template.html contains:

<table>
    <tr><td>Id</td><td>Name</td></tr>
    <!LOOP hashref, id, name>
        <tr><td><!:id></td><td><!:name></td></tr>
    <!/LOOP>
</table>

This would produce:

<table>
    <tr><td>Id</td><td>Name</td></tr>
    <tr><td>100</td><td>Jack</td></tr>
    <tr><td>101</td><td>Jill</td></tr>
    <tr><td>102</td><td>Jerry</td></tr>
</table>

NOTE: "id" and "name" are parameters in the LOOP statement that are NOT DEFINED - (no argument is passed to them in the call to "loadhtml()"! This results in the subcomponents of the hashrefs passed to "hashref" (from @v) being used! This is similar to the way Template::Toolkit works and permits easier conversion of templates and scripts from that package. Also NOTE: a HASH could have been used in leau of "@v"!

There are four special variables that have meaning within a loop construct:

    * :# Current increment value. If no increment expression or index list is specified, the loop is driven by the 1st array or hash argument. In that case, the increment value is the zero-based iteration of the loop. This value is always numeric and represents the index subscript of the vectors for the current iteration.
    * :* Always the current zero-based iteration of the loop (numeric). Normally, this is the same as :#, but if an increment expression or index list is specified before the parameters, then :# is set to each element of the increment expression/index list, whereas :* is ALWAYS 0,1,...
    * :% Current key value of the 1st (driving) hash (if the 1st argument is a hash-reference). Otherwise, this variable is empty (ie. if the loop is driven by an array).
    * :^ Always contains the number of iterations (one-based) that the loop will perform. 

Naming and nesting IF and LOOP constructs.

IF and LOOP constructs can be nested with each other.  If nested within the same construct, however, they must be named (in order for the parser to match up the proper closing tags).  This allows for qualifying the special variables (:#, :*, etc.) to the desired loop. To name an "IF" or "LOOP" constuct, simply append an alphanumeric string to the keyword, for example:

<!IF2>...<!ELSE2>...<!/IF2>

    -or-

<!LOOP_OUTER>...<!/LOOP_OUTER>

The "IF" is named "2", and the "LOOP" is named "_OUTER".

Multi-loop Matrix example:

Consider the following code:


    my $data = $dbh->selectall_arrayref('select name, address, phone from some_database.table');
    ...
    &loadhtml('rate_specials.html',
        -colHeaders => [qw(Name Address Phone)],
        -matrix => $data,
        -names => '$matrix->[*][0]', #THIS IS AN EXAMPLE OF A COLUMN "Slice"!
    );

Now consider the following template code:


    <table>
         <TR class="heading"><TH>Link</TH><!LOOP_HEADERS colHeaders><TH><!:colHeaders:>Field Header<!:/colHeaders></TH><!/LOOP_HEADERS></TR>
         <!LOOP_ROWS matrix, names>
             <!IF_ODDEVEN1 :#_ROWS % 2><TR class="oddrow"><!ELSE_ODDEVEN1><TR class="evenrow"><!/IF_ODDEVEN1>
                 <TD align="center"><A HREF="cgi-bin/someotherpgm.cgi?name=:{names}"><!:names></A></td>
                 <!LOOP_COLS matrix><TD><!:matrix:>Field Value<!:/matrix></TD><!/LOOP_COLS>
             </TR>
         <!/LOOP_ROWS>
    </table>

This illustrates how simple it is to combine LoadHTML with DBI (the single call to DBI::selectall_arrayref fetches all the data from a database query into a two-dimentional row-major array referenced by $data). This HTML template could handle a variety of queries, since the number of columns (headers) is also driven by a loop. The "ODDEVEN1" IF-statement is optional and simply allows the table rows to have alternating colors for readability. Note the nested loops "_ROWS" (outer) and "_COLS" (inner), both are driven by the two-dimentional array- referencing parameter "matrix". This will produce a table showing a row for each record read by the query and each row will contain all three column values.

An extra, but unnecessary level of complexity was added to this example to illustrate another feature - the column "slice". Note that the 1st column header is "Link", and the 1st column of each row is a URL link to "someotherpgm.cgi". The reason for this example is to show access to the entire column of data represented by the field "names". By specifying an additional parameter called "-names" containing the literal value string "$matrix->[*][0]", one can unroll a specific column within a multi-dimentional array in the outter (row) loop. This means that the "names" parameter refers to the 1st ([0]'th) slice of the two-dimentional array referenced by the "matrix" parameter. For each row in the loop, the asterisk is replaced by the increment number, so that in the 1st row "names" refers to $data->[0][0] (The 1st name returned by the query). In the 2nd row, "names" refers to $data->[1][0], etc. This permits the row-major data returned by the query to be handled in a column-major way (allowing the programmer to get at the individual elements of a specified column), which would normally require an inner loop to access.

Note also, that is is not limited to 2 dimensions or to array-references. The number of dimensions is not physically limited, but can be any number and combination of array and or hash-references. The trick is that there normally must be a nested loop refering to the same parameter for each dimension to be unrolled (unless a column slice is used). When hash-references are used, they are sorted by key unless an index-list is specified.

The above example could have also been acomplished without the slice by using an inner loop (called "NAMES" below) that only referenced the desired (zero-th) element (only iterates once (0..0) unrolling the zeroth column (inner dimension) element of "matrix" for each iteration of the outer ("ROWS") LOOP as follows: (You could replace the "0..0" with the number of the column you wish to use for the slice).


    <table>
         <TR class="heading"><TH>Link</TH><!LOOP_HEADERS colHeaders><TH><!:colHeaders:>Field Header<!:/colHeaders></TH><!/LOOP_HEADERS></TR>
         <!LOOP_ROWS matrix>
             <!IF_ODDEVEN1 :#_ROWS % 2><TR class="oddrow"><!ELSE_ODDEVEN1><TR class="evenrow"><!/IF_ODDEVEN1>
                 <!LOOP_NAMES 0..0 matrix><TD align="center"><A HREF="cgi-bin/someotherpgm.cgi?name=:{matrix}"><!:matrix></A></td><!/LOOP_NAMES>/
                 <!LOOP_COLS matrix><TD><!:matrix:>Field Value<!:/matrix></TD><!/LOOP_COLS>
             </TR>
         <!/LOOP_ROWS>
    </table>

"SELECTLIST" Statement:

Another compound construct is the "SELECTLIST".  It generates an HTML "SELECT" statement using the elements of a Perl list or hash, generating an "OPTION" line for each element in the list or hash.  The general format is:

<!SELECTLIST NAME=select_name [VALUE[S]=value_list] [DEFAULT[SEL]=default_value] [(BYKEY)|BYVALUE] [REVERSE[D]] :list_parameter>
    [...HTML to display if page invoked standalone...]
<!/SELECTLIST>

The NAME and any options other than "VALUE", "DEFAULT", "DEFAULTSEL", "BYKEY", "BYVALUE", and "REVERSE" are added (passed) to the generated SELECT statement.  The "list_parameter" (required), by default, becomes the values for the generated "OPTION" lines.  If "list_parameter" is a Perl hash, then the keys of the hash become the arguments for the "VALUE=" part of each OPTION line, and the values become the displayed items in the listbox. The values are then character-sorted by key (BYKEY) unless "BYVALUE" is specified. "REVERSE" reversed the order.   If "list_parameter" is a list and a second list is supplied via the "VALUE" option, then the second list becomes the "VALUE=" part of each OPTION line and the "list_parameter" list items are displayed. They are displayed in the order they appear in the list(s), unless "REVERSE" is specified.   If no "VALUE" option is given and "list_parameter" is a list, then no "VALUE=" option is generated and the values become both the actual values and the displayed values for the listbox.  The DEFAULT option, if specified, is a value which is to be the initially highlighted value in the select-list.  If the "MULTIPLE" select option is specified, then the "DEFAULT=" value may be either a scalar or a list-reference. Each value in the "DEFAULT" list (if a list reference) is matched against the "VALUE" list and those that match are "SELECTED" by default. If "DEFAULTSEL=" is specified, the default list values are compared with the SELECT values instead the "VALUES" values.  Note that the resulting selection-list items are sorted in character-sequence order when the list parameter is a hash  To get a true numeric sort, one must left-pad the hash keys with spaces. 

Example:

<!SELECTLIST NAME="id" DEFAULT=":thisid" :employees><INPUT NAME=id TYPE=TEXT><!/SELECTLIST>

...

    $mydefault = 123;
    %employeehash = (110 => 'John Smith', 145 => 'Richard Adams', 123 => 'Mike Cox', 132 => 'Eddy Jones');
    &loadhtml('/usr/htdocs/myhtml.htm', -thisid => $mydefault, -employees => \%employeehash);

This would replace the "id" TEXT box field with the following HTML:

<SELECT NAME="id">
    <OPTION VALUE="110">John Smith
    <OPTION SELECTED VALUE="123">Mike Cox
    <OPTION VALUE="132">Eddy Jones
    <OPTION VALUE="145">Richard Adams
</SELECT>

Checkboxes and radio-buttons:

Checkboxes and radio-buttons also require special handling.  A default value is specified in the HTML via a parameter.  The parameter will be replaced by the word "CHECKED" if it's value matches the value specified for the checkbox or radio-button.  for example:

    <BR><INPUT TYPE=CHECKBOX NAME="checkit" :ischecked>Check here if True!<BR>

If the value passed to ":ischecked" is "true" in Perl (not zero, empty string, or whitespace), the HTML will be generated with ":ischecked" replaced with the word "CHECKED", otherwise, the ":ischecked" it will be removed. NOTE: If the word "CHECKED" is already in the HTML, it will be removed if the value for ":ischecked" is false, but will remain if no argument is defined for ":ischecked".

    <INPUT TYPE=RADIO NAME="topings" VALUE="meat" :ischecked>Give me Meat and Cheese
    <INPUT TYPE=RADIO NAME="topings" VALUE="veggies" :ischecked>Give me Veggies, please

If the argument passed to ":ischecked" is equal 'meat' or 'veggies', then the corresponding radio-button will be marked "CHECKED", otherwise, neither will be checked.

"INCLUDE" statement:

Additional HTML files can be loaded and processed within an HTML file via the "INCLUDE" statement.  All files loaded via the "INCLUDE" statement are also parsed and modified the same way the initial HTML file is.  The include file can be specified as either a server file-name or a url.  Examples:

    <!INCLUDE /user/htdocs>
    <!INCLUDE http://domain/path/myhtml.htm>
 

You can also include a portion of another html template file without including the endire file by using tags

For example:

    <!INCLUDE http://domain/path/myhtml.htm:header>

This assumes the template file myhtml.htm contains the following block tag:

<!-- BEGIN header -->
... stuff to be included ...
<!-- END header -->

You can also force different default values for parameters by including them in the include, ie.:

    <!INCLUDE http://domain/path/myhtml.htm:header(roses=red,violets=blue)> -or-
    <!INCLUDE /user/htdocs(arg1=:{another_arg})>

Providing default values for form items.

LoadHtml provides special ways to assign default values to HTML "INPUT" statements.  Consider the following for putting a default value into a TEXT field:

    <INPUT NAME=name TYPE=TEXT VALUE=":{default}">

This will work, but LoadHtml provides a better way.  If done this way and the form is invoked stand-alone, the input box will show a literal ":{default}", which is probably not desired for demos.  The preferred way is:

    <INPUT NAME=name TYPE=TEXT VALUE="standalone-default" :default=somestring>

This provides a value "standalone-default", if the page is invoked as stand-alone HTML and a value of "somestring", if no argument or "undef" is passed for the corresponding argument.  If the "=somestring" string is omitted, the box will show as empty, if no argument is passed for ":{default}".  NOTE:  If an empty string is passed as an argument, the box will be empty regardless of any default values specified!  This option also applies to "HIDDEN" input fields.

    <SELECT NAME=myselect :default>...</SELECT>

This permits the default (initially selected) value of the SELECT statement to be specified by the value referenced by argument "default".

    <TEXTAREA... :default=somestring>stand-alone default</TEXTAREA>

This works similar to the "<INPUT TYPE=TEXT...>" input field described previously.
 IV). Other Special Tags:

Embedding Perl variables:

If "AllowEvals(1)" is called before calling "loadhtml", then any embedded Perl variables of the format:  ":$scaler  or :$array[index] or :$hash{index} or :$package::variable  will be replaced by it's value current at the time LoadHtml is called.

Embedding Perl code (the "EVAL" Statement):

If "AllowEvals(1)" is called before calling "loadhtml", then any Perl code between the tag: "<!EVAL.../EVAL>" will be evaluated and any returned results will replace the EVAL tag.  Consider the following example:

     <!EVAL
    my (@t) = localtime(time);
    return ($t[3] . '-' . (qw(JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC))[$t[4]] . '-' .  $t[5] . '.');
    /EVAL>

This tiny Perl program calls Perl's "localtime" function, and returns the current date with the month formated into it's proper three-character abbreviation.  The more complicated example below generates a dynamic url link:

    <!EVAL
    my ($homepage) = ":0";
    $homepage =~ s/userpage/pp:4/;
    if (-e "$homepage")
    {
    $homepage = 'http://myhost.domain.com/cgi-bin/loadtext.pl?link='
    . $homepage . '&args=:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16,:17,:18,:19,:20,:21,:22';
    return ('<CENTER&gt<BR&gt<A HREF=' . $homepage . '&gtGo to my Personal Homepage</A&gt</CENTER&gt<BR&gt<HR SIZE=5&gt');
    }
    /EVAL>

Note that parameter substitutions take place within this code.  Also note the use of "&gt" in lieu of the ">" symbol.  This is required to prevent the HTML processor from closing the "<!EVAL" tag before the end of the "EVAL" code!  The special parameter ":0" contains the name of the html template file.

Embedding Perl code (the "PERL" Statement):

You can also embed the results of a separate Perl script file using the "PERL" tag.

The format is: <!PERL file="/path/to/perlscript.pl"><!-- "Default String" --><!/PERL>

The script must be "eval"-able (last expression's results are returned) by Perl. Also, "-perls => 1" must be passed to SetRegices() and AllowEvals(1) must be called - since THIS IS A SECURITY RISK - if the Perl script is malicious! NOTE: how the "Default String" is enclosed in quotes, since this is fed to Perl's eval() fn also (if the script can't be eval'ed), so must be a valid Perl result also!

Of course, one can be even more dangerous, ie: "<!PERL file=":{variable}"><!/PERL>"!

Embedding hash-definitions within HTML pages:

Hash tables can now be defined within an HTML page for creating lookup tables, etc. To create a hash table, use the "<!HASH> tag. For example:

<!HASH attbdescs>
    'any' => 'Any attribute',
    'di' => 'Direct/Indirect',
    'dgr' => 'Education (Degree)',
    'ins' => 'Education (Institution)',
    'maj' => 'Education (Major)',
    'exl' => 'Experience (LM)',
    'ex' => 'Experience (Total)',
    'flv' => 'Foreign Language',
    'flw' => 'Foreign Language',
    'fpt' => 'Full/Part Time',
    'ou' => 'Organization',
    'pos' => 'Title',
    'sg' => 'Salary Grade',
    'sc' => 'Security Clearance',
    'sk' => 'Skills and Knowledge',
    'tr' => 'Training'
<!/HASH attbdescs>

This defines a lookup table for several codes and gives each a description. The hash can be any valid Perl hash-definition. The hash will be referred to within the HTML by the name "attbdescs". To cause a hash's value to be displayed, use its name in the special tag in the format:

    <!%hashname{key}default>

For example to display the following tag:

    <!%attbdescs{pos}>

would be replaced with "Title". The real use for this is specifying the key dynamically, ie:

    <!%attbdescs{:1}-NO SUCH VALUE!->

The result depends on the value of ":1". If the value of ":1" does not match any of the key values, then "-NO SUCH VALUE!-> is displayed.

If the template page is also being used stand-alone, the entire hash definition (between "<!HASH...>" and "<!/HASH>) can be enclosed as a comment ("<!-- ... -->").

Other Tags:

    <!POC> (or <!POC:>replace this standalone default text<!/POC>)

Generates the point-of-contact's name (whatever value passed to the set_poc() function. The default value is the string "your website administrator". NOTE: "-pocs => 1" must be passed to &SetRegices() first!

    <!TODAY> (or <!TODAY:>replace this standalone default text<!/TODAY>)

    <!TODAY="yyyy-mm-dd hh:mm"> (or <!TODAY="mm/yyyy":>replace this standalone default text<!/TODAY>)

Generates today's date (default format is "mm/dd/yy" if DBD::Sprite is installed and the "to_char" function from that library is available, otherwise, the format is: scalar(localtime($mtime).

    <!FILEDATE> (or <!FILEDATE:>replace this standalone default text<!/FILEDATE>)

    <!FILEDATE="yyyy-mm-dd"> (or <!FILEDATE="mm/yyyy":>replace this standalone default text<!/FILEDATE>)

Generates the last-modified date/time of the template file (default format is "mm/dd/yy" if DBD::Sprite is installed and the "to_char" function from that library is available, otherwise, the format is: scalar(localtime($mtime).

 V). User-callable Functions (Details):

    &loadhtml($htmlfile, @args)

Main function to read/process a specified template file / url ($htmlfile) and prints out the resulting html page to STDOUT. @args represents a list of values. Each argument value replaces any occurrance of the corresponding parameter number (ie. ":1", ":2", etc.). If the first, third, fifth, etc. are valid Perl "words" starting with a hyphen, then the next argument (ie. the 2nd, fourth, sixth, etc.) represents a value that will replace every occurrance of the parameter with the same name, ie. "-parm => 'value', converts :{parm} or <!:parm> or <!:parm:>...<!:/parm> to 'value' everywhere it occurrs. For each parameter that is used in a LOOP or SELECTLIST construct, the value should be an array reference or a hash referehce, rather than a scalar value. If successful, loadhtml returns 1 (true) if fails, ie. could not open the template file, <undef>. The special parameter ":0" is replaced with the name of the template file (1st argument).

You can also convert programs that use Template::Toolkit by changing:

    $template_object->process($template_file, $template_hashref);

to:

    &loadhtml($template_file, %{$template_hashref});

    &loadhtml($htmlfile, @args);

is equivalent to:

    print &buildhtml($htmlfile, @args);

    my $html = &buildhtml($htmlfile, @args);

Same as loadhtml, except returns the generated webpage as a string instead of writing it to STDOUT;

    print &dohtml($htmlstring, @args);

    my $html = &dohtml($htmlstring, @args);

Same as buildhtml, except processes a input string instead of a template file or url.

    &AllowEvals(1|0);

Toggles whether or not embedded Perl variables and expressions are performed, namely the <!PERL> and <EVAL> constructs and Perl variables in the format: "<!:$variable>".
Default is 0.

    &set_poc($str);

Sets the string to replace the special "<POCS>" construct. Default is to ignore this tag. If called without a string or an empty string, the string is set to "your website administrator".

    &SetRegices(%optionshash);

Sets special control options. The currently defined options (with their default values) are: -hashes => 0, -CGIScript => 0, -includes => 1, -embeds => 0, -loops => 1, -numbers => 1, -pocs => 0, -perls => 0)

These options allow speeding up processing when turned off (not needed).

    -hashes: Allows the <!HASH> tag sto be processed if on, otherwise ignored.

    -CGIScript: Causes s special hidden form variable called "CGIScript" to be added at
the bottom of the first form with the value set to "$ENV{SCRIPT_NAME}" if on, otherwise not added.

    -includes: Allows the <!INCLUDE> tags to be processed if on, otherwise ignored.

    -embeds: Allows the <!EMBED> tags to be processed if on, otherwise ignored.

    -loops: Allows the <!LOOP> tags to be processed if on, otherwise ignored.

    -numbers: Allows the classic numeric parameter (":1", ":2", etc.) tags to be processed if on, otherwise ignored.

    -pocs: Allows the <!POC> tags to be processed if on, otherwise ignored.

    -perls: Allows the <!PERL> tags to be processed if on, otherwise ignored.

&SetListSeperator($separator_string);

Sets the separator string to be used if an array-reference is passed to a parameter that appears outside of a loop (where a scalar value is expected) Such values are automatically converted to a string of values ala Perl's "join()" function. The default string is ", ".

    &SetHtmlHome($htmlhome, $roothtmlhome, $hrefhtmlhome, $hrefcase);

This allows certain embedded links within a document to be "converted" for proper handling. Relative links refer to a different path when the document is loaded via CGI/LoadHTML than then they are loaded directly as urls by a browser, for example, the document root usually becomes the directory the CGI script is in. Anyway, this is an attempt to allow valid HTML pages to also be loaded as templates within a CGI script and maintain their links properly.

$htmlhome - specifies the URL path to append to relative links in SRC=, HREF=, CL=, HT=, GROUND=, and window.open() arguments.

$roothtmlhome specifies the filesystem path to append to relative file names in <INCLUDE> tags.

$hrefhtmlhome - similar to $htmlhome, but only applies to HREF= links, if it is necessary to redirect them to a different path, ie. a cgi-script for pre-processing. If both $hrefhtmlhome and $htmlhome are specified and non-empty, the former will override for HREF= links and the other will applie to the other link types, ie. SRC=, etc.

$hrefcase - used to limit the substitutions of $htmlhome and $hrefhtmlhome to specific links. It can be set to 'l' (Lower-case links only), left undefined for all links, or set to anything else for Upper-case links only. For purposes of case, a "Lower-case" link would be "href=", an "Upper-case" link would be "HREF=".

    &loadhtml_package($package_name);

Change the default package LoadHTML uses for embedded Perl variables. Default is main. Best way to set this is to call "loadhtml_package(__PACKAGE__);".
 VI). Minimum System Requirements:

    * 1) Any system supporting Perl and CGI.
    * 2) Perl, v. 5.003 or better.
    * 3) Perl's "LWP" module (not an absolute requirement, but VERY useful) and required prerequesites:  MIME-Base64 (MIME), HTML-Parser (HTML), libnet (Net), MD5, and Data-Dumper (Data).  All of these are available for download from CPAN. 

=head1 METHODS

=over 4

=item B<loadhtml_package>([package_name])

Change the default package LoadHTML uses for embedded Perl variables. Default is I<main>. Best way to set this is to call "loadhtml_package(__PACKAGE__);".

=item B<loadhtml>(template_file, @arguments)

Main function to read/process a specified template file / url ($htmlfile) and prints out the resulting html page to STDOUT. @args represents a list of values. Each argument value replaces any occurrance of the corresponding parameter number (ie. ":1", ":2", etc.). If the first, third, fifth, etc. are valid Perl "words" starting with a hyphen, then the next argument (ie. the 2nd, fourth, sixth, etc.) represents a value that will replace every occurrance of the parameter with the same name, ie. "-parm => 'value', converts :{parm} or <!:parm> or <!:parm:>...<!:/parm> to 'value' everywhere it occurrs. For each parameter that is used in a LOOP or SELECTLIST construct, the value should be an array reference or a hash referehce, rather than a scalar value. If successful, loadhtml returns 1 (true) if fails, ie. could not open the template file, <undef>. The special parameter ":0" is replaced with the name of the template file (1st argument).

=item $html = B<buildhtml>([package_name])

Same as loadhtml, except returns the generated webpage as a string instead of writing it to STDOUT;

=item $html = B<dohtml>(html_datastring, @arguments)

Same as buildhtml, except reads it's template data from a string variable instead of a file.

=item B<AllowEvals>(1|0)

Toggles whether or not embedded Perl variables and expressions are performed, namely the <!PERL> and <EVAL> constructs and Perl variables in the format: "<!:$variable>".
Default is 0.

=item B<set_poc>(string)

Sets the string to replace the special "<POCS>" construct. Default is to ignore this tag. If called without a string or an empty string, the string is set to "your website administrator".

=item B<SetListSeperator>(separator_string)

Sets the separator string to be used if an array-reference is passed to a parameter that appears outside of a loop (where a scalar value is expected) Such values are automatically converted to a string of values ala Perl's "join()" function.  
Default:  I<", ">.

=item B<SetRegices>(%optionshash)

Sets special control options. The currently defined options (with their default values) are: -hashes => 0, -CGIScript => 0, -includes => 1, -embeds => 0, -loops => 1, -numbers => 1, -pocs => 0, -perls => 0)
These options allow speeding up processing when turned off (not needed).

=over 4

B<-hashes>:  Allows the <!HASH> tag sto be processed if on, otherwise ignored.

B<-CGIScript>:  Causes s special hidden form variable called "CGIScript" to be added at
the bottom of the first form with the value set to "$ENV{SCRIPT_NAME}" if on, otherwise not added.

B<-includes>:  Allows the <!INCLUDE> tags to be processed if on, otherwise ignored.

B<-embeds>:  Allows the <!EMBED> tags to be processed if on, otherwise ignored.

B<-loops>:  Allows the <!LOOP> tags to be processed if on, otherwise ignored.

B<-numbers>:  Allows the classic numeric parameter (":1", ":2", etc.) tags to be processed if on, otherwise ignored.

B<-pocs>:  Allows the <!POC> tags to be processed if on, otherwise ignored.

B<-perls>:  Allows the <!PERL> tags to be processed if on, otherwise ignored.

=back

=item B<SetHtmlHome>(htmlhome, roothtmlhome, hrefhtmlhome, hrefcase)

This allows certain embedded links within a document to be "converted" for proper handling. Relative links refer to a different path when the document is loaded via CGI/LoadHTML than then they are loaded directly as urls by a browser, for example, the document root usually becomes the directory the CGI script is in. Anyway, this is an attempt to allow valid HTML pages to also be loaded as templates within a CGI script and maintain their links properly.

=over 4

B<htmlhome> - specifies the URL path to append to relative links in SRC=, HREF=, CL=, HT=, GROUND=, and window.open() arguments.

B<roothtmlhome> - specifies the filesystem path to append to relative file names in <INCLUDE> tags.

B<hrefhtmlhome> - similar to $htmlhome, but only applies to HREF= links, if it is necessary to redirect them to a different path, ie. a cgi-script for pre-processing. If both B<hrefhtmlhome> and B<htmlhome> are specified and non-empty, the former will override for HREF= links and the other will applie to the other link types, ie. SRC=, etc.

B<hrefcase> - used to limit the substitutions of B<htmlhome> and B<hrefhtmlhome> to specific links. It can be set to 'l' (Lower-case links only), left undefined for all links, or set to anything else for Upper-case links only. For purposes of case, a "Lower-case" link would be "href=", an "Upper-case" link would be "HREF=".

=back

=back

=head1 AUTHOR

Jim Turner, C<< <https://metacpan.org/author/TURNERJW> >>.

=head1 COPYRIGHT

Copyright (c) 1996-2018 Jim Turner C<< <mailto:turnerjw784@yahoo.com> >>.
All rights reserved.  

This program is free software; you can redistribute 
it and/or modify it under the same terms as Perl itself.

=cut

package LoadHtml;

#use lib '/home1/people/turnerj';

use strict;
#no strict 'refs';
#use vars (qw(@ISA @EXPORT $useLWP $err $rtnTime $VERSION));
use vars (qw(@ISA @EXPORT $err $VERSION));

our $VERSION = '7.10';

require Exporter;
#use LWP::Simple;
my $useLWP = 0;
my $haveTime2fmtstr = 0;
eval 'use  LWP::Simple; $useLWP = 1;';
#use Socket;

@ISA = qw(Exporter);
@EXPORT = qw(loadhtml_package loadhtml buildhtml dohtml modhtml AllowEvals cnvt set_poc 
		SetListSeperator SetRegices SetHtmlHome);


local ($_);

local $| = 1;
my $calling_package = 'main';    #ADDED 20000920 TO ALLOW EVALS IN ASP!

my $poc = 'your website administrator';
my $listsep = ', ';
my $evalsok = 0;
my %cfgOps = (
	hashes => 0,
	CGIScript => 0,
	includes => 1,
	loops => 1,
	numbers => 1,
	pocs => 0,
	perls => 0,
	embeds => 0,
);   #ADDED 20010720.
my ($htmlhome, $roothtmlhome, $hrefhtmlhome, $hrefcase);

sub SetListSeperator
{
	$listsep = shift;
}

sub cnvt
{
	my $val = shift;
	return ($val eq '26') ? ('%' . $val) : (pack("c",hex($val)));
}

sub set_poc
{
	$poc = shift || 'your website administrator';
	$cfgOps{pocs} = 1;
}

sub SetRegices
{
	my (%setregices) = @_;
	my ($i, $j);

	foreach $j (qw(hashes CGIScript includes embeds loops numbers pocs perls))
	{
		if ($setregices{"-$j"})
		{
			$cfgOps{$j} = 1;
		}
		elsif (defined($setregices{"-$j"}))
		{
			$cfgOps{$j} = 0;
		}
	}
}

sub loadhtml
{
	my %parms = ();
	my $html = '';

	local ($/) = '\x1A';

	if (&fetchparms(\$html, \%parms, 1, @_))
	{
		print &modhtml(\$html, \%parms);
		return 1;
	}
	else
	{
		print $html;
		return undef;
	}
}

sub buildhtml
{
	my %parms = ();
	my $html = '';

	local ($/) = '\x1A';
	return &fetchparms(\$html, \%parms, 1, @_) ? &modhtml(\$html, \%parms) : $html;
}

sub dohtml
{
	my %parms = ();
	my $html = '';

	return &fetchparms(\$html, \%parms, 0, @_) ? &modhtml(\$html, \%parms) : $html;
}

sub fetchparms
{
	my $html = shift;
	my $parms = shift;
	my $fromFile = shift;
	my ($parm0) = shift;
	
	my ($v, $i, $t);
	
#	%loopparms = ();

	%{$parms} = ();
	$$html = '';

	$i = 1;
	$parms->{'0'} = $parm0;
	while (@_)
	{
		$v = shift;
		$parms->{$i++} = (ref($v)) ? $v : "$v";
		last  unless (@_);
		if ($v =~ s/^\-([a-zA-Z]+)/$1/)
		{
			$t = shift;
			if (defined $t)   #ADDED 20000523 PREVENT -W WARNING!
			{
				$parms->{$i} = (ref($t)) ? $t : "$t";
			}
			else
			{
				$parms->{$i} = '';
			}
			$parms->{$v} = $parms->{$i++};
		}
	}

	unless ($fromFile)
	{
		$$html = $parm0;
		return ($$html) ? 1 : 0;
	}

	if (open(HTMLIN,$parm0))
	{
		$$html = (<HTMLIN>);
		close HTMLIN;
	}
	else
	{
		$$html = LWP::Simple::get($parm0)  if ($useLWP);
		unless(defined($$html) && $$html =~ /\S/o)
		{
			$$html = &html_error("Could not load html page: \"$parm0\"!");
			return undef;
		}
	}
	return 1;
}

sub AllowEvals
{
	$evalsok = shift;
}

sub makaswap
{
	my $parms = shift;
	my $one = shift;

	return ("\:$one")  unless (defined($one) && defined($parms->{$one}));
	if (ref($parms->{$one}) =~ /ARRAY/o)   #JWT, TEST LISTS!
	{
		return defined($listsep) ? (join($listsep,@{$parms->{$one}})) : ($#{$parms->{$one}}+1);
	}
	elsif ($parms->{$one} =~ /(ARRAY|HASH)\(.*\)/o)   #FIX BUG.
	{
		return ('');   #JWT, TEST LISTS!
	}
	else
	{
		return ($parms->{$one});
	}
	#ACTUALLY, I DON'T THINK THIS IS A BUG, BUT RATHER WAS A PROBLEM
	#WHEN $#PARMS > $#LOOPPARMS, PARMS WITH VALUE='' IN A LOOP WOULD
	#NOT GET SUBSTITUTED DUE TO IF-CONDITION 1 ABOVE, BUT WOULD LATER
	#BE SUBSTITUTED AS SCALERS BY THE GENERAL PARAMETER SUBSTITUTION
	#REGEX AND THUS GET SET TO "ARRAY(...)".  CONDITION-2 ABOVE FIXES THIS.
};

sub makamath   #ADDED 20031028 TO SUPPORT IN-PARM EXPRESSIONS.
{
	my ($one) = shift;

	$_ = eval $one;
	return $_;
};

sub makaloop
{
	my ($parms, $parmnos, $loopcontent, $looplabel) = @_;
#print "---makaloop: args=".join('|',@_)."=\n";
	my $rtn = '';
	my ($lc,$i0,$i,$j,%loopparms);
	my (@forlist);   #MOVED UP 20030515. - ORDERED LIST OF ALL HASH KEYS (IFF DRIVING PARAMETER IS A HASHREF).
	$parmnos =~ s/\:(\w+)([\+\-\*]\d+)/eval(&makaswap($parms,$1).$2)/egs;   #ALLOW OFFSETS, ie. ":#+1"		$parmnos =~ s/\:(\w+)/&makaswap($parms,$1)/egs;    #ALLOW ie. <!LOOP 1..:1>
	$parmnos =~ s/\:(\w+)/&makaswap($parms,$1)/egs;   #ALLOW OFFSETS, ie. ":#+1"		$parmnos =~ s/\:(\w+)/&makaswap($parms,$1)/egs;    #ALLOW ie. <!LOOP 1..:1>
	$parmnos =~ s/[\:\(\)]//go;
	$parmnos =~ s/\s+\,/\,/go;
	$parmnos =~ s/\,\s+/\,/go;
	my @vectorlist = ();     #THE ORDERED LIST OF INDICES TO ITERATE OVER (ALWAYS NUMBERS):
#	if ($parmnos =~ s/([a-zA-Z]+)\s+([a-zA-Z])/$2/)    #CHANGED TO NEXT LN (20070831) TO ALLOW UNDERSCORES IN ITERATOR PARAMETER NAMES.
	if ($parmnos =~ s/([a-zA-Z][a-zA-Z_]*)\s+([a-zA-Z])/$2/)
	{
#print "<BR>-LOADHTML: 1=$1= param=$$parms{$1}=\n";  #JWT:ADDED EVAL 20120309 TO PREVENT FATAL ERROR IF REFERENCE ARRAY MISSING!:
		eval { @vectorlist = @{$parms->{$1}} };     #WE HAVE AN INDEX LIST PARAMETER (<!LOOP index arg1,arg2...>)
#print "<BR>-???- 1st arg=$1=		VECTOR=".join('|',@vectorlist)."=\n";
	}
	elsif ($parmnos =~ s/(\d+\,\d+)((?:\,\d+)*)\s+([a-zA-Z])/$3/)    #WE HAVE A LITERAL INDEX LIST (<!LOOP 2,3,5,4 arg1,arg2...>)
	{
		eval "\@vectorlist = ($1 $2);";
	}
	$parmnos =~ s/\s+/\,/go;

	my (@listparms) = split(/\,/o, $parmnos);
#1ST IF-CHOICE ADDED 20070807 TO SUPPORT AN INDEX ARRAY OF HASH KEYS W/DRIVING PARAMETER OF TYPE HASHREF:
	if (ref($parms->{$listparms[0]}) eq 'HASH' && defined($vectorlist[0]) && defined(${$parms->{$listparms[0]}}{$vectorlist[0]}))
	{
#print "<BR>-???- 1st is HASH:  VECTOR=".join('|',@vectorlist)."=\n";
		#INDEX ARRAY CONTAINS HASH-KEYS AND 1ST (DRIVING) VECTOR IS A HASHREF:
		@forlist = sort keys(%{$parms->{$listparms[0]}});
		my @keys = @vectorlist;
		@vectorlist = ();
		for (my $i=0;$i<=$#keys;$i++)
		{
			for (my $j=0;$j<=$#forlist;$j++)
			{
				if ($keys[$i] eq $forlist[$j])
				{
					push (@vectorlist, $j);
					last;
				}
			}
		}
		$i0 = scalar @vectorlist;   #NUMBER OF LOOP ITERATIONS TO BE DONE.
	}
	elsif (defined($vectorlist[0]) && $vectorlist[0] =~ /^\d+$/o)
	{
#print "<BR>-???2- VL=".join('|',@vectorlist)."=\n";
		#INDEX ARRAY OF JUST NUMBERS:
		if (ref($parms->{$listparms[0]}) eq 'HASH')
		{
			@forlist = sort keys(%{$parms->{$listparms[0]}});
		}
		$i0 = scalar @vectorlist;
	}
	else   #NO INDEX LIST, SEE IF WE HAVE INCREMENT EXPRESSION (ie. "0..10|2"), ELSE DETERMINE FROM 1ST PARAMETER:
	{
#print "<BR>-???3- NO INDEX LIST! vl0=$vectorlist[0]=\n";
		my ($istart) = 0;
		my ($iend) = undef;
		my ($iinc) = 1;
		my $parmnos0 = $parmnos;
		$istart = $1  if ($parmnos =~ s/([+-]?\d+)\.\./\.\./o);
		$iend = $1  if ($parmnos =~ s/\.\.([+-]?\d+)//o);
		$parmnos =~ s/\.\.//o;      #ADDED 19991203 (FIXES "START.. ").
		$iinc = $1  if ($parmnos =~ s/\|([+-]?\d+)//o);
		$parmnos =~ s/^\s*\,//o;    #ADDED 19991203 (FIXES "START.. ").
		shift @listparms  unless ($parmnos eq $parmnos0);   #1ST LISTPARM IS THE INCREMENT EXPRESSION, REMOVE IT NOW.
		if (ref($parms->{$listparms[0]}) eq 'HASH')
		{
			@forlist = sort keys(%{$parms->{$listparms[0]}});
			if ($#vectorlist >= 0) {       #THIS IF ADDED 20070914 TO SUPPORT ALTERNATELY SORTED LIST TO DRIVE HASH-DRIVEN LOOPS:
				my @keys = @vectorlist;   #IE. <!LOOP listparm hashparm, ...>
				@vectorlist = ();
				for (my $i=0;$i<=$#keys;$i++)
				{
					for (my $j=0;$j<=$#forlist;$j++)
					{
						if ($keys[$i] eq $forlist[$j])
						{
							push (@vectorlist, $forlist[$j]);
							last;
						}
					}
				}
				@forlist = @vectorlist;
			}
			$iend = $#forlist  unless (defined $iend);
#print "<BR>-???- 1ST ARG IS HASH:  VL=".join('|',@vectorlist)."= FL=".join('|',@forlist)."=\n";
		}
		else
		{
#no strict 'refs';
#print "<BR>-???- lp=".join('|',@listparms)."= parm0=$parms->{$listparms[0]}=\n";
#print "<BR>-REF=".ref($parms->{$listparms[0]})."=\n";
			unless (defined $iend)
			{
				$iend = (ref($parms->{$listparms[0]}) eq 'ARRAY'
				    ? $#{$parms->{$listparms[0]}} : 0);
			}
#print "<BR>-iend=$iend=\n";
		}
		@vectorlist = ();
		$i = $istart;
		$i0 = 0;
		while (1)
		{
			if ($istart <= $iend)
			{
				last  if ($i > $iend || $iinc <= 0);
			}
			else
			{
				last  if ($i < $iend || $iinc >= 0);
			}
			push (@vectorlist, $i);
			$i += $iinc;
			++$i0;
		}
	}

	my $icnt = 0;
	foreach $i (@vectorlist)
	{
		$lc = $loopcontent;
		foreach $j (keys %{$parms})
		{
			#if (@{$parms->{$j}})  #PARM IS A LIST, TAKE ITH ELEMENT.
			if (" @listparms " =~ /\s$j\s/)
			{
				#@parmlist = @{$parms->{$j}};
				if (ref($parms->{$j}) =~ /HASH/io)   #ADDED 20020613 TO ALLOW HASHES AS LOOP-DRIVERS!
				{
					#WANT_VALUES: $loopparms{$j} = $parms->{$j}->{(keys(%{$parms->{$j}}))[$i]};
					#$loopparms{$j} = (keys(%{$parms->{$j}}))[$i];  #CHGD. TO NEXT 20030515
					$loopparms{$j} = ${$parms->{$j}}{$forlist[$i]};
#						$lc =~ s/\:\%${looplabel}/$forlist[$i]/eg;  #MOVED TO 302l 20070713 ADDED 20031212 TO MAKE :%_loopname HOLD KEY OF 1ST HASH!
				}
				elsif (ref($parms->{$j}) =~ /ARRAY/io)  #TEST ADDED SO FOLLOWING SWITCHES COULD BE ADDED 20070615
				{
					$loopparms{$j} = ${$parms->{$j}}[$i];
				}
				elsif ($parms->{$j} =~ /^\$(\w+)/o)
				{
					#ADDED THIS ELSIF AND NEXT ELSE 20070615 TO 
					#PLAY NICE W/$dbh->selectall_arrayref()
					#SO WE CAN PASS A 2D ROW-BASED MATRIX OF DB DATA 
					#AND ACCCESS EACH COLUMN AS A NAMED PARAMETER BY
					#SPECIFYING: "-fieldname => '$matrix->[*][2]'"
					#WHERE "matrix" IS THE DRIVING LOOP PARAMETER NAME
					#AND "*" IS REPLACED BY NEXT SUBSCRIPT IN LOOP.
					#THIS *AVOIDS* HAVING TO CONVERT ROW-MAJOR ARRAYS 
					#TO COLUMN-MAJOR AND PASSING EACH COLUMN SLICE!
					my $one = $1;
					my $eval = $parms->{$j};
#					$eval =~ s/\*/$i/g;   #CHGD. TO NEXT 20070831 TO ALLOW RECURSION, IE. '$matrix->[*][*][0]', ETC.
					$eval =~ s/\*/$i/;
					my $eval0 = $eval;    #ADDED 20070831 TO SAVE FOR POSSIBLE REGRESSION.
					$eval =~ s/$one/parms\-\>\{$one\}/;
					$loopparms{$j} = eval $eval;
#print "\n---- j=$j= parm=$parms->{$j}= eval=$eval= lp now=$loopparms{$j}= at=$@=\n";
#					$loopparms{$j} = $parms->{$j}  if ($@);   #CHGD. TO NEXT 20070831 TO ALLOW RECURSION, IE. '$matrix->[*][*][0]', ETC.
					if ($@)
					{
						$eval0 =~ s/(?:\-\>)?\[\d+\]//o;  #STRIP OFF HIGH-ORDER DIMENSION SO THAT REFERENCE IS CORRECT W/N THE RECURSIVE CALL TO MAKALOOP!
						$loopparms{$j} = $eval0;
#print "-!!!- regressing back to lp=$loopparms{$j}=\n";
					}
				}
				else
				{
					$loopparms{$j} = $parms->{$j};
				}
				$loopparms{$j} = ''  unless(defined($loopparms{$j}));
			}
			else   #PARM IS A SCALER, TAKE IT'S VALUE.
			{
				$loopparms{$j} = $parms->{$j};
			}
		}
#print "<BR>-???- ll=$looplabel= lc=$lc=\n";
# (:# = CURRENT INDEX NUMBER INTO PARAMETER VECTORS; :* = ZERO-BASED ITERATION#; :% = CURRENT HASH KEY, IFF DRIVEN BY A HASHREF; :^ = NO. OF ITERATIONS TO BE DONE)
		$lc =~ s#<\!\:\%(${looplabel})([^>]*?)>#&makanop2($parms,$forlist[$i],$2)#egs;  #MOVED HERE 20070713 FROM 267l TO MAKE :%_loopname HOLD KEY OF 1ST HASH!
		$lc =~ s/\:\%${looplabel}/$forlist[$i]/egs;  #MOVED HERE 20070713 FROM 267l TO MAKE :%_loopname HOLD KEY OF 1ST HASH!
		$lc =~ s#<\!\:\#(${looplabel})([^>]*?)>#&makanop2($parms,$i,$2)#egs;
		$lc =~ s/\:\#${looplabel}([\+\-\*]\d+)/eval("$i$1")/egs;   #ALLOW OFFSETS, ie. ":#+1"
		$lc =~ s/\:\#${looplabel}/$i/egs;
		$lc =~ s#<\!\:\^(${looplabel})([^>]*?)>#&makanop2($parms,$i0,$2)#egs;
		$lc =~ s/\:\^${looplabel}([\+\-\*]\d+)/eval("$i0$1")/egs;   #CHGD. 20020926 FROM :* TO :^.
		$lc =~ s/\:\^${looplabel}/$i0/egs;
		$lc =~ s#<\!\:\*(${looplabel})([^>]*?)>#&makanop2($parms,$icnt,$2)#egs;
		$lc =~ s/\:\*${looplabel}([\+\-\*]\d+)/eval("$icnt$1")/egs;   #ADDED 20020926 TO RETURN INCREMENT NUMBER (1ST = 0);
		$lc =~ s/\:\*${looplabel}/$icnt/egs;
#foreach my $x (sort keys %loopparms) { print "<BR>-loopparm($x)=$loopparms{$x}=\n"; };
#print "<BR>--------------\n";

		#IF-STMT BELOW ADDED 20070830 TO EMULATE Template::Toolkit's ABILITY TO REFERENCE
		#SUBCOMPONENTS OF A REFERENCE BY NAME, IE:

		#-arg => {'id' => 'value', 'name' => 'value'}
		#...
		#<!LOOP arg, id, name>
		if (ref($parms->{$listparms[0]}) eq 'HASH')
		{
			foreach $j (@listparms)
			{
				unless (defined $loopparms{$j})
				{
#print "<BR>-!!!- will convert $j w/1st parm a HASH! i=$i= j=$j= F=$forlist[$i]= lp0=$listparms[0]= parm=$parms->{$listparms[0]}= val=$parms->{$listparms[0]}{$forlist[$i]}=\n";
					$lc =~ s#<\!\:$j([^>]*?)\:>.*?<\!\:\/\1>#&makanop1($parms->{$listparms[0]}{$forlist[$i]},$j,$1)#egs;
					$lc =~ s#<\!\:$j([^>]*?)>#&makanop1($parms->{$listparms[0]}{$forlist[$i]},$j,$1)#egs;
					$lc =~ s/\:\{$j\}/&makaswap($parms->{$listparms[0]}{$forlist[$i]},$j)/egs;   #ALLOW ":{word}"!
				}
			}
		}
		elsif (ref($parms->{$listparms[0]}) eq 'ARRAY')
		{
			foreach $j (@listparms)
			{
				unless (defined $loopparms{$j})
				{
#print "<BR>-!!!- will convert $j w/1st parm an ARRAY! i=$i= j=$j=  parm=$parms->{$listparms[0]}= val=$parms->{$listparms[0]}[$i]=\n";
					$lc =~ s#<\!\:$j([^>]*?)\:>.*?<\!\:\/\1>#&makanop1($parms->{$listparms[0]}[$i],$j,$1)#egs;
					$lc =~ s#<\!\:$j([^>]*?)>#&makanop1($parms->{$listparms[0]}[$i],$j,$1)#egs;
					$lc =~ s/\:\{$j\}/&makaswap($parms->{$listparms[0]}[$i],$j)/egs;   #ALLOW ":{word}"!
				}
			}
		}
		$rtn .= &modhtml(\$lc,\%loopparms);
		++$icnt;
	}

#	$i += $iinc;    #NEXT 2 REMOVED 20070809 - DON'T APPEAR TO BE NEEDED.
#	++$i0;
	return ($rtn);
};

sub makasel           #JWT: REDONE 05/20/1999!
{
	my ($parms, $selpart,$opspart,$endpart) = @_;

	local *makaselop = sub
	{
		my ($selparm,$padding,$valuparm,$valu,$dispvalu) = @_;
		$valu =~ s/\:\{?(\w+)\}?/&makaswap($parms,$1)/eg;      #ADDED 19991206
		$dispvalu =~ s/\:\{?(\w+)\}?/&makaswap($parms,$1)/eg;  #ADDED 19991206
		$valu = $dispvalu  unless ($valuparm);  #ADDED 05/17/1999
		my ($res) = "$padding<OPTION";
		if ($valuparm)
		{
			$res .= $valuparm . '"' . $valu . '"';
			$dispvalu = $valu . $dispvalu  unless ($dispvalu =~ /\S/o);
		}
		else
		{
			$valu = $dispvalu;
			$valu =~ s/\s+$//o;
		}
		$res .= '>' . $dispvalu;
		if (ref($parms->{$selparm}) =~ /ARRAY/o)   #JWT, IF SELECTED IS A LIST, CHECK ALL ELEMENTS!
		{
			my ($i);
			for ($i=0;$i<=$#{$parms->{$selparm}};$i++)
			{
				if ($valu eq ${$parms->{$selparm}}[$i])
				{
					$res =~ s/\<OPTION/\<OPTION SELECTED/io;
					last;
				}
			}
		}
		else
		{
			$res =~ s/\<OPTION/\<OPTION SELECTED/io  if ($valu eq $parms->{$selparm});
		}
		return $res;
	};

	#my ($rtn) = $selpart;  #CHGD TO NEXT LINE 05/17/1999
	my ($rtn);
	#if ($opspart =~ s/\s*\:(\w+)// || $selpart =~ s/\:(\w+)\s*>$//)  
	#CHANGED 12/18/98 TO PREVENT 1ST OPTION VALUE :# FROM DISAPPEARING!  JWT.

	if ($selpart =~ s/\:(\w+)\s*>$//o)
	{
		$selpart .= '>';
		my $selparm = $1;
		my ($opspart2);
		$opspart =~ s/SELECTED//gio;
		while ($opspart =~ s/(\s*)<OPTION(?:(\s+VALUE\s*\=\s*)([\"\'])([^\3]*?)\3[^>]*)?\s*\>([^<]*)//is)
		{
			$opspart2 .= &makaselop($selparm,$1,$2,$4,$5);
		}
		$opspart = $opspart2;
	}
	$rtn = $selpart . $opspart . $endpart;
	return ($rtn);
};

sub fetchinclude
{
	my $parms = shift;
	my ($fidurl) = shift;
	my ($modhtmlflag) = shift;
	my $tag = shift;
	my %includeparms;    #NEXT 6 ADDED 20030206 TO SUPPORT PARAMETERIZED INCLUDES!
	while (@_)
	{
		$_ = shift;
		$_ =~ s/\-//o;
		$includeparms{$_} = shift;
	}

	my ($html,$rtn);

	#$fidurl =~ s/\:(\w+)/&makaswap($1)/eg;      #JWT 05/19/1999
	$fidurl =~ s/^\"//o;          #JWT 5 NEXT LINES ADDED 1999/08/31.
	$fidurl =~ s/\"\s*$//o;
	$fidurl =~ s/\:\{?(\w+)\}?/&makaswap($parms,$1)/eg;
	if (defined($roothtmlhome) && $roothtmlhome =~ /\S/o)
	{
		$fidurl =~ s#^(?!(/|\w+\:))#$roothtmlhome/$1#ig;
	}
	#$fidurl =~ s/\:\{?(\w+)\}?/&makaswap($parms,$1)/eg;  #JWT 20010703: MOVED ABOVE PREV. IF
	if (open(HTMLIN,$fidurl))
	{
		$html = (<HTMLIN>);
		close HTMLIN;
	}
	else
	{
		$html = LWP::Simple::get($fidurl)  if ($useLWP);
		unless(defined($html) && $html =~ /\S/o)
		{
			$rtn = &html_error(">Could not include html page: \"$fidurl\"!");
			return ($rtn);
		}
	}
	if ($tag)  #ADDED 20060117 TO ALLOW PARTIAL FILE INCLUDES BASED ON TAGS.
	{
		$html =~ s/^.*\<\!\-\-\s+BEGIN\s+$tag\s*\-\-\>//is or $html = '';
		$html =~ s#\<\!\-\-\s+END\s+$tag\s*\-\-\>.*$##is;
	}
	#$rtn = &modhtml(\$html, %parms);  #CHGD. 20010720 TO HANDLE EMBEDS.
	#return ($rtn);
	#return $modhtmlflag ? &modhtml(\$html, %parms) : $html;  #CHD 20030206 TO SUPPORT PARAMETERIZED INCLUDES.
	return $modhtmlflag ? &modhtml(\$html, {%{$parms}, %includeparms}) : $html;
};

sub doeval
{
	my ($expn) = shift;
	my ($fid) = shift;
	if ($fid)
	{
		my ($dfltexpn) = $expn;
		$fid =~ s/^\s+//o;
		$fid =~ s/^.*\=\s*//o;
		$fid =~ s/[\"\']//go;
		$fid =~ s/\s+$//o;
		if (open(HTMLIN,$fid))
		{
			my @expns = (<HTMLIN>);
			$expn = join('', @expns);
			close HTMLIN;
		}
		else
		{
			$expn = LWP::Simple::get($fid)  if ($useLWP);
			unless (defined($expn) && $expn =~ /\S/o)
			{
				$expn = $dfltexpn;
				return (&html_error("Could not load embedded perl file: \"$fid\"!"))
				unless ($dfltexpn =~ /\S/o);
			}
		}
	}
	$expn =~ s/^\s*<!--//o;   #STRIP OFF ANY HTML COMMENT TAGS.
	$expn =~ s/-->\s*$//o;
	return ('')  if ($expn =~ /\`/o);   #DON'T ALLOW GRAVS!
#	return ('')  if ($expn =~ /\Wsystem\W/o);   #DON'T ALLOW SYSTEM CALLS - THIS NOT GOOD WAY TO DETECT!

	$expn =~ s/\&gt/>/go;	
	$expn =~ s/\&lt/</go;	

	$expn = 'package htmlpage; ' . $expn;
	my $x = eval "$expn";
	$x = "Invalid Perl Expression - returned $@"  unless (defined $x);
	return ($x);
};

sub dovar
{
	my $var = shift;
	my $two = shift;
	$two =~ s/^=//o;
	#$var = substr($var,0,1) . 'main::' . substr($var,1)  unless ($var =~ /\:\:/);
	#PREV. LINE CHANGED 2 NEXT LINE 20000920 TO ALLOW EVALS IN ASP!
	#$var = substr($var,0,1) . $calling_package . '::' . substr($var,1)  unless ($var =~ /\:\:/);
	#PREV. LINE CHGD. TO NEXT 20031006 TO FIX "${$VAR}...".
	$var =~ s/\$(\w)/\$$calling_package\:\:$1/g;
	my $one = eval $var;
	$one = $two  unless ($one);
	return $one;
};

sub makabutton
{
	my ($parms,$pre,$one,$two,$parmno,$four) = @_;
	my ($rtn) = "$pre$one$two$parmno$four";
	my ($myvalue);

	local *setbtnval = sub
	{
		my ($one,$two,$three) = @_;
		#$two =~ s/\:(\w+)/&makaswap($parms,$1)/eg;   #CHGD 19990527. JWT.
		$two =~ s/\:\{?(\w+)\}?/&makaswap($parms,$1)/eg;
		$myvalue = "$two";
		return ($one.$two.$three);
	};
	if ($two =~ /VALUE\s*=\"[^\"]*\"/io || $one =~ /CHECKBOX/io)
	{
		$two =~ s/(VALUE\s*=\")([^\"]*)(\")/&setbtnval($1,$2,$3)/ei;
		$rtn = "$pre$one$two$parmno$four";
#		$rtn =~ s/CHECKED//i  if (defined($myvalue)); #JWT:CHGD. TO NEXT: 19990609!
#		$rtn =~ s/CHECKED//io  if (defined($parms->{$parmno})); #JWT:CHGD. TO NEXT: 20100830 (v7.05)!
		$rtn =~ s/\bCHECKED\b//io  if (defined($parms->{$parmno}));
		#if ((defined($myvalue) && $parms->{$parmno} eq $myvalue) || ($one =~ /CHECKBOX/i && $parms->{$parmno} =~ /\S/))
		if (ref($parms->{$parmno}) eq 'ARRAY')  #NEXT 9 LINES ADDED 20000823
		{                                     #TO FIX CHECKBOXES W/SAME NAME 
			foreach my $i (@{$parms->{$parmno}})   #IN LOOPS!
			{
				if ($i eq $myvalue)
				{
					$rtn =~ s/\:$parmno/ CHECKED/;
					last;
				}
			}
			$rtn =~ s/\:$parmno//;
		}
		#elsif ((defined($parms->{$parmno}) && defined($myvalue) && $parms->{$parmno} eq $myvalue) || ($one =~ /CHECKBOX/i && $parms->{$parmno} =~ /\S/)) #JWT: 19990609! - CHGD. 2 NEXT 20041020!
		elsif ((defined($parms->{$parmno}) && defined($myvalue) && $parms->{$parmno} eq $myvalue) || (!defined($myvalue) && $one =~ /CHECKBOX/io && $parms->{$parmno} =~ /\S/o))
		{	#NOTE:  IF NO "VALUE=" IS SPECIFIED, THEN CHECKED UNLESS PARAMETER IS EMPTY/UNDEFINED!!
			$rtn =~ s/\:$parmno/ CHECKED/;
		}
		else
		{
			$rtn =~ s/\:$parmno//;
		}
#print "<BR>-loadhtml: myvalue=$myvalue= parmno=$parmno= parmval=".$parms->{$parmno}."= rtn=$rtn=\n";
	}
	else
	{
		$rtn =~ s/\:$parmno//;
	}
	return ($rtn);
};

sub makatext
{
	my $parms = shift;
	my $one = shift;
	my $parmno = shift;
	my $dflt = shift;

	my $val;
	my $rtn = $one;
	if (defined($parms->{$parmno}))
	{
		$val = $parms->{$parmno};
	}
	elsif ($dflt =~ /\S/o)
	{
		$dflt =~ s/^\=//o;
		$dflt =~ s/\"(.*?)\"/$1/;
		$val = $dflt;
	}
	if (defined($val))
	{
		if ($rtn =~ /\sVALUE\s*\=/io)
		{
			$rtn =~ s/(\sVALUE\s*=\s*\").*?\"/$1 . $val . '"'/ei;
		}
		else
		{
			$rtn = $one . ' VALUE="' . $val . '"';
		}
	}
	return ($rtn);
};

sub makanif
{
	my ($parms,$regex,$ifhtml,$nestid) = @_;

	my ($x) = '';
	my ($savesep) = $listsep;

	$regex =~ s/\&lt/</gio;
	$regex =~ s/\&gt/>/gio;
	$regex =~ s/\&le/<=/gio;
	$regex =~ s/\&ge/>=/gio;
	$regex =~ s/\\\%/\%/gio;
	$listsep = undef;

	$regex =~ s/([\'\"])(.*?)\1/
			my ($q, $body) = ($1, $2);
			$body =~ s!\:\{?(\w+)\}?!defined($parms->{$1}) ? &makaswap($parms,$1) : ''!eg;
			$body =~ s!\:!\:\x02!go;    #PROTECT AGAINST MULTIPLE SUBSTITUTION!
			$q.$body.$q;
	/eg;

	#$regex =~ s/\:\{?(\w+)\}?/defined($parms->{$1}) ? '"'.&makaswap($parms,$1).'"' : '""'/eg;

	#PREV. LINE REPLACED BY NEXT REGEX 20000309 TO QUOTE DOUBLE-QUOTES IN PARM. VALUE.
	$regex =~ s/\:\{?(\w+)\}?/
			my ($one) = $1;
			my ($res) = '""';
			if (defined($parms->{$one}))
			{
				$res = &makaswap($parms,$1);
				$res =~ s!\"!\\\"!go;
				$res = '"'.$res.'"';
			}
			$res
	/eg;
	$regex =~ s/\x02//go;    #UNPROTECT!
	$regex =~ s/\:([\$\@\%][\w\:\[\{\]\}\$]+)/&dovar($1)/egs  if ($evalsok);
	#$regex =~ s/\:([\$\@\%][\w\:\[\{\]\}\$\-\>]+)/&dovar($1)/egs  if ($evalsok);

	$regex =~ /^([^`]*)$/o;   #MAKE SURE EXPRESSION CONTAINS NO GRAVS!
	$regex = $1;   #20000626 UNTAINT REGEX FOR EVAL!
	$regex =~ s/([\@\#\$\%])([a-zA-Z_])/\\$1$2/g;   #QUOTE ANY SPECIAL PERL CHARS!
	#$regex =~ s/\"\"\:\w+\"\"/\"\"/g;   #FIX QUOTE BUG -FORCE UNDEFINED PARMS TO RETURN FALSE!
	$regex = '$x = ' . $regex . ';';
	eval $regex;
	$listsep = $savesep;

	my ($ifhtml1,$ifhtml2) = split(/<\!ELSE$nestid>\s*/i,$ifhtml);
	if ($x)
	{
		if (defined $ifhtml1)
		{
			$ifhtml1 =~ s#^(\s*)<\!\-\-(.*?)\-\->(\s*)$#$1$2$3#s;
			return ($ifhtml1);
		}
		else
		{
			return ('');
		}
	}
	else
	{
		if (defined $ifhtml2)
		{
			$ifhtml2 =~ s#^(\s*)<\!\-\-(.*?)\-\->(\s*)$#$1$2$3#s;
			return ($ifhtml2);
		}
		else
		{
			return ('');
		}
	}
};

sub makanop1
{
	#
	#	SUBSTITUTIONS IN COMMENTS TAKE THE ONE OF THE FORMS:
	#	<!:#default[before-stuff:#after-stuff]:>remove ...<!:/#>   OR
	#
	#		where:		"#"=Parameter number to substitute.
	#				"default"=Optional default value to use if parameter
	#				is empty or omitted.
	#				"stuff to remove" is removed.
	#
	#	NOTES:  ONLY 1 SUCH COMMENT MAY APPEAR PER LINE,
	#	THE DEFAULT, BEFORE-STUFF AND AFTER-STUFF MUST FIT ON ONE LINE.
	#	DUE TO HTML LIMITATIONS, ANY ">" BETWEEN THE "[...]" MUST BE
	#	SPECIFIED AS "&gt"!
	#
	#	THIS IS VERY USEFUL FOR SUBSTITUTING WHERE HTML WILL NOT ACCEPT
	#	COMMENTS, EXAMPLE:
	#
	#	<!:1Add[<INPUT NAME="submit" TYPE="submit" VALUE=":1 Record"&gt]:>
	#	<INPUT NAME="submit" TYPE="submit" VALUE="Create Record">
	#	<!/1>
	#
	#	THIS CAUSES A SUBMIT BUTTON WITH THE WORDS "Create Record" TO
	#	BE DISPLAYED IF PAGE IS JUST DISPLAYED, "Add Record" if loaded
	#	by loadhtml() (CGI) but no argument passed.  NOTE the use of
	#	"&gt" instead of ">" since HTML terminates comments with ">"!!!!
	#

	my $parms = shift;
	my $one = shift;
	my $two = shift;
	my ($rtn) = '';
	my ($picture);
	$picture = $1  if ($two =~ s/\%(.*)\%//o);
	#$three = shift;
	my $three = '';                ##NEXT 3 LINES REP. PREV. LINE 5/14/98  JWT!
	$two =~ s/^=//o;
	$two =~ s/([^\[]*)(\[.*\])?/$three = $2; $1/e;
	#$two =~ s/^=//;  #MOVED UP 2 LINES 20050523!
#print "-???- 1=$one= 2=$two= parms=$parms=\n";
	return ($two)  unless(defined($one) && ref($parms) eq 'HASH' && defined($parms->{$one}) && "\Q$parms->{$one}\E");
	if (defined($three) ? ($three =~ s/^\[(.*?)\]/$1/) : 0)
	{
		#$three =~ s/\:(\w+)/(${parms{$1}}||$two)/egx;  #JWT 19990611
		$three =~ s/\:(\w+)/(&makaswap($parms,$1)||$two)/egx;
		$three =~ s/\&gt/>/go;
		$rtn = $three;
	}
	elsif ($picture)  #ALLOW "<:1%10.2f%...> (SPRINTF) FORMATTING!
	{
		if ($picture =~ s/^&(.*)/$1/)
		{
			my ($picfn) = $1;
			$picfn =~ s/\:\{?(\w+)\}?/&makaswap($parms,$1)/eg;  #ADDED 20050517 TO ALLOW "%&:{alt_package}::commatize%"
			$picfn = $calling_package . '::' . $picfn  #ADDED 20050517 TO DEFAULT PACKAGE OF commatize TO MAIN RATHER THAN "LoadHtml"!
			unless ($picfn =~ /\:\:/o);
#				my (@args) = undef;   #CHGD. TO NEXT 20070426 TO PREVENT WARNING.
			my (@args) = ();
			(@args) = split(/\,/o,$1)  if ($picfn =~ s/\((.*)\)//o);
no strict 'refs';
#				if (defined(@args))   #CHGD. TO NEXT 20070426 TO PREVENT WARNING.
			if (@args)
			{
				for my $j (0..$#args)
				{
					$args[$j] =~ s/\:(\w+)/&makaswap($parms,$1)/egs;
				}
				#$rtn = &{$picfn}((${parms{$one}}||$two), @args); #JWT 19990611
				$rtn = &{$picfn}((&makaswap($parms,$one)||$two), @args);
			}
			else
			{
				#$rtn = &{$picfn}(${parms{$one}}||$two); #JWT 19990611
				$rtn = &{$picfn}(&makaswap($parms,$one)||$two);
			}
		}
		else
		{
			#$rtn = sprintf("%$picture",(${parms{$one}}||$two)); #JWT 19990611
			$rtn = sprintf("%$picture",(&makaswap($parms,$one)||$two));
		}
	}
	else
	{
		#$rtn = ${parms{$one}}||$two; #JWT 19990611
		$rtn = &makaswap($parms,$one)||$two;
	}
	return ($rtn);
};

sub makanop2
{
	#
	#	SUBSTITUTIONS IN COMMENTS TAKE THE ONE OF THE FORMS:
	#	<!:[#*^%]_LOOP_NAME:>remove ...<!:/_LOOP_NAME>   OR
	#
	#    ADDED 20070713

	my $parms = shift;
	my $one = shift;
	my $two = shift;

	my ($rtn) = '';
#print "<BR>-!!!- makanop2($one|$two)\n";
	my ($picture);
	$picture = $1  if ($two =~ s/\%(.*)\%//o);
	#$three = shift;
	my $three = '';                ##NEXT 3 LINES REP. PREV. LINE 5/14/98  JWT!
	$two =~ s/^=//o;
	if ($picture)  #ALLOW "<:1%10.2f%...> (SPRINTF) FORMATTING!
	{
		if ($picture =~ s/^&(.*)/$1/)
		{
			my ($picfn) = $1;
			$picfn =~ s/\:\{?(\w+)\}?/&makaswap($parms,$1)/eg;  #ADDED 20050517 TO ALLOW "%&:{alt_package}::commatize%"
			$picfn = $calling_package . '::' . $picfn  #ADDED 20050517 TO DEFAULT PACKAGE OF commatize TO MAIN RATHER THAN "LoadHtml"!
			unless ($picfn =~ /\:\:/o);
			my (@args) = ();
			(@args) = split(/\,/o,$1)  if ($picfn =~ s/\((.*)\)//o);
no strict 'refs';
			if (@args)
			{
				for my $j (0..$#args)
				{
					$args[$j] =~ s/\:(\w+)/&makaswap($parms,$1)/egs;
				}
				#$rtn = &{$picfn}((${parms{$one}}||$two), @args); #JWT 19990611
				$rtn = &{$picfn}($one, @args);
			}
			else
			{
				#$rtn = &{$picfn}(${parms{$one}}||$two); #JWT 19990611
				$rtn = &{$picfn}($one);
			}
		}
		else
		{
			#$rtn = sprintf("%$picture",(${parms{$one}}||$two)); #JWT 19990611
			$rtn = sprintf("%$picture",$one);
		}
	}
	else
	{
		$rtn = $one;
	}
	return ($rtn);
};

sub buildahash
{
	my ($one,$two) = @_;

	$two =~ s/^\s*<!--//o;
	$two =~ s/-->\s*$//o;
	$two =~ s/^\s*\(//o;
	$two =~ s/\)\s*$//o;
no strict 'refs';
	#$evalstr = "\%h1_myhash = ($two)";
	my $evalstr = "\%{\"h1_$one\"} = ($two)";
	my $x = eval $evalstr;
	return ('');
};

sub makahash
{
	#
	#	FORMAT:  <!$hashname{index_str}default>

	my ($one,$two,$three) = @_;
no strict 'refs';
	return (${"h1_$one"}{$two})  if (defined(${"h1_$one"}{$two}));
	return $three;
};

sub makaselect
{
	#
	#	FORMAT:  <!SELECTLIST select-options [DEFAULT[SEL]=":scalar|:list"] [VALUE[S]=:list] [(BYKEY)|BYVALUE] [REVERSE[D]]:#>..stuff to remove...
	#	...
	#	...<!/SELECTLIST>
	#
	#   NOTE:  "select-options" MAY CONTAIN "default="value"" AND "value"
	#	MAY ALS0 BE A SCALER PARAMETER.  THE LIST PARAMETER MUST BE AT
	#	THE END JUST BEFORE THE ">" WITH NO SPACE IN BETWEEN!
	#	THESE COMMENTS AND ANYTHING IN BETWEEN GETS REPLACED BY A SELECT-
	#	LISTBOX CONTAINING THE ITEMS CONTAINED IN THE LIST REFERENCED BY
	#	PARAMETER NUMBER "#".  (PASS AS "\@list").
	#	"select_options" MAY ALSO CONTAIN A "value=:#" PARAMETER
	#	SPECIFYING A SECOND LIST PARAMETER TO BE USED FOR THE ACTUAL 
	#	VALUES.  DEFAULTS TO SAME AS DISPLAYED LIST IF OMITTED.
	#	SPECIFYING A SCALAR OR LIST PARAMETER OR VALUE FOR "DEFAULT[SEL]=" 
	#	CAUSES VALUES WHICH MATCH THIS(THESE) VALUES TO BE SET TO SELECTED 
	#	BY DEFAULT WHEN THE LIST IS DISPLAYED.  DEFAULT= MATCHES THE 
	#	DEFAULT LIST AGAINST THE VALUES= LIST, DEFAULTSEL= MATCHES THE 
	#	DEFAULT LIST AGAINST THE *DISPLAYED* VALUES LIST (IF DIFFERENT).
	#	IF USING A HASH, BY DEFAULT IT IS CHARACTER SORTED BY KEY, IF 
	#	"BYVALUE" IS SPECIFIED, IT IS SORTED BY DISPLAYED VALUE.  "REVERSE" 
	#	CAUSES THE HASH OR LIST(S) TO BE DISPLAYED IN REVERSE ORDER.
	#
	my$parms = shift;
	my ($one) = shift;
	my ($two) = shift;
	my ($rtn) = '';
	my ($dflttype) = 'DEFAULT';
	my ($dfltval) = '';
	my (%dfltindex) = ('DEFAULT' => 'value', 'DEFAULTSEL' => 'sel');

	#@value_options = ();
	#@sel_options = ();
	my $options;
	if (ref($parms->{$two}) eq 'HASH')
	{
		#1ST PART OF NEXT IF ADDED 20031124 TO SUPPORT BOTH VALUE ARRAY AND DESCRIPTION HASH.
		if ($one =~ s/value[s]?=(\")?:(\w+)\1?//i)
		{
			@{$options->{value}} = @{$parms->{$2}};
			foreach my $i (@{$options->{value}})
			{
				push (@{$options->{sel}}, ${$parms->{$two}}{$i});
			}
		}
		elsif ($one =~ s/BYVALUE//io)
		{
			foreach my $i (sort {$parms->{$two}->{$a} cmp $parms->{$two}->{$b}} (keys(%{$parms->{$two}})))   #JWT: SORT'EM (ALPHA).
			{
				push (@{$options->{value}}, $i);
				push (@{$options->{sel}}, ${$parms->{$two}}{$i});
			}
		}
		else
		{
			$one =~ s/BYKEY//io;
			foreach my $i (sort(keys(%{$parms->{$two}})))   #JWT: SORT'EM (ALPHA).
			{
				push (@{$options->{value}}, $i);
				push (@{$options->{sel}}, ${$parms->{$two}}{$i});
			}
		}
	}
	else
	{
		@{$options->{sel}} = @{$parms->{$two}};

#NEXT 9 LINES (IF-OPTION) ADDED 20010410 TO ALLOW "VALUE=:#"!
		if ($one =~ s/value[s]?=(\")?:(\#)([\+\-\*]\d+)?\1?//i)
		{
			my ($indx) = $3;
			$indx =~ s/\+//o;
			for (my $i=0;$i<=$#{$options->{sel}};$i++)
			{
				push (@{$options->{value}}, $indx++);
			}
		}
		elsif ($one =~ s/value[s]?=(\")?:(\w+)\1?//i)
		{
		@{$options->{value}} = @{$parms->{$2}};
		}
		elsif ($one =~ s/value[s]?\s*=\s*(\")?:\#([\+\-\*]\d+)?\1?//i)
		{
			#JWT(ALLOW "VALUE=:# TO SPECIFY USING NUMERIC ARRAY-INDICES OF 
			#LIST TO BE USED AS ACTUAL VALUES.
			for my $i (0..$#{$options->{sel}})
			{
				push (@{$options->{value}}, eval("$i$2"));
			}
		}
		else
		{
			@{$options->{value}} = @{$options->{sel}};
		}
	}
	if ($one =~ s/REVERSED?//io)
	{
		@{$options->{sel}} = reverse(@{$options->{sel}});
		@{$options->{value}} = reverse(@{$options->{value}});
	}

#$one =~ s/default=\"(.*?)\"//i;
#$one =~ s/default=\"(.*?)\"//i;
#if ($one =~ s/(default|defaultsel)=\"(.*?)\"//i)  #20000505: CHGD 2 NEXT 2 LINES 2 MAKE QUOTES OPTIONAL!
	if (($one =~ s/(default|defaultsel)\s*=\s*\"(.*?)\"//io) 
			|| ($one =~ s/(default|defaultsel)\s*=\s*(\:?\S+)//io))  #20000505: CHGD 2 NEXT LINE 2 MAKE QUOTES OPTIONAL!
	{
		$dflttype = $1;
		$dfltval = $2;
		$dflttype =~ tr/a-z/A-Z/;
		#$dfltval =~ s/\:(\w+)/
		$dfltval =~ s/\:\{?(\w+)\}?/
				if (ref($parms->{$1}) eq 'ARRAY')
				{
					'(?:'.join('|',@{$parms->{$1}}).')'
				}
				else
				{
					quotemeta($parms->{$1})
				}
		/eg;
	}
#$one =~ s/\:(\w+)/$parms->{$1}/g;
	$one =~ s/\:\{?(\w+)\}?/$parms->{$1}/g;      #JWT 05/24/1999
	$rtn = "<SELECT $one>\n";
	$one = $dfltval;
	for (my $i=0;$i<=$#{$options->{sel}};$i++)
	{
		#if (${$options->{value}}[$i] =~ /^\Q${one}\E$/)
#		if (${($dfltindex{$dflttype}.'_options')}[$i] =~ /^${one}$/)
		if (${$options->{$dfltindex{$dflttype}}}[$i] =~ /^${one}$/)
		{
			$rtn .= "<OPTION SELECTED VALUE=\"${$options->{value}}[$i]\">${$options->{sel}}[$i]</OPTION>\n";
		}
		else
		{
			$rtn .= "<OPTION VALUE=\"${$options->{value}}[$i]\">${$options->{sel}}[$i]</OPTION>\n";
		}
	}
	$rtn .= '</SELECT>';
	return ($rtn);
};

sub modhtml
{
	my ($html, $parms) = @_;
	my ($v);

	#NOW FOR THE REAL MAGIC (FROM ANCIENT EGYPTIAN TABLETS)!...

	if ($cfgOps{loops})
	{
		while ($$html =~ s#<\!LOOP(\S*)\s+(.*?)>\s*(.*?)<\!/LOOP\1>\s*#&makaloop($parms, $2,$3,$1)#eis) {};
	}

	$$html =~ s#<\!HASH\s+(\w*?)\s*>(.*?)<\!\/HASH[^>]*>\s*#&buildahash($1,$2)#eigs
			if ($cfgOps{hashes});

	$$html =~ s#</FORM>#<INPUT NAME="CGIScript" TYPE=HIDDEN VALUE="$ENV{'SCRIPT_NAME'}">\n</FORM>#i 
			if ($cfgOps{CGIScript});

	#$$html =~ s#<\!INCLUDE\s+(.*?)>\s*#&fetchinclude($parms, $1)#eigs  #CHGD. TO NEXT 20010720 TO SUPPORT EMBEDS.
	$$html =~ s!<\!INCLUDE\s+(.*?)>\s*!
		my $one = $1;
		$one =~ s/^\"//o;
		$one =~ s/\"\s*$//o;
		my $tag = 0;
		$tag = $1  if ($one =~ s/\:(\w+)//o);  #ADDED 20060117 TO ALLOW PARTIAL FILE INCLUDES BASED ON TAGS.
		if ($one =~ s/\((.*)\)\s*$//)
		{
			my $includeparms = $1;
			$includeparms =~ s/\=/\=\>/go;
			eval "&fetchinclude($parms, \"$one\", 1, $tag, $includeparms)";
		}
		else
		{
			&fetchinclude($parms, $one, 1, $tag);
		}
	!eigs  if ($cfgOps{includes});

	if ($cfgOps{pocs})
	{
		$$html =~ s#<\!POC:>(.*?)<\!/POC>#$poc#ig  if ($cfgOps{pocs});  #20000606
		$$html =~ s#<\!POC>#$poc#ig  if ($cfgOps{pocs});
	}

	$$html =~ s#\<\!FILEDATE([^\>]*?)\:\>.*?\<\!\/FILEDATE\>#&filedate($parms,$1,0)#eig;  #20020327
	$$html =~ s#\<\!FILEDATE([^\>]*)\>#&filedate($parms,$1,0)#eig;  #20020327
	$$html =~ s#\<\!TODAY([^\>]*?)\:\>.*?\<\!\/TODAY\>#&filedate($parms,$1,1)#eig;  #20020327
	$$html =~ s#\<\!TODAY([^\>]*)\>#&filedate($parms,$1,1)#eig;  #20020327

	while ($$html =~ s#<\!IF(\S*)\s+(.*?)>\s*(.*?)<\!/IF\1>\s*#&makanif($parms, $2,$3,$1)#eigs) {};

	$$html =~ s#<\!\:(\w+)([^>]*?)\:>.*?<\!\:\/\1>#&makanop1($parms,$1,$2)#egs;
	$$html =~ s#<\!\:(\w+)([^>]*?)>#&makanop1($parms,$1,$2)#egs;
#JWT:CHGD. TO NEXT 20100920 TO ALLOW STYLES IN SELECT TAG!	$$html =~ s#(<SELECT\s+[^\:\>]*?\:\w+\s*>)(.*?)(<\/SELECT>)#&makasel($parms, $1,$2,$3)#eigs;
	$$html =~ s#(<SELECT\s+[^\>]*\>)(.*?)(<\/SELECT>)#&makasel($parms, $1,$2,$3)#eigs;
	$$html =~ s#<\!SELECTLIST\s+(.*?)\:(\w+)\s*>(.*?)<\!\/SELECTLIST>\s*#&makaselect($parms, $1,$2,$3)#eigs;

	$$html =~ s#(<TEXTAREA[^>]*?)\:(\w+)(?:\=([\"\']?)([^\3]*)\3|\>)?\s*>.*?(<\/TEXTAREA>)#$1.'>'.($parms->{$2}||$4).$5#eigs;
	$$html =~ s/(TYPE\s*=\s*\"?)(CHECKBOX|RADIO)([^>]*?\:)(\w+)(\s*>)/&makabutton($parms,$1,$2,$3,$4,$5)/eigs;
	$$html =~ s/(<\s*INPUT[^\<]*?)\:(\w+)(\=.*?)?>/&makatext($parms, $1,$2,$3).'>'/eigs;
	$$html =~ s/\:(\d+)/&makaswap($parms,$1)/egs 
			if ($cfgOps{numbers});   #STILL ALLOW JUST ":number"!
	$$html =~ s/\:\{(\w+)\}/&makaswap($parms,$1)/egs;   #ALLOW ":{word}"!
	$$html =~ s#<\!\%(\w+)\s*\{([^\}]*?)\}([^>]*?)>#&makahash($1,$2,$3)#egs 
			if ($cfgOps{hashes});
#	$$html =~ s/\:\{(\w+)\}/&makaswap($parms,$1)/egs;   #ALLOW ":{word}"!  #MOVED ABOVE PREV. LINE 20070428 SO "<!%hash{:{parameter}}>" WOULD WORK (USED IN "dsm")!

	#NEXT LINE ADDED 20031028 TO ALLOW IN-PARM EXPRESSIONS!
	$$html =~ s/\:\{([^\}]+)\}/&makamath($1)/egs;   #ALLOW STUFF LIKE ":{:{parm1}+:{parm2}+3}"!
	if ($evalsok)
	{
		$$html =~ s#<\!\:([\$\@\%][\w\:]+\{.*?\})([^>]*?)\:>.*?<\!\:\/\1>#&dovar($1,$2)#egs;  #ADDED 20000123 TO HANDLE HASHES W/NON VARIABLE CHARACTERS IN KEYS.
		$$html =~ s#<\!\:(\$[\w\:\[\{\]\}\$]+)([^>]*?)\:>.*?<\!\:\/\1>#&dovar($1,$2)#egs;
		$$html =~ s#<\!\:([\$\@\%][\w\:]+\{.*?\})([^>]*?)>#&dovar($1,$2)#egs;  #ADDED 20000123 TO HANDLE HASHES W/NON VARIABLE CHARACTERS IN KEYS.
		$$html =~ s#<\!\:(\$[\w\:\[\{\]\}\$]+)([^>]*?)>#&dovar($1,$2)#egs;
		$$html =~ s/\:(\$[\w\:\[\{\]\}\$]+)/&dovar($1)/egs;
		$$html =~ s/<\!EVAL\s+(.*?)(?:\/EVAL)?>/&doeval($1)/eigs;
		$$html =~ s#<\!PERL\s*([^>]*)>\s*(.*?)<\!\/PERL>#&doeval($2,$1)#eigs  if ($cfgOps{perls});
	}
	else
	{
		$$html =~ s#<!PERL\s*([^>]*)>(.*?)<!/PERL>##igso;
	};

	#THE FOLLOWING ALLOWS SETTING ' HREF="relative/link.htm" TO 
	#A CGI-WRAPPER, IE. ' HREF="http://my/path/cgi-bin/myscript.pl?relative/link.htm".

	if (defined($hrefhtmlhome))
	{
#		my $hrefhtmlback = $hrefhtmlhome;
#		$hrefhtmlback =~ s#\/[^\/]+$##o;
		if (defined($hrefcase))     #THIS ALLOWS CONTROL OF WHICH "href=" LINKS TO WRAP WITH CGI!
		{
			if ($hrefcase eq 'l')   #ONLY CONVERT LOWER-CASE "href=" LINKS THIS WAY.
			{
				$$html =~ s# (href)\s*=\s*\"(?!(\#|/|\w+\:))# $1=\"$hrefhtmlhome/$2#g;   #ADDED HREF ON 20010719!
			}
			else                    #ONLY CONVERT UPPER-CASE "HREF=" LINKS THIS WAY.
			{
				$$html =~ s# (HREF)\s*=\s*\"(?!(\#|/|\w+\:))# $1=\"$hrefhtmlhome/$2#g;   #ADDED HREF ON 20010719!
			}
		}
		else                        #CONVERT ALL "HREF=" LINKS THIS WAY.
		{
			$$html =~ s#( href)\s*=\s*\"(?!(\#|/|\w+\:))# $1=\"$hrefhtmlhome/$2#gi;   #ADDED HREF ON 20010719!
			#$$html =~ s# (href)\s*=\s*\"(?!(\#|/|\w+\:))# $1=\"$hrefhtmlhome/\x02$2#gi;   #ADDED HREF ON 20010719!
		}

		#RECURSIVELY CONVERT "my/deep/deeper/../../path" to "my/path".

	}
	if (defined($htmlhome) && $htmlhome =~ /\S/o)      #JWT 6 NEXT LINES ADDED 1999/08/31.
	{
		$$html =~ s#([\'\"])((?:\.\.\/)+)#$1$htmlhome/$2#ig;  #INSERT <htmlhome> between '|" and "../[../]*"
		1 while ($$html =~ s#[^\/]+\/\.\.\/##o);   #RECURSIVELY CONVERT "my/deep/deeper/../../path" to "my/path".
		if (defined($hrefcase))     #ADDED 20020117:  THIS ALLOWS CONTROL OF WHICH LINKS TO WRAP WITH CGI!
		{
			if ($hrefcase eq 'l')   #ONLY CONVERT LOWER-CASE "href=" LINKS THIS WAY.
			{
				$$html =~ s#(src|ground|href)\s*=\s*\"(?!(\#|/|\w+\:))#$1=\"$htmlhome/$2#g;   #CONVERT RELATIVE LINKS TO ABSOLUTE ONES.
				$$html =~ s# (cl|ht)\s*=\s*\"(?!(\#|/|\w+\:))# $1=\"$htmlhome/$2#g;   #CONVERT RELATIVE SPECIAL JAVASCRIPT LINKS TO ABSOLUTE ONES.
				$$html =~ s#(\s+window\.open\s*\(\s*\')(?!(\#|/|\w+\:))#$1$htmlhome/$2#g;     #ADDED 20050504 TO MAKE CALENDAR.JS WORK!
			}
			else
			{
				$$html =~ s#(SRC|GROUND|HREF)\s*=\s*\"(?!(\#|/|\w+\:))#$1=\"$htmlhome/$2#g;   #CONVERT RELATIVE LINKS TO ABSOLUTE ONES.
				$$html =~ s# (CL|HT)\s*=\s*\"(?!(\#|/|\w+\:))# $1=\"$htmlhome/$2#g;   #CONVERT RELATIVE SPECIAL JAVASCRIPT LINKS TO ABSOLUTE ONES.
			}
		}
		else
		{
			$$html =~ s#(src|ground|href)\s*=\s*\"(?!(\#|/|\w+\:))#$1=\"$htmlhome/$2#ig;   #CONVERT RELATIVE LINKS TO ABSOLUTE ONES.
			$$html =~ s# (cl|ht)\s*=\s*\"(?!(\#|/|\w+\:))# $1=\"$htmlhome/$2#ig;   #CONVERT RELATIVE SPECIAL JAVASCRIPT LINKS TO ABSOLUTE ONES.
			$$html =~ s#(\s+window\.open\s*\(\s*\')(?!(\#|/|\w+\:))#$1$htmlhome/$2#ig;     #ADDED 20050504 TO MAKE CALENDAR.JS WORK!
		}
		$$html =~ s#\.\.\/##g;   #REMOVE ANY REMAING "../".

		#NOTE:  SOME JAVASCRIPT RELATIVE LINK VALUES MAY STILL NEED HAND-CONVERTING 
		#VIA BUILDHTML, FOLLOWED BY ADDITIONAL APP-SPECIFIC REGICES, ONE EXAMPLE 
		#WAS THE "JSFPR" SITE, FILLED WITH ASSIGNMENTS OF "'image/file.gif'", 
		#WHICH WERE CONVERTED USING:
		#	$html =~ s#([\'\"])images/#$1$main_htmlsubdir/images/#ig;

	}

	#NEXT LINE ADDED 20010720 TO SUPPORT EMBEDS (NON-PARSED INCLUDES).

#	$$html =~ s#<\!EMBED\s+(.*?)>\s*#&fetchinclude($parms, $1, 0)#eigs  
#			if ($cfgOps{embeds});

	#ABOVE CHANGED TO NEXT REGEX 20060117 TO ALLOW PARTIAL FILE INCLUDES BASED ON TAGS.
	$$html =~ s!<\!EMBED\s+(.*?)>\s*!
		my $one = $1;
		$one =~ s/^\"//o;
		$one =~ s/\"\s*$//o;
		my $tag = 0;
		$tag = $1  if ($one =~ s/\:(\w+)//o);
		&fetchinclude($parms, $one, 0, $tag);
	!eigs  if ($cfgOps{embeds});

	return ($$html);
}

sub html_error
{
	my ($mymsg) = shift;
	
	return (<<END_HTML);
<html>
<head><title>CGI Program - Unexpected Error!</title></head>
<body>
<h1>$mymsg</h1>
<hr>
Please contact $poc for more information.
</body></html>
END_HTML
}

sub SetHtmlHome
{
	($htmlhome, $roothtmlhome, $hrefhtmlhome, $hrefcase) = @_;

	# hrefcase = undef:  convert all "href=" to $hrefhtmlhome.
	# hrefcase = 'l':    convert only "href=" to $hrefhtmlhome.
	# hrefcase = '~l':    convert only "HREF=" to $hrefhtmlhome.
}

sub loadhtml_package   #ADDED 20000920 TO ALLOW EVALS IN ASP!
{
	$calling_package = shift || 'main';
}

sub filedate    #ADDED 20020327
{
	my $parms = shift;
	my $fmt = shift;
	my $usetoday = shift;   #ADDED 20030501 TO SUPPORT DISPLAYING CURRENT DATE!

	$fmt =~ s/^\=\s*//o;
	$fmt =~ s/[\"\']//go;
	$fmt =~ s/\:$//go;
	$fmt ||= 'mm/dd/yy';    #SUPPLY A REASONABLE DEFAULT.
	my $mtime = time;
	(undef,undef,undef,undef,undef,undef,undef,undef,undef,undef,$mtime) 
			= stat ($0)  unless ($usetoday);   #CHGD. TO NEXT:12/30/15:
#			= stat ($parms->{'0'})  unless ($usetoday);
	$mtime ||= time;

#to_char() comes from DBD::Sprite, but is usable as a stand-alone program and is optional.

#x	my @parmsave = @_;
#x	@_ = ($mtime, $fmt);

#x	eval "package $calling_package; require 'to_char.pl'";
	eval 'use Date::Time2fmtstr; $haveTime2fmtstr = 1; 1'  unless ($haveTime2fmtstr);
#x	if ($@)
#x	{
#x		@_ = @parmsave;
#x		return scalar(localtime($mtime));
#x	}
#x	if (!$rtnTime || $err =~ /^Invalid/o)
#x	{
		#@_ = (time, 'mm/dd/yy');
		#do 'to_char.pl';
#x		my $qualified_fn = $calling_package . '::to_char';
no strict 'refs';
#x		return &{$qualified_fn}($mtime, $fmt);
		return @_ ? scalar(localtime($mtime)) : &time2str($mtime, $fmt) || scalar(localtime($mtime));
#x	}
#x	@_ = @parmsave;
#x	return $rtnTime;
}

1
