# FILE: template.pl
# DESCRIPTION: Template Interpreter
#-------------------------------------------------------------------------------
# DISCUS COPYRIGHT NOTICE
#
# Discus is copyright (c) 2002 by DiscusWare, LLC, all rights reserved.
# The use of Discus is governed by the Discus License Agreement which is
# available from the Discus WWW site at:
#    http://www.discusware.com/discus/license
#
# Pursuant to the Discus License Agreement, this copyright notice may not be
# removed or altered in any way.
#-------------------------------------------------------------------------------

use strict;
use vars qw($GLOBAL_OPTIONS $DCONF $PARAMS);

###
### page_cacheproc
###
### Cache processor for page data
###

sub page_cacheproc {
	my ($topic, $substitutions) = @_;
	$substitutions = hash_merge($substitutions, $PARAMS->{"page_start"}->{$topic});
	my @temp = @{ $PARAMS->{"page_preproc"}->{$topic} };
	my $text = (parse_handle(\@temp, $substitutions, undef, $PARAMS->{"page_ifs"}->{$topic}))[0];
	return $text;
}

###
### template_makecache
###
### Makes a cache for a certain template
###

sub template_makecache {
	my ($subst, $array, $pagetopicflag, $addmessage) = @_;
	my @arr = @{ $array}; 
	my @arr_out = ();
	my $ins_ctr = 0;
	my $topic = 0;
	if (defined $pagetopicflag) {
		$topic = $pagetopicflag;
		foreach my $line (@arr) {
			while ($line =~ m|<#insert\s*(?:once)?\s*part\s*\((.*?)\)\s*"(.*?)"\s*#>|) {
				$ins_ctr += 1; $line = join("", $`, @{$PARAMS->{"skin,$topic,$2"}}, $');
				template_error("Recursive insert error (more than 100 inserts in table") if $ins_ctr > 100;
			}
			$ins_ctr = 0;		
		}
	}
	@arr_out = map { join("", $_, "\n") } split(/\n/, join("", @arr));
	@arr_out = grep { ! /^\s*#/ && /\S/ && ! /^\s*<!--(BEGIN|END)-->\s*$/ } @arr_out;
	@arr_out = map { s/^\s*//; s/\s*$//; join("", $_, "\n"); } @arr_out;
	@arr_out = map { s/\s*\#\#.*\s*$/\n/; $_; } @arr_out;
	@arr = ();
	my $ifcounter = 0;
	my @ifarray = ();
	my @ifsubs = ();
	$subst = basic_substitutions($subst);
	$subst = color_substitutions($subst);
	if (defined $pagetopicflag) {
		if (defined $PARAMS->{"skin,$topic,INIT"}) {
			my @temp_params = @{ $PARAMS->{"skin,$topic,INIT"} };
			$subst = (parse_handle(\@temp_params, $subst, 1))[1];
		}
	}
	my $linectr = 0;
	my $indefine = 0;
	my @context = ();
	foreach my $line (@arr_out) {
		$linectr += 1;
		shift @context if scalar @context > 10;
		push @context, $line;
		if ($line =~ m|^\s*<#define\s*array|i) {
			$indefine = 1; next;
		} elsif ($line =~ m|^\s*<#/define|i) {
			$indefine = 0; next;
		} elsif ($indefine == 1) {
			next;
		}
		if ($line !~ m|<#endif#>|i && $line =~ m|^\s*<#if(.*?)#>\s*$|i) {
			$ifcounter += 1; my $zz = $1;
			push @ifarray, { id => $ifcounter, statement => $zz, linecounter => $linectr };
			my $z = makecache_is_true($zz, $addmessage);
			if (defined $z) {
				push @ifsubs, {condition => $z, sub => sub { return $z; }};
			} else {
				push @ifsubs, {condition => $zz, sub => sub { return is_true($zz, $_[0]); }};
			}
			$line = "\r%%$ifcounter%%\n";
		} elsif ($line =~ m|^\s*<#else#>\s*$|i) {
			if (! scalar @ifarray) {
				my $string = "<p>Unmatched &lt;#else#&gt; statement</p><ul>";
				while ( my $cline  = shift @context) {
					$cline =~ s/([^\w\s])/join("", "&#", ord($1), ";")/ge;
					$string .= "<li>$cline\n" if scalar @context;
					$string .= "<li><font color='#ff0000'>$cline</font>\n" if ! scalar @context;
				}
				template_error("$string</ul>");
			}
			$line = "\r%%?$ifarray[$#ifarray]->{id}%%\n";
			$ifarray[$#ifarray]->{else} = $linectr;
		} elsif ($line =~ m|^\s*<#endif#>\s*$|i) {
			if (! scalar @ifarray) {
				my $string = "<p>Unmatched &lt;#endif#&gt; statement</p><ul>";
				while ( my $cline  = shift @context) {
					$cline =~ s/([^\w\s])/join("", "&#", ord($1), ";")/ge;
					$string .= "<li>$cline\n" if scalar @context;
					$string .= "<li><font color='#ff0000'>$cline</font>\n" if ! scalar @context;
				}
				template_error("$string</ul>");
			}
			$line = "\r%%:$ifarray[$#ifarray]->{id}%%\n";
			my $x = "\r%%$ifarray[$#ifarray]->{id}+";
			$x .= ($ifarray[$#ifarray]->{else} - $ifarray[$#ifarray]->{linecounter}) if defined $ifarray[$#ifarray]->{else};
			$x .= "|";
			$x .= ($linectr - $ifarray[$#ifarray]->{linecounter});
			$x .= "%%\n";
			$arr_out[$ifarray[$#ifarray]->{linecounter}-1] = $x;
			pop @ifarray;
		} elsif ($line =~ m|[<\{]#\s*if(.*?)\s*#[>\}](.*?)[<\{]#else#[>\}](.*?)[<\{]#endif#[>\}]|i) {
			my ($before, $condition, $iftrue, $iffalse, $after) = ($`, $1, $2, $3, $');
			my $z = makecache_is_true($condition, $addmessage);
			if (defined $z) {
				$line = join("", $before, $iftrue, $after) if $z == 1;
				$line = join("", $before, $iffalse, $after) if $z == 0;
			} else {
				$ifcounter += 1;
				push @ifsubs, {condition => $condition, sub => sub { return is_true($condition, $_[0]); }};
				$line = join("", $before, "\r%%$ifcounter%%", $iftrue, "\r%%?$ifcounter%%", $iffalse, "\r%%:$ifcounter%%", $after);
			}
		} elsif ($line =~ m|[<\{]#\s*if(.*?)\s*#[>\}](.*?)[<\{]#endif#[>\}]|i) {
			my ($before, $condition, $iftrue, $after) = ($`, $1, $2, $');
			my $z = makecache_is_true($condition, $addmessage);
			if (defined $z) {
				$line = join("", $before, $iftrue, $after) if $z == 1;
				$line = join("", $before, $after) if $z == 0;
			} else {
				$ifcounter += 1;
				push @ifsubs, {condition => $condition, sub => sub { return is_true($condition, $_[0]); }};
				$line = join("", $before, "\r%%$ifcounter%%", $iftrue, "\r%%:$ifcounter%%", $after);
			}
		} elsif ($line =~ m|[<\{]#\s*if(.*?)\s*#[>\}]|i) {
			my ($before, $condition, $after) = ($`, $1, $');
			my $z = makecache_is_true($condition, $addmessage);
			if (defined $z) {
				$line = join("", $before, $after) if $z == 1;
				$line = join("", $before, "\n") if $z == 0;
			} else {
				$ifcounter += 1;
				push @ifsubs, {condition => $condition, sub => sub { return is_true($condition, $_[0]); }};
				chomp $after;
				$line = join("", $before, "\r%%$ifcounter%%", $after, "\r%%:$ifcounter%%", "\n");
			}
		}			
			
	}
	if (scalar @ifarray) {
		template_error("Unterminated IF condition &lt;#if $ifarray[$#ifarray]->{statement}#&gt;");
	}
	$PARAMS->{L} = read_language();
	@arr_out = map { s/\$DCONF(?:->)?\{(\w+)\}/$DCONF->{$1}/g; s/\$GLOBAL_OPTIONS(?:->)?\{(\w+)\}/$GLOBAL_OPTIONS->{$1}/g; s/\$L(?:->)?\{(\w+)\}/defined $PARAMS->{L}->{$1} ? $PARAMS->{L}->{$1} : $&/ge; s/\$color->\{(\w+)\}/$subst->{color}->{$1}/g;	if ($topic > 0) { s/\$head->{topic_number}/$topic/g; } $_; } @arr_out;
	return (\@arr_out, $subst, \@ifsubs);
}


###
### page_makecache
###
### Makes a cache of the 'page' section for the selected skin
###

sub page_makecache {
	my ($topic, $substitutions) = @_;
	my @arr = @{$PARAMS->{"skin,$topic,page"}};
	$topic = "main" if $topic == 0;
	($PARAMS->{"page_preproc"}->{$topic}, $PARAMS->{"page_start"}->{$topic}, $PARAMS->{"page_ifs"}->{$topic}) = template_makecache($substitutions, \@arr, $topic, $substitutions->{addmessage});
}

###
### makecache_is_true
###
### Stripped-down processing for cache maker
###

sub makecache_is_true {
	my ($statement, $addmessage) = @_;
	my @condx = ();
	my $y = "==|!=|>|<|>=|<=|eq|ne|gt|ge|lt|le|rexp=~|rexp!~|=~|!~";
	if ($statement =~ m|&&|) {
		@condx = split(/&&/, $statement);
	} elsif ($statement =~ m#\|\|#) {
		@condx = split(/\|\|/, $statement);
	} else {
		@condx = ($statement);
	}
	my $boolean = "or";	$boolean = "and" if $statement =~ m|&&|;
	my $other = 0;
	foreach my $condx (@condx) {
		$condx =~ s/\s+$//;
		$condx =~ s/^\s+//;
		$condx =~ s/\s+/ /g;
		next if $condx eq "";
		if ($condx =~ m|^pro$|i) {
			return 1 if $DCONF->{pro} && $boolean eq "or";
			return 0 if !$DCONF->{pro} && $boolean eq "and";
		} elsif ($condx =~ m|^\!(\s*)pro$|i) {
			return 1 if !$DCONF->{pro} && $boolean eq "or";
			return 0 if $DCONF->{pro} && $boolean eq "and";
		} elsif ($condx =~ m%^\s*\$GLOBAL_OPTIONS(?:->)?\{(\w+)\}$%) {
			my $z = _is_true($GLOBAL_OPTIONS->{$1}, ">=", 1, {}, 1);
			return 1 if $z && $boolean eq "or";
			return 0 if ! $z && $boolean eq "and";
		} elsif ($condx =~ m%^\s*\$GLOBAL_OPTIONS(?:->)?\{(\w+)\}\s*($y)\s*([^\s\$]+)\s*$%o) {
			my $z = _is_true($GLOBAL_OPTIONS->{$1}, $2, $3, {}, 1);
			return 1 if $z && $boolean eq "or";
			return 0 if ! $z && $boolean eq "and";
		} elsif ($condx =~ m%^\s*\$DCONF(?:->)?\{(\w+)\}\s*($y)\s*([^\s\$]+)\s*$%o) {
			my $z = _is_true($DCONF->{$1}, $2, $3, {}, 1);
			return 1 if $z && $boolean eq "or";
			return 0 if ! $z && $boolean eq "and";
		} elsif (ref $addmessage eq "HASH" && $condx =~ m%^\s*\$addmessage->\{(\w+)\}\s*($y)\s*([^\s\$]+)\s*$%o) {
			my $z = _is_true($addmessage->{$1}, $2, $3, {}, 1);
			return 1 if $z && $boolean eq "or";
			return 0 if ! $z && $boolean eq "and";
		} else {
			$other = 1;
		}
	}
	return undef if $other;
	return 1 if $boolean eq "and";
	return 0;
}

###
### basic_substitutions
###
### Sets up basic substitutions
###

sub basic_substitutions {
	my ($substitutions) = @_;
	performance_string("* Initializing basic template variables");
	my @aarray = ();
	for (my $alt_key = 1; $alt_key <= 20; $alt_key += 1) { ## ALT_COLOR_MAX
		$aarray[$alt_key-1]->{'color'} = $GLOBAL_OPTIONS->{"alt_color_$alt_key"};
		$aarray[$alt_key-1]->{'number'} = $alt_key;
	}
	$substitutions->{'_ALTCOLOR'} = \@aarray;
	$substitutions->{'DCONF'} = $DCONF;
	$substitutions->{'PARAMS'} = $PARAMS;
	$substitutions->{'GLOBAL_OPTIONS'} = $GLOBAL_OPTIONS;
	$substitutions->{'browser'}->{'ns4'} = (($ENV{'HTTP_USER_AGENT'} =~ m|4\.(\d+)| && $ENV{'HTTP_USER_AGENT'} !~ m|MSIE|) ? 1 : 0);
	$substitutions->{'browser'}->{'ie4'} = ($ENV{'HTTP_USER_AGENT'} =~ m|MSIE 4\.(\d+)|) ? 1 : 0;
	$substitutions->{'browser'}->{'ie5'} = ($ENV{'HTTP_USER_AGENT'} =~ m|MSIE 5\.(\d+)|) ? 1 : 0;
	$substitutions->{'browser'}->{'ie55'} = ($ENV{'HTTP_USER_AGENT'} =~ m|MSIE 5\.(\d+)| && $1 >= 5) ? 1 : 0;
	$substitutions->{'browser'}->{'ie50'} = $substitutions->{'browser'}->{'ie5'} - $substitutions->{'browser'}->{'ie55'};
	$substitutions->{'browser'}->{'ie6'} = ($ENV{'HTTP_USER_AGENT'} =~ m|MSIE 6\.(\d+)|) ? 1 : 0;
	$substitutions->{'browser'}->{'ns6'} = ($ENV{'HTTP_USER_AGENT'} =~ m|6\.(\d+)| && $ENV{'HTTP_USER_AGENT'} !~ m|MSIE|) ? 1 : 0;
	$substitutions->{'browser'}->{'nsX'} = ($substitutions->{'browser'}->{'ns4'} || $substitutions->{'browser'}->{'ns6'}) ? 1 : 0;
	$substitutions->{'browser'}->{'ieX'} = ($substitutions->{'browser'}->{'ie4'} || $substitutions->{'browser'}->{'ie5'} || $substitutions->{'browser'}->{'ie6'}) ? 1 : 0;
	$substitutions->{'browser'}->{'dhtml'} = ($substitutions->{'browser'}->{'nsX'} || $substitutions->{'browser'}->{'ieX'}) ? 1 : 0;
	$substitutions->{'browser'}->{'mac'} = $ENV{'HTTP_USER_AGENT'} =~ m|Macintosh| ? 1 : 0;
	$substitutions->{'browser'}->{'mozilla'} = $ENV{'HTTP_USER_AGENT'} =~ m|Mozilla/([\d\.]+)| ? $1 : 0;
	$substitutions->{'browser'}->{'webtv'} = $ENV{'HTTP_USER_AGENT'} =~ m|webtv|i ? $1 : 0;
	if ($GLOBAL_OPTIONS->{skinvar_board} || $GLOBAL_OPTIONS->{skinvar_users}) {
		dreq("fcn-info");
		$substitutions = board_info_substitutions($substitutions);
	}	
	$DCONF->{'icon_dir'} = "icons" if $DCONF->{'icon_dir'} eq "";
	$PARAMS->{'icon_url'} = join("/", $DCONF->{html_url}, $DCONF->{icon_dir});
	$PARAMS->{'skin_icon_url'} = join("/", $DCONF->{html_url}, $DCONF->{icon_dir}, $GLOBAL_OPTIONS->{skinchoice});
	$PARAMS->{'skin_icon_dir'} = join("/", $DCONF->{html_dir}, $DCONF->{icon_dir}, $GLOBAL_OPTIONS->{skinchoice});
	$PARAMS->{'topic_file_url'} = $GLOBAL_OPTIONS->{use_static_topic_document} == 1 ? join("/", $DCONF->{message_url}, $DCONF->{board_topics_file}) : join("/", $DCONF->{script_url}, "discus.$DCONF->{cgi_extension}?pg=topics");
	foreach my $k (keys %{$DCONF}) {
		$substitutions->{'_'}->{$k} = $DCONF->{$k} if ref $DCONF->{$k} eq "" && ! defined $substitutions->{'_'}->{$k};
	}
	foreach my $k (keys %{$PARAMS}) {
		$substitutions->{'_'}->{$k} = $PARAMS->{$k} if ref $PARAMS->{$k} eq "" && ! defined $substitutions->{'_'}->{$k};
	}
	return $substitutions;	
}

###
### color_substitutions
###
### Initializes color substitutions
###

sub color_substitutions {
	my ($substitutions) = @_;
	$substitutions->{'color'}->{'bgcolor'} = $GLOBAL_OPTIONS->{'COLOR_bgcolor'} if $GLOBAL_OPTIONS->{'COLOR_bgcolor'} ne "0";
	$substitutions->{'color'}->{'text'} = $GLOBAL_OPTIONS->{'COLOR_text'} if $GLOBAL_OPTIONS->{'COLOR_text'} ne "0";
	$substitutions->{'color'}->{'link'} = $GLOBAL_OPTIONS->{'COLOR_link'} if $GLOBAL_OPTIONS->{'COLOR_link'} ne "0";
	$substitutions->{'color'}->{'vlink'} = $GLOBAL_OPTIONS->{'COLOR_vlink'} if $GLOBAL_OPTIONS->{'COLOR_vlink'} ne "0";
	$substitutions->{'color'}->{'image'} = $GLOBAL_OPTIONS->{'COLOR_image'} if $GLOBAL_OPTIONS->{'COLOR_image'} ne "0";
	$substitutions->{'color'}->{'background'} = $substitutions->{'color'}->{'image'};
	$substitutions->{'color'}->{'alink'} = $GLOBAL_OPTIONS->{'COLOR_alink'} if $GLOBAL_OPTIONS->{'COLOR_alink'} ne "0";
	$substitutions->{'color'}->{'face'} = $GLOBAL_OPTIONS->{'COLOR_face'} if $GLOBAL_OPTIONS->{'COLOR_face'} ne "0";
	$substitutions->{'color'}->{'size'} = $GLOBAL_OPTIONS->{'COLOR_size'} if $GLOBAL_OPTIONS->{'COLOR_size'} ne "0";
	$substitutions->{'color'}->{'smsize'} = $GLOBAL_OPTIONS->{'COLOR_size'} > 1 ? $GLOBAL_OPTIONS->{'COLOR_size'}-1 : 1;
	for (my $alt_key = 1; $alt_key <= 20; $alt_key += 1) { ## ALT_COLOR_MAX
		$substitutions->{'color'}->{"alt_$alt_key"} =
			defined $GLOBAL_OPTIONS->{"alt_color_$alt_key"} ? $GLOBAL_OPTIONS->{"alt_color_$alt_key"} :
			$alt_key == 1 ? "#0000a0" :
			$alt_key <= 10 ? ( $alt_key % 2 == 0 ? "#f7f7f7" : "#d0d0d0" ) : "#000000";
	}
	return $substitutions;
}

###
### templ_int
###
### Controls interpretation of templates.  Modification of this script
### is strongly discouraged for all but the most advanced of Perl programmers.
### Modifications should be made in the templates or skins themselves.
###

sub templ_int {
	my ($filename, $substitutions, $cached_file) = @_;
	if ($GLOBAL_OPTIONS->{tcache} == 2) {
		dreq("templnew");
		return (dtl_interpret($filename, $substitutions))[0];		
	}
	if ($filename =~ /^\*(\d*)page$/ && defined $PARAMS->{"page_preproc"}->{$1}) {
		return page_cacheproc($1, $substitutions);
	}
	if (! defined $substitutions->{'_ALTCOLOR'}) {
		$substitutions = basic_substitutions($substitutions);
	}
	if (! defined $substitutions->{'color_override'}) {
		$substitutions = color_substitutions($substitutions);
	}
	my @template = ();
	if (defined $cached_file) {
		@template = @{ $cached_file };
	} elsif ($filename =~ m|^\*(\d*)(.*)|) {
		my ($topic, $partial) = ($1, $2);
		return part_of_skin($topic, $partial, $substitutions);
	} elsif ($filename =~ m|^/|) {
		if (open(TEMPLATE, "< $filename")) {
			@template = <TEMPLATE>;
			close (TEMPLATE);
		} else {
			template_error("Could not read file $filename, so this screen could not be rendered.");
		}
	} elsif ($GLOBAL_OPTIONS->{tcache}) {
		my ($template, $ifarray) = fetch_template_file($filename, undef, 1);
		my @temp = @{ $template };
		performance_string("+ inserting cached template ($filename.tmpl) via templ_int");
		my $text = (parse_handle(\@temp, $substitutions, undef, $ifarray))[0];
		return $text;
	} else {
		@template = @{ fetch_template_file($filename, undef, 0) };
	}
	template_error("Template file [admin_dir]/template/*******/$filename.tmpl is corrupt!  It does not contain the &lt;!--END--&gt; identifier.  Perhaps it was not completely uploaded!") if !grep(m|^\s*<!--END-->\s*$|, @template);
	performance_string("+ inserting template ($filename.tmpl) via templ_int");
	while ($_ = shift(@template)) {
		return (parse_handle(\@template, $substitutions, 1))[0] if m|^\s*<!--BEGIN-->\s*$|;
	}
	template_error("Template file [admin_dir]/template/*******/$filename.tmpl is corrupt!  It does not contain a &lt;!--BEGIN--&gt; identifier!");
}

sub fetch_template_file {
	my ($filename, $forcedir, $docache) = @_;
	my @dirs = ('custom', 'pro', 'admin', 'ui', 'mailmesg');
#	@dirs = ($forcedir) if defined $forcedir;
	performance_string("< fetch_template_file for file $filename [force dir: $forcedir]");
	if ($docache) {
		if (! -e "$DCONF->{admin_dir}/data/tcache") {
			mkdir "$DCONF->{admin_dir}/data/tcache", oct($DCONF->{perms0777});
			chmod oct($DCONF->{perms0777}), "$DCONF->{admin_dir}/data/tcache";
		}
		if (-e "$DCONF->{admin_dir}/data/tcache/$filename.cache") {
			my $hash = {}; my @ifarray = ();
			open (FILE, "< $DCONF->{admin_dir}/data/tcache/$filename.cache");
			binmode FILE;
			while (<FILE>) {
				if (! /\S/) {
					my @file = <FILE>;
					close (FILE);
					performance_string("* returned cached file: ", scalar @file, " lines, ", scalar @ifarray, " conditions");
					my $ch = quotemeta(chr(12));
					@file = map { s/$ch\%\%(\W*)(\d+)/\r\%\%$1$2/g; $_; } @file;
					return (\@file, \@ifarray);
				}
				my ($num, $condition) = /^(\d+)\t(.*?)\s*$/;
				if ($condition =~ /^[01]$/) {
					$ifarray[$num-1] = { sub => sub { return $condition; }};
				} else {
					$ifarray[$num-1] = { sub => sub { return is_true($condition, $_[0]); }};
				}
			}
			close (FILE);
			template_error("Could not render cached file for $filename.tmpl, so this screen could not be rendered.");
		} else {
			foreach my $dir (@dirs) {
				if (open(TEMPLATE, "< $DCONF->{admin_dir}/template/$dir/$filename.tmpl")) {
					my @t = <TEMPLATE>;
					close (TEMPLATE);
WW:					while (my $x = shift @t) {
						last WW if $x =~ /^\s*<!--BEGIN-->\s*$/i;
					}
					my ($array, $subst, $ifarray) = template_makecache({}, \@t);
					my $ch = chr(12);
					my @array = @{ $array };
					@array = map { s/\r\%\%(\W*)(\d+)/$ch\%\%$1$2/g; $_; } @array;
					open (FILE, "> $DCONF->{admin_dir}/data/tcache/$filename.cache");
					binmode FILE;
					my $ctr = 0;
					foreach my $line (@{ $ifarray }) {
						$ctr++;
						print FILE "$ctr\t$line->{condition}\n";
					}
					print FILE "\n";
					print FILE @array;
					close (FILE);					
					return ($array, $ifarray);					
				}
			}
		}
	} else {
		foreach my $dir (@dirs) {
			if (open(TEMPLATE, "< $DCONF->{admin_dir}/template/$dir/$filename.tmpl")) {
				my @t = <TEMPLATE>;
				close (TEMPLATE);
				return \@t;
			}
		}
	}
	template_error("Could not read file [admin_dir]/template/*******/$filename.tmpl, so this screen could not be rendered.");
}

sub part_of_skin {
	my ($topic, $partial, $substitutions, $testflag, $noinit) = @_;
	$PARAMS->{skinlog} = 1;
	if (! defined $PARAMS->{"skin,$topic"}) {
		$PARAMS->{"skin,$topic"} = read_skin($topic);
		if (ref $PARAMS->{"skin,$topic"} eq 'ARRAY') {
			my $flag = "";
			foreach my $line (@{$PARAMS->{"skin,$topic"}}) {
				if ($line =~ m|^\s*<#part "([^"]+)"#>\s*$|i) {
					$flag = $1;
					@{$PARAMS->{"skin,$topic,$flag"}} = ("<!--BEGIN-->\n");
				} elsif ($line =~ m|^\s*<#/part#>\s*$|i) {
					push (@{$PARAMS->{"skin,$topic,$flag"}}, "<!--END-->\n");
					$flag = "";
				} elsif ($flag ne "") {
					push (@{$PARAMS->{"skin,$topic,$flag"}}, $line);
				}
			}
			page_makecache($topic, $substitutions) if $partial eq 'page';
		} else {
			$PARAMS->{"skin,$topic"} = $PARAMS->{"skin,"};
			if ($partial eq 'page') {
				if (defined $PARAMS->{"page_preproc"}->{main}) {
					$PARAMS->{"page_preproc"}->{$topic} = $PARAMS->{"page_preproc"}->{main};
					$PARAMS->{"page_start"}->{$topic} = $PARAMS->{"page_start"}->{main};
					$PARAMS->{"page_ifs"}->{$topic} = $PARAMS->{"page_ifs"}->{main};
				} else {
					page_makecache($topic, $substitutions);
				}
			}
		}
	}
	my $t = $topic eq "" ? "main" : $topic;
	if ($partial eq 'page' && defined $PARAMS->{"page_preproc"}->{$t}) {
		return page_cacheproc($t, $substitutions);
	}
	if (! $noinit) {
		if (defined $PARAMS->{"skin,$topic,INIT"}) {
			my @temp_params = @{ $PARAMS->{"skin,$topic,INIT"} };
			$substitutions = (parse_handle(\@temp_params, $substitutions, 1))[1];
		}	
	}
	if (defined($PARAMS->{"skin,$topic,$partial"})) {
		return 1 if $testflag;
		my @temp_params = @{ $PARAMS->{"skin,$topic,$partial"} };
		return (parse_handle(\@temp_params, $substitutions, 1))[0];
	} else {
		return 0 if $testflag;
		template_error("Template Request for part [$partial] is not valid.  Skin does not contain this part!");
	}
}

sub read_skin {
	my ($topic) = @_;
	my @result = ();
	my $z = $GLOBAL_OPTIONS->{'skinchoice'};
	$GLOBAL_OPTIONS->{'skinchoice'} = $` if $z =~ /\-/ && ! -e "$DCONF->{admin_dir}/skins/$GLOBAL_OPTIONS->{'skinchoice'}.tmpl";
	$GLOBAL_OPTIONS->{'skinchoice'} = "" if ! -e "$DCONF->{admin_dir}/skins/$GLOBAL_OPTIONS->{'skinchoice'}.tmpl";
	$GLOBAL_OPTIONS->{'skinchoice'} = "tables2" if $GLOBAL_OPTIONS->{'skinchoice'} eq "";
	performance_string("< read_skin for topic $topic, skin choice $z");
	if ($topic != 0) {
		if ($DCONF->{"registered_skin_$topic"} ne "") {
			my $filepath = $DCONF->{"registered_skin_$topic"};
			if (open(FILE, "< $filepath")) {
				@result = <FILE>;
				close (FILE);
				return \@result;
			} else {			
				template_error("Skin Open Error: Could not get skin for topic [$topic]; registered skin $filepath could not be opened ($!)");
			}
		}	
		my $path = get_message_path($topic);
		if (-f "$path/default.tmpl" && -r "$path/default.tmpl") {
			open (FILE, "< $path/default.tmpl");
			@result = <FILE>;
			close (FILE);
			return \@result;
		} elsif (defined $PARAMS->{'skin,'}) {
			performance_string("* read_skin reverting to default");
			my @k = grep { /^skin,,/ } keys %{$PARAMS};
			foreach my $k (@k) {
				my $parts = (split(/,/, $k, 3))[2];
				$PARAMS->{"skin,$topic,$parts"} = $PARAMS->{$k};
			}
			return undef;			
		}
	}
	if (open(FILE, "$DCONF->{admin_dir}/skins/$GLOBAL_OPTIONS->{'skinchoice'}.tmpl")) {
		@result = <FILE>;
		close (FILE);
		return \@result;
	} else {
		template_error("Skin Open Error: Could not get skin for topic [$topic] with global skin [$GLOBAL_OPTIONS->{'skinchoice'}]");
	}
}

sub debug_handle {
	my ($l) = @_;
	$l =~ s/([^\w\s])/join('', '&#', ord($1), ';')/ge;
	$l =~ s/\s/&nbsp;/g;
	return $l;
}

sub parse_handle {
	my ($arrayref, $substitutions, $original, $ifarray) = @_;
	my $text = ""; my $flag = 0; my $position = 0; my $ifcounter = 0; my $skipper = 0;
	my ($res1, $res2, $res3, $hold, $simple);
	$simple = 0;
	my @ifarray = ();
	@ifarray = @{$ifarray} if ref $ifarray eq 'ARRAY';
WO:	while (my $line = shift (@{$arrayref})) {
		$line =~ s/\r\n/\n/g; ##$line =~ s/\r/\n/g;
		if ($line =~ m|^\s*<!--END-->\s*$|i) {
			return ($text, $substitutions, 0, $skipper);
		}
		next if $line =~ m|^\s*#|;
		next if $line =~ m|^\s*<!--\s*BEGIN\s*-->\s*$|i;
		$line = "$`\n" if $line =~ m|##|;
		$line = $` if $line =~ m|\s*\\\s*$|;
		next if $line !~ m|\S|;
		if ($line =~ /^\s*<#\s*simple\s*variables\s*#>\s*$/i) {
			$simple = 1; next;
		}
		next if $line =~ m|^\s*<#label "?(\w+)"?\s*#>\s*$|i;
		$line =~ s/^\s*<#>/#/;
		if ($flag == $position && $line =~ /\r\%\%\W?(\d+)([\+\|\d]*)\%\%/) {
			my $z = $1; my $cond = $2;
			if ($cond eq "" && $line =~ /\r\%\%(\d+)\%\%(.*)\r\%\%\?\1\%\%(.*)\r\%\%\:\1\%\%/s) {
				if (ref $ifarray[$z-1]->{sub} ne 'CODE') {
					template_error(join("", "1) [", ref $ifarray, "] Bad cached 'if' statement for condition #", $z));
				}
				my $res = &{ $ifarray[$z-1]->{sub} }($substitutions);
				$line = join("", $`, $2, $') if $res == 1;
				$line = join("", $`, $3, $') if $res == 0;
			} elsif ($cond eq "" && $line =~ /\r\%\%(\d+)\%\%(.*)\r\%\%\:\1\%\%/s) {
				if (ref $ifarray[$z-1]->{sub} ne 'CODE') {
					template_error(join("", "2) [", ref $ifarray, "] Bad cached 'if' statement for condition #", $z));
				}
				my $res = &{ $ifarray[$z-1]->{sub} }($substitutions);
				$line = join("", $`, $2, $') if $res == 1;
				$line = join("", $`, $') if $res == 0;
			} elsif ($cond =~ m%^\+(\d*)\|(\d+)$%) {
				my ($elseoffset, $endifoffset) = ($1, $2);
				if (ref $ifarray[$z-1]->{sub} ne 'CODE') {
					template_error(join("", "3) [", ref $ifarray, "] Bad cached 'if' statement for condition #", $z));
				}
				my $res = &{ $ifarray[$z-1]->{sub} }($substitutions);
				if ($res == 1) {
					if ($elseoffset ne "") {
						splice @{$arrayref}, $elseoffset-1, 1+$endifoffset-$elseoffset;
					} else {
						splice @{$arrayref}, $endifoffset-1, 1;
					}
				} else {
					if ($elseoffset eq "") {
						splice @{$arrayref}, 0, $endifoffset;
					} else {
						splice @{$arrayref}, $endifoffset-1, 1;
						splice @{$arrayref}, 0, $elseoffset;
					}					
				}
				next WO;
			}
		}
		if ($line =~ /[<\{]#/) {
			if (!$position && $line !~ m|[<\{]#endif#[>\}]|i && $line =~ m|^\s*<#if (.*)#>\s*$|) {
				$flag = 2 - is_true($1, $substitutions);
				$hold = $line; $hold =~ s/</&lt;/g; $hold =~ s/>/&gt;/g;
				$position = 1;
			} elsif ($position && !$ifcounter && $line =~ m|^\s*<#else#>\s*$|) {
				$position = 2;
			} elsif ($position && !$ifcounter && $line =~ m|^\s*<#endif#>\s*$|) {
				$position = 0; $flag = 0;
			} elsif (!$position && !$flag && $line =~ m|^\s*<#else#>\s*$|) {
				unshift(@{$arrayref}, $line);
				return ($text, $substitutions, 0, $skipper);
			} elsif (!$position && !$flag && $line =~ m|^\s*<#endif#>\s*$|) {
				unshift(@{$arrayref}, $line);
				return ($text, $substitutions, 0, $skipper);
			} elsif ($position != $flag && $line !~ m|[<\{]#endif#[>\}]|i && $line =~ m|^\s*<#if (.*)#>\s*$|i) {
				$ifcounter += 1;
			} elsif ($position != $flag && $line =~ m|^\s*<#endif#>\s*$|) {
				$ifcounter -= 1;
			} elsif ($flag == $position) {
				if ($line =~ m|^\s*<#\s*dtl\s*off\s*#>\s*$|i) {
UI:					while (my $z = shift @{ $arrayref }) {
						next WO if $z =~ m|^\s*<#\s*dtl\s*on\s*#>\s*$|i;
						$text .= $z;
					}
				} elsif ($line =~ m|^\s*<#\s*perl\s*#>\s*$|i) {
					while (my $z = shift @{ $arrayref }) {
						next WO if $z =~ m|^\s*<#\s*/\s*perl\s*#>\s*$|i;
					}
					next WO;
				} elsif ($line =~ m|^\s*<#\s*no\s*perl\s*#>\s*$|i || $line =~ m|^\s*<#\s*/\s*no\s*perl\s*#>\s*$|i) {
					next WO;				
				} elsif ($line !~ m|[<\{]#endif#[>\}]|i && $line =~ m|^\s*<#if (.*)#>\s*$|i) {
					unshift(@{$arrayref}, $line);
					my ($tb, $sx, $s, $f) = parse_handle($arrayref, $substitutions, 0, $ifarray);
					$text .= $tb;
					$substitutions = $sx;
					if ($f) {
						$flag = 0; $position = 0; $skipper = 1;
					}
					return ($text, $substitutions, $s, $skipper) if $s == 1;
				} elsif ($line =~ m|^\s*<#foreach\s+\$(\w+)\s+\(\@(\w+)\)#>\s*$|i) {
					my $z = "";
					if (!defined($substitutions->{$2})) {
						my $a = $2; my $st = $line;	$st =~ s/([^\w\s])/join("", "&#", ord($1), ";")/ge;
						template_error("Undefined array \@$a in 'foreach' statement '$st' in this template.");
					} else {
						$z = process_foreach($arrayref, $1, $2, $substitutions, $ifarray);
						$text .= $z;
					}
				} elsif ($line =~ m|^\s*<#for\s+\$(\w+)\s*=\s*(.*?)\s+to\s+(.*?)\s+step\s+(.*?)\s*#>\s*$|i) {
					my $z = process_for($arrayref, $1, $2, $3, $4, $substitutions, $ifarray);
					$text .= $z;
				} elsif ($line =~ m|^\s*<#define|) {
					if ($line =~ m|^\s*<#define\s+\$(\w+)\s*=\s*"?(.*?)"?#>\s*$|i) {
						$substitutions->{"_"}->{$1} = substitutions($2, $substitutions, $simple, $ifarray);
					} elsif ($line =~ m|^\s*<#define\s+\$(\w+)\s*\[\s*(.*?)\s*\]\s*=\s*"?(.*?)"?#>\s*$|i) {
						next if substitutions($2, $substitutions, $simple, $ifarray) <= 0;
						$substitutions->{$1}[-1+substitutions($2, $substitutions, $simple, $ifarray)] = substitutions($3, $substitutions, $simple, $ifarray);
					} elsif ($line =~ m|^\s*<#define\s*array\s*\@(\w+)\s*\(([\w\s,]+)\)\s*#>\s*$|i) {
						$substitutions = process_define_array($1, $2, $substitutions, $arrayref);
					} elsif ($line =~ m|^\s*<#define\s*\$GLOBAL_OPTIONS(?:->)?\{(\w+)\}\s*=\s*"?(.*?)"?\s*#>\s*$|i) {
						next if $GLOBAL_OPTIONS->{skinvar_override_option} == 0;
						$GLOBAL_OPTIONS->{$1} = substitutions($2, $substitutions, $simple, $ifarray);
					} else {
						$line =~ s/([^\w\s])/join(ord($1), "&#", ";")/ge;
						template_error("Bad &lt;#define#&gt; statement in template file... This line was: $line");
					}				
				} elsif ($line =~ m|^\s*<#mathdefine:?\s*\$(\w+)\s*=\s*(.*?)\s*#>\s*$|i) {
					$substitutions->{"_"}->{$1} = mathdefine(substitutions($2, $substitutions, $simple, $ifarray));			
				} elsif ($line =~ m|^\s*<#language\s*:\s*\$L(?:->)?\{(\w+)\}\s*=?\s*"(.*?)"\s*#>\s*$|i) {
					$PARAMS->{L} = read_language();
					if (! defined $PARAMS->{L}->{$1}) {
						$PARAMS->{L}->{$1} = substitutions($2, $substitutions, $simple, $ifarray);
					}				
				} elsif ($line =~ m|^\s*<#key\s+replace\s*\$(\w+)\s*"(.*?)"\s*[-=]\s*>\s*"(.*?)"\s*#>|i) {
					$substitutions->{$1}->{$3} = $substitutions->{$1}->{$2};
				} elsif ($line =~ m|^\s*<#skipto\s*"?(\w+)"?#>\s*$|i) {
					my $one = $1;
					$flag = 0; $position = 0; $skipper = 1; my $sc = 0;
WI:					while (my $sh = shift(@{$arrayref})) {
						$sc += 1;
						next WO if $sh =~ m|^\s*<#label\s*"?$one\s*"?#>\s*$|i;
					}
					template_error("Bad label <b>$one</b> called by SKIPTO directive.");
				} elsif ($line =~ m|^\s*<#sub \s*(.*)\s*#>\s*$|i) {
					$substitutions = declare_sub($arrayref, $1, $substitutions, $ifarray);
				} elsif ($line =~ m|^\s*<#while\s*\((.*)\)\s*#>\s*$|i) {
					my ($tb, $sb) = process_while($arrayref, $1, $substitutions, $ifarray);
					$text .= $tb;
					$substitutions = $sb;
				} elsif ($line =~ m|^\s*<#math:\s*(.*?)\s*#>\s*$|i) {
					$substitutions = process_math($1, $substitutions);
				} elsif ($line =~ m|^\s*<#\s*replace\s*"(.*?)"\s*with\s*"(.*?)"\s*in\s*\$(.*?)\s*#>\s*$|i) {
					my ($oldpattern, $newpattern, $variablename) = ($1, $2, $3);
					my $q = quotemeta($oldpattern); my $n = substitutions($newpattern, $substitutions, $simple, $ifarray);
					$substitutions->{"_"}->{$variablename} =~ s/$q/$n/gi;
				} elsif ($line =~ m|^\s*<#reference\s*array\s*\@(\w+)\s+from\s*(.*?)\s*except\s*(.*?)\s*=\s*\((.*?)\)\s*#>\s*$|i) {
					my ($arrayname, $arrayfrom, $field, $except) = ($1, $2, $3, $4);
					my %e = map { trim($_), 1 } split(/,/, trim($except));
					$field = trim($field);
					my $tgt = undef;
					$tgt = $substitutions->{$1}->{$2} if $arrayfrom =~ m|\$(\w+)->\{(\w+)\}|;
					my @z = ();
					if (ref $tgt eq 'ARRAY') {
						@z = grep ($e{$_->{$field}} == 0, @{$tgt});
					}
					$substitutions->{$arrayname} = \@z;
				} elsif ($line =~ m|^\s*<#reference\s*array\s*\@(\w+)\s+from\s*(.*?)\s*#>\s*$|i) {
					my ($arrayname, $arrayfrom) = ($1, $2);
					my $tgt = undef;
					$tgt = $substitutions->{$1}->{$2} if $arrayfrom =~ m|\$(\w+)->\{(\w+)\}|;
					if (ref $tgt ne 'ARRAY') {
						my @empty = ();
						$tgt = \@empty;
					}
					$substitutions->{$arrayname} = $tgt;
				} elsif ($line =~ m|^\s*<#exit#>\s*$|i) {
					$flag = 0;
					return ($text, $substitutions, 1, $skipper);
				} else {
					my $u = process_line($line, $substitutions, $simple, $ifarray);
					$text .= $u;
				}
			}
		} elsif ($flag == $position) {
			my $u = process_line($line, $substitutions, $simple, $ifarray);
			$text .= $u;
		}
	}
	if ($flag) {
		template_error("Unterminated IF condition in template file... make sure IF statements are closed with &lt;#endif#&gt;!  Last line was: [$hold].");
	}
	return ($text, $substitutions, 0, $skipper);
}

sub mathdefine {
	my ($text) = @_;
	while ($text =~ /(\w+)\(/) {
		my ($before, $match, $after) = ($`, $1, $');
		$after = mathdefine($after);
		if ($after !~ /([^\)]+)\)/) {
			template_error("Bad Math Definition: $before $match $after (no closing paren)");
		}
		my ($after1, $after2) = ($1, $');
		if ($match =~ /^int$/i) {
			$after1 = int($after1);
		} else {
			my @x = split(/,/, $after1);
			my $ct = trim(shift @x);
			while (my $u = shift @x) {
				$u = trim($u);
				$ct += $u if ($match =~ /^sum$/i || $match =~ /^plus$/i);	
				$ct -= $u if ($match =~ /^diff$/i || $match =~ /^minus$/i);	
				$ct *= $u if ($match =~ /^prod$/i || $match =~ /^product$/ || $match =~ /^times$/i);	
				$ct /= $u if $u != 0 && ($match =~ /^quot$/i || $match =~ /^quotient$/ || $match =~ /^divide$/i);	
				$ct %= $u if $u != 0 && ($match =~ /^mod$/i || $match =~ /^modulus$/);	
				$ct **= $u if ($match =~ /^raise$/i);	
			}
			$after1 = $ct;			
		}
		$text = join("", $before, $after1, $after2);		
	}
	return $text;
}

sub process_while {
	my ($arrayref, $input, $substitutions, $ifarray) = @_;
	undef my @inwhile;
	my $whilecounter = 0;
	undef my $text;
O:	while (my $y = shift(@{ $arrayref })) {
		if ($y =~ m|^\s*<#/while#>\s*$|i) {
			$input =~ s/^\s+//;
			$input =~ s/\s+$//;
inner:		while (is_true($input, $substitutions)) {
				my @inwhile_copy = @inwhile;
				$whilecounter += 1;
				my ($textback, $sx, $s) = parse_handle(\@inwhile_copy, $substitutions, undef, $ifarray);
				$substitutions = $sx;
				if ($textback =~ m|\r?<#\r?last\r?#>\r?|i || $textback =~ m|\r?<#\r?break\r?#>\r?|i) {
					$textback = $`;
					$text .= $textback;
					last inner;
				}
				$text .= $textback;
				if ($whilecounter > 5000) {
					template_error("While reached 5000 iterations.  Suspect never-ending loop!  Statement [$input].");
				}
			}
			return ($text, $substitutions);
		} elsif ($y =~ m|^\s*<#while\s*\((.*)\)\s*#>\s*$|i) {
			my ($st, $sx) = process_while($arrayref, $1, $substitutions, $ifarray);
			$substitutions = $sx;
			push (@inwhile, $st);
		} else {
			push (@inwhile, $y);
		}
	}
	template_error("While condition [$input] not terminated!");
}

sub declare_sub {
	my ($arrayref, $input, $substitutions, $ifarray) = @_;
	template_error("Invalid Subroutine declaration for [$input]!") if $input !~ m|^(\w+)\s*\(([\s\$\w\,]*)\)\s*$|;
	my $subname = $1;
	my $subarg = $2;
	if (defined $substitutions->{"_subs"}->{$subname}) {
		while (my $line = shift(@{$arrayref})) {
			return $substitutions if $line =~ m|^\s*<#\s*end\s*sub\s*#>\s*$|i;
		}
		return $substitutions;
	}
	$subarg =~ s/\s//g;
	my @subarg = split(/,/, $subarg);
	$substitutions->{"_subs"}->{$subname}->{args} = \@subarg;
	while (my $line = shift(@{$arrayref})) {
		return $substitutions if $line =~ m|^\s*<#\s*end\s*sub\s*#>\s*$|i;
		push(@{ $substitutions->{"_subs"}->{$subname}->{code} }, $line);
	}
	template_error("Subroutine [$subname] not ended with &lt;#end sub#&gt; tag!");
}

sub process_math {
	my ($operation, $substitutions) = @_;
	my $text = "";
	my ($value1, $op, $value2) = split(/\s+/, $operation);
	$value1 = $1 if $value1 =~ m|^"(.*)"$|;
	$value2 = $1 if $value2 =~ m|^"(.*)"$|;
	my $value1_temp = substitutions($value1, $substitutions);
	$value2 = substitutions($value2, $substitutions);
	$value1_temp += $value2 if $op eq "+=";
	$value1_temp -= $value2 if $op eq "-=";
	$value1_temp *= $value2 if $op eq "*=";
	$value1_temp /= $value2 if $value2 != 0 && $op eq "/=";
	$value1_temp %= $value2 if $value2 != 0 && $op eq "%=";
	$substitutions->{$1}->{$2} = $value1_temp if $value1 =~ m|^\$(\w+)->\{(\w+)\}$|;
	$substitutions->{_}->{$1} = $value1_temp if $value1 =~ m|^\$(\w+)$|;
	return $substitutions;
}

sub process_define_array {
	my ($arrayname, $fieldnames, $substitutions, $arrayref) = @_;
	if (defined($substitutions->{$arrayname})) {
		$substitutions->{$arrayname} = undef;
	}
	$fieldnames =~ s/\s//g;
	my @fieldnames = split(/,/, $fieldnames);
	undef my @array;
	&template_error("Array \@$arrayname does not have any field names defined!") if scalar(@fieldnames) < 1;
	while (my $line = shift(@{$arrayref})) {
		if ($line =~ m|^\s*<#/define#>\s*$|i) {
			$substitutions->{$arrayname} = \@array;
			return $substitutions;
		}
		if ($line =~ m|^\s*<#define|) {
			template_error("Cannot nest definitions in defining \@$arrayname!");
		}
		if ($line =~ m|^\s*<#if([^>]+)#>\s*$|i || $line =~ m|^\s*<#else#>\s*$| || $line =~ m|^\s*<#endif#>\s*$|i) {
			template_error("Block IF-THEN-ELSE statements not permitted within array definitions, in defining \@$arrayname!");
		}
		if ($line =~ m|^\s*<#foreach\s*|i || $line =~ m|^\s*<#endloop#>\s*$|i) {
			template_error("FOREACH statements not permitted within array definitions, in defining \@$arrayname!");
		}
		if ($line =~ m|^\s*<#for\s*|i || $line =~ m|^\s*<#endfor#>\s*$|i) {
			template_error("FOR statements not permitted within array definitions, in defining \@$arrayname!");
		}
		chomp $line;
		my @linesplits = split(/\t/, $line);
		undef my $hashref;
		if ($linesplits[0] =~ m|^\s*<#if\s+(.*?)#>\s*$|i) {
			next if ! is_true($1, $substitutions);
			shift (@linesplits);
		}
		foreach my $key (@fieldnames) {
			$hashref->{$key} = &process_line(shift(@linesplits), $substitutions);
			$hashref->{$key} =~ s/\s+$//;
		}
		if (defined($hashref->{'_index'})) {
			&template_error("Invalid index $hashref->{_index} for array definition \@$arrayname!") if $hashref->{_index} < 1;
			$array[ $hashref->{'_index'} - 1 ] = $hashref;
		} else {
			push (@array, $hashref);
		}
	}
	template_error("Array \@$arrayname does not have an ending tag for the definition!");
}

sub process_for {
	my ($arrayref, $variable, $lower, $upper, $step, $substitutions, $ifarray) = @_;
	$lower = 0 + substitutions($lower, $substitutions, undef, $ifarray);
	$upper = 0 + substitutions($upper, $substitutions, undef, $ifarray);
	$step = 0 + substitutions($step, $substitutions, undef, $ifarray);
	my $nest_counter = 0;
	my @array = ();
	while (my $line = shift @{ $arrayref }) {
		if ($line =~ m|^\s*<#for (.*)#>\s*$|i) {
			$nest_counter += 1;
			push @array, $line;
		} elsif ($nest_counter > 0 && $line =~ m|^\s*<#endfor#>\s*$|i) {
			$nest_counter -= 1;
			push @array, $line;
		} elsif ($line =~ m|^\s*<#endfor#>\s*$|i) {
			template_error("Step cannot be zero") if $step == 0;
			my @tarr = ();
			if ($upper >= $lower) {
				$step = -1*$step if $step < 0;
				for (my $i = $lower; $i <= $upper; $i += $step) {
					push @tarr, $i;
				}
			} else {
				$step = -1*$step if $step < 0;
				for (my $i = $upper; $i >= $lower; $i += $step) {
					push @tarr, $i;
				}
			}
			return "" if scalar(@tarr) == 0;
			my $text = "";
			my $temp_subst = $substitutions;
			my $counter = 1;
			foreach my $i (@tarr) {
				my @array_copy = @array;
				$temp_subst->{"_"}->{$variable} = $i;
				$temp_subst->{$variable}->{value} = $i;
				$temp_subst->{$variable}->{_internal_counter} = $counter - 1;
				$temp_subst->{$variable}->{'_iteration'} = $counter;
				$temp_subst->{$variable}->{'_iteration_minus1'} = ($counter - 1);
				$temp_subst->{$variable}->{'_is_last_element'} = $counter == scalar(@tarr) ? 1 : 0;
				$temp_subst->{$variable}->{'_is_first_element'} = $counter == 1 ? 1 : 0;
				$counter++;
				my $textback = (parse_handle(\@array_copy, $temp_subst, undef, $ifarray))[0];
				$textback = $` if $textback =~ m|\r?<#\r?next\r?#>\r?|i;
				if ($textback =~ m|\r?<#\r?last\r?#>\r?|i) {
					return join("", $text, $`);
				}
				$text .= $textback;				
			}
			return $text;
		} else {
			push (@array, $line);
		}
	}
	template_error("Unterminated FOR (\$$variable) loop in template file... make sure FOR statements are closed with &lt;#endfor#&gt;!");
}

sub process_foreach {
	my ($arrayref, $variable, $array, $substitutions, $ifarray) = @_;
	my $text = "";
	my @array = ();
	my $nest_counter = 0;
	while (my $line = shift(@{$arrayref})) {
		if ($line =~ m|^\s*<#foreach(.*)#>\s*$|i) {
			$nest_counter += 1;
			push (@array, $line);
		} elsif ($nest_counter > 0 && $line =~ m|^\s*<#endloop#>\s*$|i) {
			$nest_counter -= 1;
			push (@array, $line);
		} elsif ($line =~ m|^\s*<#endloop#>\s*$|i) {
			my @temp_arr = @{ $substitutions->{$array} };
			my $counter = 1;
			my $internal_counter = 0;
			foreach my $key (@temp_arr) {
				$internal_counter += 1;
				my $temp_subst = $substitutions;
				$temp_subst->{$variable} = $key;
				$temp_subst->{$variable}->{'_internal_counter'} = $internal_counter;
				$temp_subst->{$variable}->{'_iteration'} = $counter;
				$temp_subst->{$variable}->{'_iteration_minus1'} = $counter-1;
				$temp_subst->{$variable}->{'_is_last_element'} = $internal_counter == scalar(@temp_arr) ? 1 : 0;
				$temp_subst->{$variable}->{'_is_first_element'} = $internal_counter == 1 ? 1 : 0;
				$temp_subst->{$variable}->{'_previous_element'} = $counter == 1 ? $temp_arr[0] : $temp_arr[$counter-2];
				$temp_subst->{$variable}->{'_next_element'} = $counter == scalar(@temp_arr) ? $temp_arr[$#temp_arr] : $temp_arr[$counter];
				my @array_copy = @array;
				my $textback = (parse_handle(\@array_copy, $temp_subst, undef, $ifarray))[0];
				$textback = $` if $textback =~ m|\r?<#\r?next\r?#>\r?|i;
				if ($textback =~ m|\r?<#\r?last\r?#>\r?|i) {
					$textback = $`;
					return join("", $text, $textback);
				}
				if ($textback =~ m|<#skip\s*iteration#>|i) {
					while ($textback =~ m|<#skip\s*iteration#>|i) {
						$textback = join("", $`, $');
					}
				} else {
					$counter += 1;
				}
				$text .= $textback;
			}
			return $text;
		} else {
			push (@array, $line);
		}
	}
	template_error("Unterminated FOREACH (\$$variable \@$array) loop in template file... make sure FOREACH statements are closed with &lt;#endloop#&gt;!");
}

sub process_line {
	my ($line, $substitutions, $simple, $ifarray) = @_;
	if ($line =~ /[<\{]#/) {
		if ($line =~ m|^(.*)[<\{]#if (.*?)#[>\}](.*)(\s*)$|) {
			my ($before, $condition, $after) = ($1, $2, join("", $3, $4));
			my ($iftrue, $ifelse, $anyway);
			if ($after =~ m|(.*)[<\{]#else#[>\}](.*)[<\{]#endif#[>\}](.*)(\s*)$|) {
				($iftrue, $ifelse, $anyway) = ($1, $2, join("", $3, $4));
			} elsif ($after =~ m|(.*)[<\{]#endif#[>\}](.*)(\s*)$|) {
				($iftrue, $ifelse, $anyway) = ($1, "", join("", $2, $3));
			} else {
				($iftrue, $ifelse, $anyway) = ($after, "", "");
			}
			if (is_true($condition, $substitutions)) {
				return substitutions(join("", $before, $iftrue, $anyway), $substitutions, $simple, $ifarray);
			} else {
				return substitutions(join("", $before, $ifelse, $anyway), $substitutions, $simple, $ifarray);
			}
		} elsif ($line =~ m|^\s*<#include file[= ]+"(.*)"\s*([\w, ]*)#>\s*$|) {
			my $filename = substitutions($1, $substitutions, undef, $ifarray);
			return "" if $PARAMS->{cache}->{notfound}->{$filename};
			my $px = $2; $px =~ s/\s+/,/g; my @params = split(/,/, $px);
			undef my $hashref;
			foreach my $x (@params) {
				$hashref->{$x} = 1;
			}
			my $text = "";
			my $flag = 0;
			if (open(FILE, "< $filename")) {
				while (<FILE>) {
					s%<IMG SRC="icons/%<IMG SRC="$DCONF->{html_url}/$DCONF->{icon_dir}/%gi;
					s%<IMG SRC="$DCONF->{icon_dir}/%<IMG SRC="$DCONF->{html_url}/$DCONF->{icon_dir}/%gi;
					if ($hashref->{'BODY'} && m|^<BODY|) {
						$flag = 1;
					} elsif ($hashref->{'BODY'} && $flag) {
						$text .= $_;
					} elsif ($hashref->{'BODY'} && !$flag) {
						next;
					} else {
						$text .= $_;
					}
				}
				close (FILE);
			} else {
				template_error("Failed to open required file to generate this portion of the display. $!") if $hashref->{'FATAL'};
				$PARAMS->{cache}->{notfound}->{$filename} = 1;
			}
			return $text;
		} elsif ($line =~ m|^\s*<#\s*insert\s*(once)?\s*part\s*\((.*?)\)\s*"([^"]+)"\s*\(?(1?)\)?\s*#>\s*$|i) {
			my ($once, $topic, $part, $flag) = ($1, $2, $3, $4);
			$topic = substitutions($topic, $substitutions, $simple, $ifarray);
			$substitutions->{'_internal'}->{'depth'} += 1;
			template_error("Recursive Part Call Error: Template depth limited to 50 levels for safety reasons.  Expect a recursive call from a template to itself.  Part called: [$part]; line $line") if $substitutions->{'_internal'}->{'depth'} > 50;
			if ($once ne "" && defined $PARAMS->{"once,$topic,$part"}) {
				performance_string("^ Skin $topic/$part from cache (length " . length($PARAMS->{"once,$topic,$part"}) . " chars)");
				$substitutions->{'_internal'}->{'depth'} -= 1;
				return $PARAMS->{"once,$topic,$part"};
			} else {
				performance_string("^ Interpreting $topic/$part via insert part");
			}
			my $l = part_of_skin($topic, $part, $substitutions, 0, $flag);
			$substitutions->{'_internal'}->{'depth'} -= 1;
			$PARAMS->{"once,$topic,$part"} = $l if $once ne "";
			return $l;
		} elsif ($line =~ m|^\s*<#\s*insert\s*template\s*"([^"]+)"\s*#>\s*$|i) {
			my $template = $1;
			$substitutions->{'_internal'}->{'depth'} += 1;
			template_error("Recursive Template Call Error: Template depth limited to 50 levels for safety reasons.  Expect a recursive call from a template to itself.  Last template: [$template]") if $substitutions->{'_internal'}->{'depth'} > 50;
			my $text = templ_int($template, $substitutions);
			$substitutions->{'_internal'}->{'depth'} -= 1;
			return $text;
		} elsif ($line =~ m|^\s*<#form variables#>\s*$|) {
			my $text = "";
			foreach my $key (keys(%{$substitutions->{FORMref}})) {
				$text .= "<INPUT TYPE=HIDDEN NAME=\"$key\" VALUE=\"$substitutions->{'FORMref'}->{$key}\">\n" if $key !~ m|^_|;
			}
			return $text;
		} elsif ($line =~ m|^\s*<#form variables\(([\w,]+)\)#>\s*$|) {
			my $text = "";
			foreach my $key (split(/,/, $1)) {
				$text .= "<INPUT TYPE=HIDDEN NAME=\"$key\" VALUE=\"$substitutions->{'FORMref'}->{$key}\">\n";
			}
			return $text;
		} else {
			return substitutions($line, $substitutions, $simple, $ifarray);
		}
	} else {
		return substitutions($line, $substitutions, $simple, $ifarray);
	}
}

sub form_escape {
	my ($input) = @_;
	$input =~ s/&/&amp;/g;
	$input =~ s/</&lt;/g;
	$input =~ s/>/&gt;/g;
	$input =~ s/"/&quot;/g;
	$input =~ s/'/&#39;/g;
	return $input;
}

sub process_sub {
	my ($subname, $subargs, $substitutions, $ifarray) = @_;
	$subargs .= ",";
	$subargs =~ s/\r//g;
	template_error("Call to undefined subroutine [$subname]!") if ! defined $substitutions->{"_subs"}->{$subname};
	undef my @sa;
	while ($subargs =~ m|"([^"]*)"\s*,|) {
		$subargs = $';
		push (@sa, substitutions($1, $substitutions, undef, $ifarray));
	}
	my $temp_subst = $substitutions;
	foreach my $y (@{ $substitutions->{"_subs"}->{$subname}->{args} }) {
		my $z = $y;
		$z =~ s/^\$//;
		$temp_subst->{"_"}->{$z} = substitutions(shift(@sa), $temp_subst, undef, $ifarray);
	}
	my @temp_code = @{ $substitutions->{"_subs"}->{$subname}->{code} };
	my $text = (parse_handle(\@temp_code, $temp_subst, undef, $ifarray))[0];
	return $text;
}

sub complex_replace {
	my ($substitutions, $v1, $v2, $v3, $match) = @_;
	return $match if $substitutions->{_avoid}->{$v1};
	return $match if $substitutions->{_avoid}->{$v2};
	return $substitutions->{$v1}->{$substitutions->{$v2}->{$v3}};
}

sub template_js_prepare {
	my ($text) = @_;
	my @u = split(/&#?\w+;|<.*?>/, $text);
	my @v = ();
	push @v, $1 while ($text =~ m%(&#?\w+;|<.*?>)%g);
	$text = "";
	while (scalar(@u) + scalar(@v)) {
		my $u = shift @u;
		my $v = shift @v;
		$u =~ s/\s+/ /g;
		$u =~ s/(['"<>\\])/join("", "&#", ord($1), ";")/ge;
		$text .= join("", $u, $v);
	}
	foreach my $fc ("'") {
		my $fcX = ord($fc);
		my $str = join("", "&#", $fcX, ";");
		$text =~ s/$str//g;
		my $fcqm = quotemeta($fc);
		$text =~ s/$fcqm//g;
	}
	return $text;
}

sub wordwrapper {
	my ($chars, $text, $skip_conv) = @_;
	my $txt = $skip_conv ? $text : char_convert($text, 1);
	my @Z = split(/\n\n+/, $txt);
	my @t2out = ();
	if ($skip_conv) {
		my @t2in = ();
		foreach my $z (@Z) {
			$z =~ s/<!--.*?-->//g;
			my @u = split(/<.*?>/i, $z);
			my @v = (); while ($z =~ /(<.*?>)/gi) { push @v, $1; };
			push @t2in, { u => \@u, v => \@v };
		}
		foreach my $z (@t2in) {
			my @u = @{ $z->{u} };
			my @v = @{ $z->{v} };
			while (scalar @u + scalar @v) {
				my $u = shift @u;
				my $v = shift @v;
				$u =~ s/\s+/ /g; $u = trim($u);		
				push @t2out, _wrapper($u, $chars);
				push @t2out, $v if length($v);
			}
			push @t2out, "";
		}
		@t2out = grep { /\S/ } @t2out;
	} else {
		foreach my $z (@Z) {
			$z =~ s/\n/<br>/g;
			$z = trim($z);
			$z =~ s/<br>/\n/gi;
			$z =~ s/<!--.*?-->//g;
			$z = remove_html($z);
			push @t2out, _wrapper($z, $chars);
			push @t2out, "";
		}
	}
	return join("\n", @t2out);	
}

sub _wrapper {
	my ($text, $chars) = @_;
	my @aout = ();
	while ($text ne "") {
		if ($text =~ /^(.{1,$chars})(\s|$)/) {
			push @aout, $1; $text = $';
		} elsif ($text =~ /^\n/) {
			push @aout, ""; $text = $';
		} elsif ($text =~ /^(.{$chars,})(\s|$)/) {
			push @aout, $1; $text = $';
		}
	}
	return @aout;
}

sub safe_minus_one {
	my ($num) = @_;
	my $x = int($num - 1);
	template_error("Invalid Array Index: $num is less than one") if $x < 0;
	return $x;
}

sub substitutions {
	my ($text, $substitutions, $simple, $ifarray) = @_;
	$text =~ s/\r//g;
	my $K = $text =~ s%(\$|\{|\}|\\|<#|#>)%\r$1\r%g;
	if ($K) {
		$text =~ s%\r\$\r0%$0%g;
		$text =~ s%\r<#\r\s*&\s*(\w+)\s*\(([^\)]*)\s*\)\s*\r#>\r%process_sub($1, $2, $substitutions, $ifarray)%ieg;
		if ($simple) {
			if ($text =~ /\$/) {
				$text =~ s%\r\$\r(\w+)/%if(!$substitutions->{_avoid}->{$1}){single_replace($1,$substitutions)."/"}else{$&."/"}%ge;
				$text =~ s%\r\$\rL\r(->)?\{\r(\w+)\r\}\r\[(.*?)\]%read_language()->{$2}->[substitutions($3, $substitutions, $simple, $ifarray)]%ge;
				$text =~ s%\r\$\rL\r(->)?\{\r(\w+)\r\}\r%read_language()->{$2}%ge;
				$text =~ s%\r\$\rGLOBAL_OPTIONS\r(?:->)?\{\r(\w+)\r\}\r%$GLOBAL_OPTIONS->{$1}%g;
				$text =~ s%\r\$\rGLOBAL_OPTIONS\r(?:->)?\{\r\r\$\r(\w+)\r\}\r%$GLOBAL_OPTIONS->{single_replace($1,$substitutions)}%ge;
				$text =~ s%\r\$\r(\w+)->\r\{\r(\w+)\r\}\r%if(!$substitutions->{_avoid}->{$1}){$substitutions->{$1}->{$2}}else{$&}%ge;
				$text =~ s%\r\$\rENV\r\{\r(\w+)\r\}\r%$ENV{$1}%g;
				$text =~ s%\r\$\r(\w+)%if(!$substitutions->{_avoid}->{$1}){single_replace($1,$substitutions)}else{$&}%ge;
			}
			$text =~ s%\r\\\ru(\w?)%ucase($1)%ge;
			$text =~ s%\r\\\rl(\w?)%lcase($1)%ge;
			if ($text =~ /#/) {
				$text =~ s%\r<#\rform\s*escape\s*"(.*?)"\s*\r#>\r%form_escape($1)%iges;
				$text =~ s%\r<#\rescape "(.*?)"\r#>\r%escape($1)%gies;
				$text =~ s%\r<#\runescape "(.*?)"\r#>\r%unescape($1)%gie;
				$text =~ s%\r<#\rremove\s+html\s*"(.*?)"\r#>\r%remove_html($1)%gies;
				$text =~ s%\r<#\rjavascript[ _]prepare\s*"(.*?)"\s*\r#>\r%template_js_prepare($1)%gies;
				$text =~ s%\r<#\rmaxchar (\d+)/(.*?) "(.*?)"\r#>\r%maxcharcut($1, $2, $3)%gies;
				$text =~ s%\r<#\rmaxchar (\d+) "(.*?)"\r#>\r%substrnohtml($2, $1)%gies;
				$text =~ s%\r<#\rpart\s*"(.*?)"\s*\((\d*)\)\s*exists\s*\r#>\r%part_of_skin($2, $1, $substitutions, 1)%gies;	
				$text =~ s%\r<#\rcss\s*size\s*\(([\-\+\d]+)\)\s*\r#>\r%template_css_size($1)%gies;	
				while ($text =~ m|\r<#\rpick\s*(\w+)?\s*(.*?)\s*from\s*\@(\w+)\s*\((.*?)\)\s*\r#>\r|i) {
					my ($pick, $item_no, $arrayname, $item_choice, $before, $after, $match) = ($1, $2, $3, $4, $`, $', $&);
					if (ref $substitutions->{$arrayname} ne "ARRAY") {
						$match =~ s/</&lt;/g; $match =~ s/>/&gt;/g;
						&template_error("Use of undefined array \@$arrayname in statement [$match]!");
					}
					$item_choice =~ s/\s//g;
					my @item_choose = split(/,/, $item_choice);
					if ($item_choice eq "*") {
						if (scalar(@{ $substitutions->{$arrayname} }) == 0) {
							$text = join("", $before, $after);
							next;
						} else {
							@item_choose = (1 .. scalar(@{ $substitutions->{$arrayname} }));
						}
					}
					if (scalar(@item_choose) == 0) {
						template_error("Invalid selection index '$item_choice' in [$match]!");
					}
					my $arrindex = ($item_no - 1) % scalar(@item_choose);
					&template_error("Use of undefined index [ $item_choose[$arrindex] ] for array index $arrindex in item choices ($item_choice)!") unless (@{ $substitutions->{$arrayname} })[ $item_choose[$arrindex] - 1 ];
					$text = join("", $before, ( (@{ $substitutions->{$arrayname} })[ $item_choose[$arrindex] - 1 ])->{$pick}, $after);
				}
				while ($text =~ m|\r<#\rdate\s+([\d\.]*)\s+format\s+"(.*?)"\s*\r#>\r|i) {
					my ($datenum, $format, $before, $after) = ($1, $2, $`, $');
					if ($datenum != 0) {
						$text = join("", $before, get_date_time($format, int($datenum), 0), $after);
					} else {
						$text = join("", $before, "&nbsp;", $after);
					}
				}
			}
		} else {
			if ($text =~ /\$/) {
				$text =~ s%\r\$\r(\w+)/%if(!$substitutions->{_avoid}->{$1}){single_replace($1,$substitutions)."/"}else{$&."/"}%ge;
				$text =~ s%\r\$\rL\r(->)?\{\r(\w+)\r\}\r\[(.*?)\]%read_language()->{$2}->[substitutions($3, $substitutions, $simple, $ifarray)]%ge;
				$text =~ s%\r\$\rL\r(->)?\{\r(\w+)\r\}\r%read_language()->{$2}%ge;
				$text =~ s%\r\$\rENV\r\{\r(\w+)\r\}\r%$ENV{$1}%g;
				$text =~ s%\r\$\rGLOBAL_OPTIONS\r(?:->)?\{\r(\w+)\r\}\r%$GLOBAL_OPTIONS->{$1}%g;
				$text =~ s%\r\$\rGLOBAL_OPTIONS\r(?:->)?\{\r\r\$\r(\w+)\r\}\r%$GLOBAL_OPTIONS->{single_replace($1,$substitutions)}%ge;
				if ($text =~ /\$/) {
					$text =~ s%\r\$\rsubstitutions->\r\{\r(\w+)\r\}\r%$substitutions->{$1}%g;
					$text =~ s%\r\$\r(\w+)\[\r\$\r(\w+)->\r\{\r(\w+)\r\}\r\]->\r\{\r(\w+)\r\}\r%$substitutions->{$1}->[-1+($substitutions->{$2}->{$3})]->{$4}%ge;
					$text =~ s%\r\$\r(\w+)\[\r\$\r(\w+)->\r\{\r(\w+)\r\}\r\]%$substitutions->{$1}->[safe_minus_one($substitutions->{$2}->{$3})]%ge;
					$text =~ s%\r\$\r(\w+)\[(\d+)\]->\r\{\r(\w+)\r\}\r%$substitutions->{$1}[safe_minus_one($2)]->{$3}%g;
					$text =~ s%\r\$\r(\w+)->\r\{\r(\w+)\r\}\r->\r\{\r(\w+)\r\}\r%$substitutions->{$1}->{$2}->{$3}%g;
					$text =~ s%\r\$\r(\w+)->\r\{\r(\w+)\r\}\r->\r\{\r\r\$\r(\w+)\r\}\r%$substitutions->{$1}->{$2}->{single_replace($3, $substitutions)}%ge;
					$text =~ s%\r\$\r(\w+)->\r\{\r\r\$\r(\w+)->\r\{\r(\w+)\r\}\r\r\}\r%complex_replace($substitutions, $1, $2, $3, $&)%ge;
					$text =~ s%\r\$\r(\w+)->\r\{\r\r\$\r(\w+)\r\}\r%$substitutions->{$1}->{single_replace($2,$substitutions)}%ge;
					$text =~ s%\r\$\r(\w+)->\r\{\r(\w+)\r\}\r%if(!$substitutions->{_avoid}->{$1}){$substitutions->{$1}->{$2}}else{$&}%ge;
					$text =~ s%\r\$\r(\w+)\[(.*?)\]->\r\{\r(\w+)\r\}\r%$substitutions->{$1}[safe_minus_one(substitutions($2, $substitutions, $simple, $ifarray))]->{$3}%ge;
					$text =~ s%\r\$\r(\w+)\[(.*?)\]%$substitutions->{$1}[safe_minus_one(substitutions($2, $substitutions, $simple, $ifarray))]%ge;
					$text =~ s%\r\$\r(\w+)%if(!$substitutions->{_avoid}->{$1}){single_replace($1,$substitutions)}else{$&}%ge;
					$text =~ s%\r\$\r#(\w+)%ref $substitutions->{$1} eq "ARRAY" ? scalar(@{$substitutions->{$1}}) : 0%ge;
					$text =~ s%\r\$\r\@(\w+)->\r\{\r(\w+):\s*(\w+)\s*=\s*"(.*?)"\r\}\r%element_array_grep($substitutions, $3, $4, $1, $2, $&)%ge;
				}
			}
			$text =~ s%\r\\\ru(\w?)%ucase($1)%ge;
			$text =~ s%\r\\\rl(\w?)%lcase($1)%ge;
			if ($text =~ /#/) {
				$text =~ s%\r<#\rform\s*escape\s*"(.*?)"\s*\r#>\r%form_escape($1)%iges;
				$text =~ s%\r<#\rrepeated (\d+) "(.*?)"\r#>\r%$2x$1%gie;
				$text =~ s%\r<#\rescape "(.*?)"\r#>\r%escape($1)%gies;
				$text =~ s%\r<#\runescape "(.*?)"\r#>\r%unescape($1)%gie;
				$text =~ s%\r<#\rremove\s+html\s*"(.*?)"\r#>\r%remove_html($1)%gies;
				$text =~ s%\r<#\rjavascript[ _]prepare\s*"(.*?)"\s*\r#>\r%template_js_prepare($1)%gies;
				$text =~ s%\r<#\rmaxchar (\d+)/(.*?) "(.*?)"\r#>\r%maxcharcut($1, $2, $3)%gies;
				$text =~ s%\r<#\rmaxchar (\d+) "(.*?)"\r#>\r%substrnohtml($2, $1)%gies;
				$text =~ s%\r<#\rpart\s*"(.*?)"\s*\((\d*)\)\s*exists\s*\r#>\r%part_of_skin($2, $1, $substitutions, 1)%gies;	
				$text =~ s%\r<#\rcss\s*size\s*\(([\-\+\d]+)\)\s*\r#>\r%template_css_size($1)%gies;	
				if ($text =~ /#/) {
					$text =~ s%\r<#\rprogram\s*sub\s*"(.*?)"\s*&(\w+)\s*\((.*?)\)\s*\r#>\r%program_sub($1, $2, $3)%gies;
					$text =~ s%\r<#\rwordwrap (\d+) "((.|\n)*?)"\r#>\r%wordwrapper($1, $2, 0)%gei;
					$text =~ s%\r<#\rwordwrapX (\d+) "((.|\n)*?)"\r#>\r%wordwrapper($1, $2, 1)%gei;
					$text =~ s%\r<#\rrtpad (\d+) "(.*?)"\r#>\r%substr(join("", $2, " " x $1), 0, $1)%gei;
					$text =~ s%\r<#\rstrlength "(.*?)"\r#>\r%length($1)%geis;
					$text =~ s%\r<#\rlength "(.*?)"\r#>\r%nohtmllength($1)%gei;
					while ($text =~ m|\r<#\r\s*gmtoffset\s*\r#>\r|i) {
						my $time = time;
						my ($s1, $m1, $hr1, $mo1, $dy1, $yr1) = localtime($time);
						my ($s2, $m2, $hr2, $mo2, $dy2, $yr2) = gmtime($time);
						my $offset = ($s1 - $s2);
						$offset += 60 * ($m1 - $m2);
						$offset += 60 * 60 * ($hr1 - $hr2);
						$offset += 24 * 60 * 60 if ($mo1 > $mo2);
						$offset -= 24 * 60 * 60 if ($mo1 < $mo2);
						$offset += 24 * 60 * 60 if ($mo1 == $mo2 && $dy1 > $dy2);
						$offset -= 24 * 60 * 60 if ($mo1 == $mo2 && $dy1 < $dy2);
						$text = join("", $`, $offset, $');
					}
					while ($text =~ m|\r<#\rpick\s*(\w+)?\s*(.*?)\s*from\s*\@(\w+)\s*\((.*?)\)\s*\r#>\r|i) {
						my ($pick, $item_no, $arrayname, $item_choice, $before, $after, $match) = ($1, $2, $3, $4, $`, $', $&);
						if (ref $substitutions->{$arrayname} ne "ARRAY") {
							$match =~ s/</&lt;/g; $match =~ s/>/&gt;/g;
							&template_error("Use of undefined array \@$arrayname in statement [$match]!");
						}
						$item_choice =~ s/\s//g;
						my @item_choose = split(/,/, $item_choice);
						if ($item_choice eq "*") {
							if (scalar(@{ $substitutions->{$arrayname} }) == 0) {
								$text = join("", $before, $after);
								next;
							} else {
								@item_choose = (1 .. scalar(@{ $substitutions->{$arrayname} }));
							}
						}
						if (scalar(@item_choose) == 0) {
							template_error("Invalid selection index '$item_choice' in [$match]!");
						}
						my $arrindex = ($item_no - 1) % scalar(@item_choose);
						&template_error("Use of undefined index [ $item_choose[$arrindex] ] for array index $arrindex in item choices ($item_choice)!") unless (@{ $substitutions->{$arrayname} })[ $item_choose[$arrindex] - 1 ];
						$text = join("", $before, ( (@{ $substitutions->{$arrayname} })[ $item_choose[$arrindex] - 1 ])->{$pick}, $after);
					}
					while ($text =~ m|\r<#\rgrep\s*"?(.*?)"?\s*=?\s*"?(\w+)"?\s*\(?\s*\@(\w+)\s*\)?\s*=?>?\s*"?(\w+)"?\s*\r#>\r|i) {
						$text = join("", $`, element_array_grep($substitutions, $1, $2, $3, $4, $&), $');
					}	
					if ($text =~ m|\r<#\r.*current\s*time.*\r#>\r|i) {
						my $time = time;
						$text =~ s%\r<#\rcurrent\s*time\r#>\r%$time%ig;
					}
					while ($text =~ m|\r<#\rdate\s+([\d\.]*)\s+format\s+"(.*?)"\s*\r#>\r|i) {
						my ($datenum, $format, $before, $after) = ($1, $2, $`, $');
						if ($datenum != 0) {
							$text = join("", $before, get_date_time($format, int($datenum), 0), $after);
						} else {
							$text = join("", $before, "&nbsp;", $after);
						}
					}
					$text =~ s/\r<#\rskip\s*iteration\r#>\r/<#skip iteration#>/gi;
					$text =~ s/\r<#\rnext\r#>\r/<#next#>/gi;
				}
			}
		}
	}
	if ($text =~ m|^\s*\r|) {
		my $a = $'; $a =~ s/^\s+//; $text = join("", "\r", $a);
	} else {
		$text =~ s/^\s+//;
	}
	if ($text =~ /\r/) {
		$text =~ s/\r\{\r\|\r\}\r//g;
		$text =~ s/\r\{\r(\|+)\r\}\r/" " x (length($1)-1)/ge;
		$text =~ s%\r\{\r\|tab\|\r\}\r%\t%ig;
		$text =~ s/\r([\{\}\\])\r/$1/g;
		$text =~ s/\r<#\r(\w+)\r#>\r/<#$1#>/g;
	}
	if ($text =~ /\r/ && ($text =~ m|\r\$\r| || $text =~ m|\r<#\r| || $text =~ m|\r#>\r|)) {
		my $msg = "<p>Unmatched command code problem</p>\n";
		$msg .= "<p>Command code used: <u>";
		my $m = $&; $m =~ s/\r//g; $m =~ s/([^\w\s])/join("", "&#", ord($1), ";")/ge;
		$msg .= "$m</u></p>\n";
		$msg .= "<p>Used in line:</p><p>";
		my $l = $text; $l =~ s/([^\w\s])/join("", "&#", ord($1), ";")/ge; $l =~ s/\r/<!--\\r-->/g; 
		$msg .= "$l</p>\n";
		if ($text =~ m|\r<#\r\s*(\S+)|) {
			$msg .= "<p>Perhaps the command &lt;#$1 ... #&gt; does not exist?</p>";
		}
		template_error($msg);
	}
	if ($GLOBAL_OPTIONS->{'version_verify'}) {
		my $str1 = "<is><dfoufs><gpou dpmps=#gg0000 tjaf=4>Uijt tpguxbsf jt cfjoh vtfe jo wjpmbujpo pg jut mjdfotf bhsffnfou.</gpou></dfoufs><is>"; $str1 =~ tr/B-ZAb-za/A-Za-z/;
		my $str2 = "<is><dfoufs><gpou dpmps=#gg0000 tjaf=4>Qmfbtf fodpvsbhf uif cpbse benjojtusbups up qspqfsmz sfhjtufs uijt tpguxbsf.</gpou></dfoufs><is>"; $str2 =~ tr/B-ZAb-za/A-Za-z/;
		$text =~ s/<body([^>]*)>/<body$1>$str1/ig;
		$text =~ s/<\/body([^>]*)>/<\/body$1>$str2/ig;
	}
	$text =~ s/\s*\\\s*$//;
	return $text;
}

sub program_sub {
	my ($requirement, $subroutine, $arguments) = @_;
	dreq($requirement) if $requirement ne "";
	my @arg = split(/,/, $arguments);
	foreach my $argu (@arg) {
		$argu = trim($argu);
		$argu = $1 if $argu =~ /"(.*)"/;
	}
	my $j = \&{ $subroutine };
	my $K = eval '&{ $j }(@arg);';
	return $K;
}

sub element_array_grep {
	my ($substitutions, $element, $pattern, $arrayname, $return_key, $match) = @_;
	return "" if $pattern eq "";
	if (ref $substitutions->{$arrayname} ne "ARRAY") {
		$match =~ s/</&lt;/g; $match =~ s/>/&gt;/g;
		template_error("Use of undefined array \@$arrayname in grep statement [$match]");
	}
	my $x = (grep($_->{$element} eq $pattern, @{ $substitutions->{$arrayname} }))[0];
	return "" if ref $x ne "HASH";
	return $x->{$return_key};
}

sub template_css_size {
	my ($sized) = @_;
	my $s = 0;
	if ($sized =~ m|^\+|) {	
		$s = $GLOBAL_OPTIONS->{COLOR_size} + $';
	} elsif ($sized =~ m|^\-|) {
		$s = $GLOBAL_OPTIONS->{COLOR_size} - $';
	} else {
		$s = $sized;
	}
	$s = 1 if $s < 1;
	$s = 7 if $s > 7;
	$s = int($s);
	my @u = ('', '8pt', '10pt', '12pt', '14pt', '18pt', '24pt', '36pt');
	return $u[$s];	
}

sub nohtmllength {
	my ($text) = @_;
	return substrnohtml($text, length($text), 1);	
}

sub maxcharcut {
	my ($chars, $cutreplace, $text) = @_;
	$text =~ s%<!-/?\*\d+-!>%%g;
	$text =~ s%<!-\*\d+:\S*-!>%%g;
	return $text if length($text) <= $chars;
	my $clen = $chars - length($cutreplace);	
	return $text if $clen <= 0;
	return join("", substrnohtml($text, $clen), $cutreplace);	
}

sub substrnohtml {
	my ($text, $leng, $arg) = @_;
	return $text if length($text) <= $leng && ! $arg;
	return substr($text, 0, $leng) if ($text !~ m|&#?(\w+);| && $text !~ m|<.*>|) && ! $arg;
	return length($text) if ($text !~ m|&#?(\w+);| && $text !~ m|<.*>|) && $arg;
	my @u = split(/&#?\w+;|<.*?>/, $text);
	my @v = ();
	push @v, $1 while ($text =~ m%(&#?\w+;|<.*?>)%g);
	$text = "";
	my $length_used = 0;
	while (scalar(@u) + scalar(@v)) {
		my $u = shift @u;
		my $v = shift @v;
		my $x = substr($u, 0, $leng - $length_used);
		if ($x eq $u) {
			$length_used += length($u);
			$text .= $u;
		} else {
			$length_used = $leng;
			$text .= $x;
		}
		if ($v =~ /^&#?\w+;/ && $length_used < $leng) {
			$text .= $v; $length_used += 1;
		} elsif ($v =~ /^<img/i && $length_used < $leng) {
			$text .= $v; $length_used += 1;
		} elsif ($v =~ /^<img/i) {
			# Don't add additional images here
		} elsif ($v =~ /^</) {
			$text .= $v;
		}
	}
	return $text if ! $arg;
	return $length_used;
}

sub single_replace {
	my ($text, $substitutions, $next) = @_;
#	header();
#	print "TEXT: $text<br>\n";
#	print "Defined: ", 0 + defined $substitutions->{"_"}->{$text}, "<br>\n";
#	print "Value: ", $substitutions->{"_"}->{$text}, "<br>\n" if defined $substitutions->{"_"}->{$text};
#	print "DCONF: ", $DCONF->{$text}, "<br>\n" if defined $DCONF->{$text};
#	print "PARAMS: ", $PARAMS->{$text}, "<br>\n" if defined $PARAMS->{$text};
	return $substitutions->{"_"}->{$text} if defined($substitutions->{"_"}->{$text});
	return $DCONF->{$text} if defined($DCONF->{$text});
	return $PARAMS->{$text} if defined($PARAMS->{$text});
	return undef;
}

sub ucase {
	return case_upper($_[0]);
}

sub lcase {
	return case_lower($_[0]);
}

sub is_true {
	my ($statement, $substitutions) = @_;
	my $y = "==|!=|>|<|>=|<=|eq|ne|gt|ge|lt|le|rexp=~|rexp!~|=~|!~";
	my @condx = ();
	if ($statement =~ m|&&|) {
		@condx = split(/&&/, $statement);
	} elsif ($statement =~ m#\|\|#) {
		@condx = split(/\|\|/, $statement);
	} else {
		@condx = ($statement);
	}
	my $boolean = "or";
	$boolean = "and" if $statement =~ m|&&|;
	my $xx = 0;
	foreach my $condx (@condx) {
		$condx =~ s/\s+$//;
		$condx =~ s/^\s+//;
		$condx =~ s/\s+/ /g;
		next if $condx eq "";
		if ($condx =~ m|^pro$|i) {
			return 1 if $DCONF->{pro} && $boolean eq "or";
			return 0 if !$DCONF->{pro} && $boolean eq "and";
		} elsif ($condx =~ m|^\!(\s*)pro$|i) {
			return 1 if !$DCONF->{pro} && $boolean eq "or";
			return 0 if $DCONF->{pro} && $boolean eq "and";
		} elsif ($condx =~ m%^(.+)\s+($y)\s+(.+)$%o) {
			$xx = &_is_true($1, $2, $3, $substitutions);
			return 1 if $xx && $boolean eq "or";
			return 0 if !$xx && $boolean eq "and";
		} elsif ($condx =~ m%^(.+)$%) {
			$xx = &_is_true($1, ">=", "1", $substitutions);
			return 1 if $xx && $boolean eq "or";
			return 0 if !$xx && $boolean eq "and";
		} else {
			&template_error("Template for this section is corrupt: bad &quot;if&quot; statement! [$statement] [$condx]");
		}
	}
	return 1 if $boolean eq "and";
	return 0 if $boolean eq "or";
}

sub _minimath {
	my ($value, $substitutions) = @_;
	my $y = '\-|\+|\*|\/|\%';
	my @p = split(/$y/, $value);
	template_error(join("", "Bad math expression: [$value] had  ", scalar(@p), " argument(s); 2 expected")) if scalar(@p) != 2;
	my ($pa, $pb) = (trim($p[0]), trim($p[1]));
	my $P1 = substitutions($pa, $substitutions);
	my $P2 = substitutions($pb, $substitutions);
	my $V = $1 if $value =~ /($y)/;
	my $v = 0;
	$v = $P1 + $P2 if $V eq "+";
	$v = $P1 - $P2 if $V eq "-";
	$v = $P1 * $P2 if $V eq "*";
	if ($V eq "%" || $V eq "/") {
		if ($P2 == 0) {
			$v = 0;
		} else {
			$v = $P1 / $P2 if $V eq "/";
			$v = $P1 % $P2 if $V eq "%";
		}
	}
	return $v;
}

sub _is_true {
	my ($value1, $c, $value2, $substitutions) = @_;
	$value1 = $1 if $value1 =~ m|^"(.*)"$|;
	$value2 = $1 if $value2 =~ m|^"(.*)"$|;
	$value2 = $1 if $value2 =~ m|^match\s*\((.*?)\)\s*$|;
	if ($value1 =~ m|^\[(.*?)\]$|) {
		$value1 = substitutions($value1, $substitutions);
		$value1 = _minimath($1, $substitutions) if $value1 =~ m|^\[(.*?)\]$|;
	} else {
		$value1 = substitutions($value1, $substitutions);
	}		
	if ($value2 =~ m|^\[(.*?)\]$|) {
		$value2 = substitutions($value2, $substitutions);
		$value2 = _minimath($1, $substitutions) if $value2 =~ m|^\[(.*?)\]$|;
	} else {
		$value2 = substitutions($value2, $substitutions);
	}		
	$value1 =~ s%\s*exists:\s*"(.*?)"\s*%-f $1 ? 1 : 0%ge;
	$value1 =~ s%\s*option_defined:\s*"(.*?)"\s*%if(defined $GLOBAL_OPTIONS->{$1}) {1} else {0}%ge;
	return 0 if $c eq "==" && $value1 == 0 && $value2 == 0 && $value1 =~ m|\D|;
	return 1 if $c eq "==" && $value1 == $value2;
	return 1 if $c eq "!=" && $value1 != $value2;
	return 1 if $c eq ">=" && $value1 >= $value2;
	return 1 if $c eq "<=" && $value1 <= $value2;
	return 1 if $c eq ">"  && $value1 >  $value2;
	return 1 if $c eq "<"  && $value1 <  $value2;
	return 1 if $c eq "eq" && $value1 eq $value2;
	return 1 if $c eq "ne" && $value1 ne $value2;
	return 1 if $c eq "gt" && $value1 gt $value2;
	return 1 if $c eq "lt" && $value1 lt $value2;
	return 1 if $c eq "ge" && $value1 ge $value2;
	return 1 if $c eq "le" && $value1 le $value2;
	if ($c eq "=~" || $c eq "!~") {
		$value2 =~ m|^(\^?)(.*?)(\$?)$|;
		my ($o, $qm, $t) = ($1, quotemeta($2), $3);
		return 1 if $c eq "=~" && $value1 =~ /$o$qm$t/i;
		return 1 if $c eq "!~" && $value1 !~ /$o$qm$t/i;
	} elsif ($c eq "rexp=~") {
		return 1 if $value1 =~ /$value2/i;
	} elsif ($c eq "rexp!~") {
		return 1 if $value1 !~ /$value2/i;
	}
	return 0;
}

sub template_error {
	my $message = join("</p><p>", @_);
	header();
	print "</TD></TR></TABLE></TD></TR></TABLE><P>\n";
	print "<CENTER><FONT COLOR=#ff0000><B>Template Error</B></FONT></CENTER><HR>\n";
	print "<p>A template problem or error occurred for the following reason:</p>\n";
	print "<p><B>$message</B></P>\n";
	print "</BODY></HTML>\n";
	program_exit(0);
}

1;
