[推薦+轉載]HTML::Template文檔翻譯:)

By tojeff

全文如下:


package Template;
$Template::VERSION = '2.6';
=head1 NAME
Template - 在CGI中使用HTML模闆的模塊
=head1 SYNOPSIS
首先你需要創建一個模闆 - 僅僅是帶有擴展的标志的 HTML 文件,
最常見的擴展标志就是 <TMPL_VAR>
例如, test.tmpl:
<html>
<head><title>Test Template</title>
<body>
My Home Directory is <TMPL_VAR NAME=HOME>
<p>
My Path is set to <TMPL_VAR NAME=PATH>
</body>
</html>
現在創建一個小 CGI 程序:
#!/usr/bin/perl -w
use Template;
# 打開HTML模闆
my $template = Template->new(filename => 'test.tmpl');
# 填充部分參數
$template->param(HOME => $ENV{HOME});
$template->param(PATH => $ENV{PATH});
#發送必須的 Content-Type,并且打印模闆輸出
print "Content-Type: text/html\n\n", $template->output;
如果設置正确的話,運行CGI程序以後,将在浏覽器中顯示如下的内容:
My Home Directory is /home/some/directory
My Path is set to /bin;/usr/bin
=head1 DESCRIPTION
本模塊試圖簡單并且自然的使用 HTML 模闆.她繼承了 HTML 并且擴展了部分新的HTML标簽
-<TMPL_VAR>,<TMPL_LOOP>,<TMPL_INCLUDE>,<TMPL_IF>,<TMPL_ELSE>和<TMPL_UNLESS>.
使用HTML和上述的标簽編寫的模闆将會被調用,也就是說你的模闆可以和你的腳本分離,
甚至可以由其他人來創建,修改,然後使用本模塊來填充模闆中的變量,循環和分支申明.
這将幫助你将腳本使用的 數據結構設計 和 HTML 分開.
=head1 THE TAGS
=head2 TMPL_VAR
<TMPL_VAR NAME="PARAMETER_NAME">
<TMPL_VAR> 标簽非常的簡單.
模闆中的每個 <TMPL_VAR> 都要調用$template->param(PARAMETER_NAME => "VALUE").
當頁面輸出的時候,<TMPL_VAR> 将會被你賦的變量值取代.
如果你沒有設置模闆中的一些變量值,在輸出時僅僅跳過.
一個可選的屬性:你可以在你的标簽裡面使用 "ESCAPE=HTML" ,以在輸出前編碼部
分HTML字符.也就是說", <, >, 和 & 字符将轉換為 ", <, >和 &.
這個屬性在你的變量中如果包含HTML代碼可能會帶來麻煩的時候才非常有用.
例:
<input name=param type=text value="<TMPL_VAR NAME="PARAM">">
如果你給 param() 傳遞類似與 sam"my ,那麼你将會在雙引号部分引來麻煩.
在另外一個方面, 如果你使用ESCAPE=HTML, 例如:
<input name=param type=text value="<TMPL_VAR ESCAPE=HTML NAME="PARAM">">
無論提交者提交的參數怎麼變化,你将會得到任何你想得到的值.
你可以使用的格式: ESCAPE="HTML", ESCAPE='HTML' 和 ESCAPE='1'.
如果傳遞給 ESCAPE 的參數0,将關閉過濾,而且默認的設置是關閉的.
同樣你也可以使用 "ESCAPE=URL" 來處理URL.
她将做 URL 過濾, 比如,替換 ' ' 為 '+'和替換 '/' 為 '%2F'.
你也可以使用一個 DEFAULT 來設置缺省的默認值.
例如, 你要輸出 "the devil gave me a taco",如果 "who" 變量沒有被設置.
The <TMPL_VAR NAME=WHO DEFAULT=devil> gave me a taco.
=head2 TMPL_LOOP
<TMPL_LOOP NAME="LOOP_NAME"> ... </TMPL_LOOP>
<TMPL_LOOP>标簽比<TMPL_VAR>稍微複雜一點. <TMPL_LOOP> 允許你劃定一塊文本區塊,
并且給她一個名字. 在區塊内部,你可以放置<TMPL_VAR>标簽. 為了循環,需要傳遞給
param()的參數分配(散列引用(hash refs))一個表 (一個數組(array ref)).
循環将重述列表,并且給每個參數産生文本塊. 未設置的參數将會被跳過. 舉例如下:
模闆文件:
<TMPL_LOOP NAME=EMPLOYEE_INFO>
Name: <TMPL_VAR NAME=NAME> <br>
Job: <TMPL_VAR NAME=JOB> <p>
</TMPL_LOOP>
腳本:
$template->param(EMPLOYEE_INFO => [
{ name => 'Sam', job => 'programmer' },
{ name => 'Steve', job => 'soda jerk' },
]
);
print $template->output();
浏覽器中的輸出:
Name: Sam
Job: programmer
Name: Steve
Job: soda jerk
如上面你所見<TMPL_LOOP>進行了參數的分配并且重述了循環輸出
一般情況下,如果你想用程序産生一個<TMPL_LOOP>的循環,下面是一個詳細的例子
(可能有很多種其他方法可以實現!):
# 需要放入循環中的數組:
my @words = qw(I Am Cool);
my @numbers = qw(1 2 3);
my @loop_data = (); # 初始化循環數組
while (@words and @numbers) {
my %row_data; # 使用新的散列
# fill in this row
$row_data{WORD} = shift @words;
$row_data{NUMBER} = shift @numbers;
# 先将數據保存在散列中,然後在壓入數組
push(@loop_data, \%row_data);
}
# 為 param()傳遞參數(引用)
$template->param(THIS_LOOP => \@loop_data);
上面的例子可以與下面的模闆一起工作:
<TMPL_LOOP NAME="THIS_LOOP">
Word: <TMPL_VAR NAME="WORD"> <br>
Number: <TMPL_VAR NAME="NUMBER"> <p>
</TMPL_LOOP>
她将産生如下的輸出:
Word: I
Number: 1
Word: Am
Number: 2
Word: Cool
Number: 3
嵌套的 <TMPL_LOOP>也可以很好按照你的期望的情況正确的工作.
如果傳遞給 param() 的參數有冗餘, 下面是使用嵌套的一個例子:
$template->param(LOOP => [
{ name => 'Bobby',
nicknames => [
{ name => 'the big bad wolf' },
{ name => 'He-Man' },
],
},
],
);
基本上, 每個<TMPL_LOOP>将獲取一個數組引用.數組内部是任意數量的散列引用.
這些散列包含'name=>value'對來給模闆中的循環傳遞單個的參數.
在<TMPL_LOOP>中, 變量僅僅能夠在 <TMPL_LOOP> 以後才可用.模闆中的<TMPL_LOOP>
的區塊外的變量将不可見.由于Perl語言的面向對象的特殊性,<TMPL_LOOP> 引入了新的類
似于Perl子程序的調用.如果你想變量是全局可用的,你可以在new()使用'global_vars'選項描述.
=head2 TMPL_INCLUDE
<TMPL_INCLUDE NAME="filename.tmpl">
該标簽在當前模闆點包含一個模闆進來. 被包含的模闆内容與直接放置在主模闆中的效果一樣.
文件路徑可以是絕對路徑(在UNIX中以 '/'開頭).如果不是絕對路徑,模塊将在文件的當前路徑搜索.
如果沒有找到,并且環境變量中的'HTML_TEMPLATE_ROOT'變量如果存在,該路徑将會被搜索.
最後, 'path' 選項将要考慮;
總之,首先是當前,其次是'HTML_TEMPLATE_ROOT',最後文件名将傳遞給 open().
查看下面的關于'HTML_TEMPLATE_ROOT'和 new()的'path'選項獲得更多的信息.
作為'HTML_INCLADE'的多重遞歸調用的保護措施, 默認'HTML_INCLADE'隻在10層以内才起作用.
你可以使用 "max_includes" 選項轉換限制條件.查看下面的 "max_includes" 選項獲得更多的細節.
=head2 TMPL_IF
<TMPL_IF NAME="PARAMETER_NAME"> ... </TMPL_IF>
<TMPL_IF>标簽可以由傳遞的參數決定是不是要在輸出中包含一段文字塊.
如果參數是Perl中的真值的話(例如 '1'),然後文字塊将會被包含.
如果是未定義或者是否(例如'0'),然後文字塊将會被跳過.參數傳遞方法類似于TMPL_VAR.
例:
<TMPL_IF NAME="BOOL">
Some text that only gets displayed if BOOL is true!
</TMPL_IF>
如果你調用$template->param(BOOL => 1),然後上面的文字塊将會被包含在輸出中.
<TMPL_IF>...</TMPL_IF>塊可以包含在任意的有效模闆中,比如VARs和LOOPs以及其他的IF/ELSE結構.
注意,交叉的<TMPL_IF>和<TMPL_LOOP>是無效的.
下面的将不能夠正常的工作:
<TMPL_IF BOOL>
<TMPL_LOOP SOME_LOOP>
</TMPL_IF>
</TMPL_LOOP>
如果TMPL_LOOP的名字與TMPL_IF相同,并且LOOP至少含有一行,IF塊将會輸出.
例:
<TMPL_IF LOOP_ONE>
This will output if the loop is not empty.
</TMPL_IF>
<TMPL_LOOP LOOP_ONE>
....
</TMPL_LOOP>
警告: 模塊的最大的優點就是協調了HTML和Perl的相互關系.
如果你使用TMPL_IF和Perl if()交叉使用很多的話,那麼你會給維護帶來很多的困難.
所以我建議你僅僅使用TMPL_IF,隻要你可以不使用Perl代碼中的 if()的情況下.
=head2 TMPL_ELSE
<TMPL_IF NAME="PARAMETER_NAME">...<TMPL_ELSE>...</TMPL_IF>
你可以使用TMPL_ELSE在你的TMPL_IF中包含一個選擇.
注意:你仍然要用</TMPL_IF>來結束,而不是</TMPL_ELSE>!
例:
<TMPL_IF BOOL>
Some text that is included only if BOOL is true
<TMPL_ELSE>
Some text that is included only if BOOL is false
</TMPL_IF>
=head2 TMPL_UNLESS
<TMPL_UNLESS NAME="PARAMETER_NAME">...</TMPL_UNLESS>
這是<TMPL_IF>對立的标簽.如果控制參數為假,或者未定義,文字塊将會輸出.
你可以使用<TMPL_ELSE>來搭配<TMPL_UNLESS>,使用方法類似與<TMPL_IF>.
例:
<TMPL_UNLESS BOOL>
Some text that is output only if BOOL is FALSE.
<TMPL_ELSE>
Some text that is output only if BOOL is TRUE.
</TMPL_UNLESS>
如果TMPL_LOOP的名字被使用在TMPL_UNLESS中, 那麼并且LOOP沒有内容,UNLESS塊将會輸出.
例:
<TMPL_UNLESS LOOP_ONE>
This will output if the loop is empty.
</TMPL_UNLESS>
<TMPL_LOOP LOOP_ONE>
....
</TMPL_LOOP>
=cut
=head2 NOTES
Template的标簽試圖模仿标準的HTML的語法.然而,它們被允許打破慣例.類似于:
<img src="<TMPL_VAR IMAGE_SRC>">
這并不是真正有效的HTML, 但是卻是非常有效的代碼,可以按照希望的要求工作.
選項 "NAME=" 是可選的, 雖然為了更好的展開,我強烈建議使用她.
例如"<TMPL_LOOP LOOP_NAME>" 是接受的.
如果你是标準HTML的追随者,并且希望你的模闆也遵循标準的HTML語法,
你可以按照HTML的形式随意的定義模闆的标簽.這可能對使用HTML的編輯器或者那些使用DTD格式的工具
來檢查模闆的HTML語法的人員.
<!-- TMPL_VAR NAME=PARAM1 -->
為了方便說明, 标準的标簽将在本文檔中使用.
=head1 METHODS
=head2 new()
調用 new() 創建一個新的模闆對象:
my $template = Template->new( filename => 'file.tmpl',
option => 'value'
);
調用 new() 的時候,你必須至少含有一對name => value對來指定訪問模闆文件的方法.
你可以使用"filename => 'file.tmpl'" 來指定一個文件名來打開她作為一個模闆.
類似的,你也可以
使用:
my $t = Template->new( scalarref => $ref_to_template_text,
option => 'value'
);

my $t = Template->new( arrayref => $ref_to_array_of_lines ,
option => 'value'
);
這些都是初始化模闆進入内存資源.在大多數的情況下,你可以想使用文件名參數.
如果你擔心使用mod_perl以後,所有的模闆的訪問權限,那麼緩沖選項的細節部分如下.
你可以從已經打開的文件句柄中讀取模闆,類似與傳統的typeglob以及FileHandle:
my $t = Template->new( filehandle => *FH, option => 'value');
如果你喜歡,四個新的 new() 調用方法樣式也可以使用.
my $t = Template->new_file('file.tmpl', option => 'value');
my $t = Template->new_scalar_ref($ref_to_template_text,
option => 'value');
my $t = Template->new_array_ref($ref_to_array_of_lines,
option => 'value');
my $t = Template->new_filehandle($fh,
option => 'value');
作為最後一個選項, 可能會有人需要, 你可以這樣調用new()方法:
my $t = Template->new( type => 'filename',
source => 'file.tmpl'
);
她将可以與三種源一起工作.
如果環境變量HTML_TEMPLATE_ROOT被設置,并且文件名是以'/'開始(UNIX),
那麼文件的路徑将關聯到"$HTML_TEMPLATE_ROOT" 的值.
例如,環境變量HTML_TEMPLATE_ROOT被設置為"/home/sam"并且我使用文件名"sam.tmpl"調用,
那麼Template将會打開"/home/sam/sam.tmpl"訪問模闆.
你仍然可以使用new()的"path"選項來影響路徑(查看下面獲得更多的信息).
你可以使用new來修改Template對象的行為.這些選項都是有效的:
=over 4
=item Error Detection Options
=over 4
=item *
die_on_bad_params - 如果設置為0,那麼,模塊允許在'param_name'不存在的情況下,
調用$template->param(param_name => 'value'),而不退出. 默認設置為1.
=item *
strict - 如果設置為0,那麼,模塊允許在TMPL_*被使用而不退出.
例:
<TMPL_HUH NAME=ZUH>
通常情況下将是一個錯誤, 但是你在調用new的時候使用'strict => 0',标簽将會忽略.默認設置為 1.
=item *
vanguard_compatibility_mode - 如果設置為1,那麼模塊将願意看到<TMPL_VAR>
标簽看起來類似于 %NAME% 作為傳統方式(早期)的補充.
同時也要設置 die_on_bad_params => 0.默認為 0.
=back
=item Caching Options
=over 4
=item *
cache - 如果設置為1,模塊将要在内存中緩沖,然後按參數分析模闆并且修正文件中的數據.
這僅僅作用适用于使用指定文件名的方式打開模闆, 而不是标量引用(scalarref)和數組引用模塊(arraryref).
緩沖同樣也查看任何文件的修正時間,包含使用的<TMPL_INCLUDE>标簽, 但是,再一次說明:
僅僅适用于指定文件名的方式打開模闆的.
這主要是服務于類似于Apache/mod_perl等持久穩固的環境中使用.
這對使用普通的CGI環境是絕對沒有任何益處的,因為程序在每次請求以後都要從内存中清除的.
為了能夠與通常的CGI程序緩沖,查看下面的'shared_cache'選項.
注意:不同的new()參數設置不會導緻緩沖的刷新, 僅僅修正模闆的時間更改将會引發緩沖的刷新.
對大多數的使用,這種方法是很好的.在mod_perl下,我簡單測試了一下,使用cache使90%
的執行過程提高了速度.Cache 默認為0.
=item *
shared_cache - 如果設置為 1 模塊将使用IPC::SharedCache(可以從 CPAN 站點獲得)模塊,
保存緩沖在共享的内存中.這樣做的的好處就是為使用每個分析模闆的一個共享進程,
這将在多用戶的服務器環境中大幅度的減少内存的使用.舉例,在一個系統上,我們使用 4MB 模闆高速
緩沖并且維持 25 個httpd進程shared_cache可以節省大約 100MB!當然, 相對與使用傳統的高速緩
沖來言,一些速度損失是不可以避免的.另外一個在cache和shared_cache就是shared_cache可以工
作在CGI環境中,而cache僅僅在Apache/mod_perl等持久穩定的系統中有效.
默認的,模闆使用IPC鍵 'TMPL' 作為共享的根段(0x4c504d54 in hex),
但是,這将可以通過在new()中對四種另外的方式和整數關鍵字設置'ipc_key'來修改.
另外的相應與IPC::SharedCache可選項可以影響共享的内存
-ipc_mode, ipc_segment_size 和ipc_max_size. 查看L<IPC::SharedCache>了解這些是怎麼
工作的(在大多數情況下,我們不需要改變默認值).
查看L<IPC::SharedCache>獲得更多關于共享内存的系統信息.
=item *
double_cache - 如果設置為1模塊将使用
shared_cache和cache模式的聯合體來獲取更優的緩沖方式.當然,她仍然是消耗兩種的模式中其中一種
的内存.同樣的 ipc_* 選項,也可以以shared_cache方式工作應用.默認,double_cache是關閉的.
=item *
blind_cache - 如果設置為1,模塊将以通常的cache方式工作,隻是每次請求時不檢查文件是不是已經
更新. 該選項的使用請無比小心, 但是切可以用與高負載的服務器上.
我的測試顯示,在mod_perl下,使用blind_cache僅僅使提高了速度1-2%.
注意: 綜合該選項與shared_cache,會導緻陳舊的模闆長貯内存!
=item *
file_cache - 如果設置為1,模塊将使用Storable模塊,将緩沖保存在文件中.
她将不再使用額外的内存, 我的簡單測試顯示她收到了50% 的執行效益.
類似與shared_cache, 她也可以适應CGI環境. 默認設置為0.
如果你設置改屬性,你還必須設置"file_cache_dir"選項.查看獲得更多細節問題.
注意: Storable模塊使用flock()來保證緩沖文件的安全訪問.
在一個不支持flock()的系統(Win95等)或者文件系統(NFS等)使用将會帶來危害.
=item *
file_cache_dir - 如果使用file_cache,設置文件高速緩沖的高速緩沖文件目錄.
你的腳本必須獲得此目錄的寫權限. 你又必須确保有足夠的可用空間來保存緩沖文件.
=item *
file_cache_dir_mode - 設置新建的緩沖文件的目錄和子目錄模式.
為了服務器的安全,默認為0700,但是在你使用你的服務器帳号登陸時,可能會給您帶來不便.
=item *
double_file_cache - 如果設置為1,模塊将綜合使用file_cache和cache來獲得多可能的緩沖.
與file_cache協同工作的file_cache_* 選項使用于double_file_cache.
默認情況下,double_file_cache設置為0.
=back
=item Filesystem Options
=over 4
=item *
path - 在new()中,你可以向該變量傳遞一個列表來設置'filename'和<TMPL_INCLUDE>标簽指定的
文件和來設置搜索的目錄. 在文件名為相對路徑,該列表僅僅是用來參考的.如果HTML_TEMPLATE_ROOT
環境變量存在的話,她将會首先被嘗試的.同樣, 如果設置了HTML_TEMPLATE_ROOT,系統将會嘗試把優先
把HTML_TEMPLATE_ROOT路徑添加到path數組. 在<TMPL_INCLUDE>文件中, 當HTML_TEMPLATE_ROOT
路徑被參考之前,被包含的文件的路徑将會被優先考慮.
例:
my $template = Template->new( filename => 'file.tmpl',
path => [ '/path/to/templates',
'/alternate/path'
]
);
注意: 路徑信息中的路徑必須是unix的路徑表達形式,使用斜杠('/')來分割的.
=item *
search_path_on_include - 如果設置為真值的話,對每個<TMPL_INCLUDE>标簽,模塊将中path
指定的路徑數組的頂端開始搜索,并且使用找到第一個匹配的模闆.
通常情況下,僅僅在當前的目錄中查找模闆. 默認設置為0.
=back
=item Debugging Options
=over 4
=item *
debug - 如果設置為1,模塊将會把任意的調試信息寫到STDERR.默認為0.
=item *
stack_debug - 如果設置為1,模塊将使用 Data::Dumper 打印分析棧的内容到STDERR.默認設置為 0.
=item *
cache_debug - 如果設置為1,模塊将發送關于緩沖加載,采樣和錯誤信息到STDERR.默認設置0.
=item *
shared_cache_debug - 如果設置為1,模塊将打開IPC::SharedCache中的調試選項(查看 L<IPC::SharedCache>獲得更多信息). 默認設置0.
=item *
memory_debug - 如果設置為1,模塊将發送關于緩沖内存的使用情況到STDERR,該功能依賴于GTop模塊.默認設置0.
=back
=item Miscellaneous Options
=over 4
=item *
associate - 該選項允許你繼承其他對象的參數.
僅僅的要求就是所繼承的對象要有一個類似與Template的param()的param()方法.
一個比較優秀的就是CGI.pm的查詢對象.
例:
my $query = new CGI;
my $template = Template->new(filename => 'template.tmpl',
associate => $query);
然後, $template->output()将會安照
$template->param('FormField', $cgi->param('FormField'))方式運行;
每個指定的 key/value 對将由$cgi->param()方法提供.
你所設置的參數将優先于關聯的參數.你可以通過傳遞匿名的數組指定多重的對象來關聯.
他們按照他們出現的順序來查找參數:
my $template = Template->new(filename => 'template.tmpl',
associate => [$query, $other_obj]);
老版本的 associateCGI() 調用仍然支持, 但是現在考慮荒廢她.
注意: 參數名是不區分大小寫的.
如果你在CGI對象中有兩個參數名,'NAME' 和 'Name',他們其中之一将會被随機的使用.
該行為可能被下面的選項控制.
=item *
case_sensitive - 設置該選項為真,将導緻Template處理模闆變量名時區分大小寫.
如果不使用"case_sensitive",下面的例子将僅僅設置一個參數:
my $template = Template->new(filename => 'template.tmpl',
case_sensitive => 1);
$template->param(
FieldA => 'foo',
fIELDa => 'bar',
);
該選項默認是關閉的.
注意: 使用case_sensitive和loop_context_vars,那麼特殊的循環變量将僅僅小寫有效.
=item *
loop_context_vars - 當該參數設置為真時(默認為非) 四個循環的上下文變量将在循環中生效:
__first__, __last__, __inner__, __odd__. 他們可以與
<TMPL_IF>, <TMPL_UNLESS> 和 <TMPL_ELSE> 一起使用來控制循環的輸出.
做為以上的補充, 當循環的上下文變量打開以後,一個 __counter__ 變量也将生效.
例:
<TMPL_LOOP NAME="FOO">
<TMPL_IF NAME="__first__">
This only outputs on the first pass.
</TMPL_IF>
<TMPL_IF NAME="__odd__">
This outputs every other pass, on the odd passes.
</TMPL_IF>
<TMPL_UNLESS NAME="__odd__">
This outputs every other pass, on the even passes.
</TMPL_IF>
<TMPL_IF NAME="__inner__">
This outputs on passes that are neither first nor last.
</TMPL_IF>
This is pass number <TMPL_VAR NAME="__counter__">.
<TMPL_IF NAME="__last__">
This only outputs on the last pass.
<TMPL_IF>
</TMPL_LOOP>
該功能的一個典型用法就是提供一個離析器,類似于perl的函數join().
例:
<TMPL_LOOP FRUIT>
<TMPL_IF __last__> and </TMPL_IF>
<TMPL_VAR KIND><TMPL_UNLESS __last__>, <TMPL_ELSE>.</TMPL_UNLESS>
</TMPL_LOOP>
将輸出 (在浏覽器中) :
Apples, Oranges, Brains, Toes, and Kiwi.
當然,必須提供一個适當的param()調用.
注意: 一個隻有一個參數的循環,__first__ 和 __last__将全部設置為真, 但是卻沒有__inner__.
=item *
no_includes - 該選項設置為1,将在模闆中禁止使用 <TMPL_INCLUDE> 标簽.
這樣可以可以給開放的模闆減少危險. 默認設置為 0.
=item *
max_includes - 設置包含功能能夠達到的最大的深度. 默認設置為 10.
包含超過深度的文件将會顯示一個錯誤. 設置為 0,可以關閉該保護功能.
=item *
global_vars - 通常的,在循環外面定義的變量将在循環中無效.
該選項使 <TMPL_VAR> 類似與全局變量- 她們将變的沒有現在.該選項也會影響<TMPL_IF>和<TMPL_UNLESS>.
例:
This is a normal variable: <TMPL_VAR NORMAL>.<P>
<TMPL_LOOP NAME=FROOT_LOOP>
Here it is inside the loop: <TMPL_VAR NORMAL><P>
</TMPL_LOOP>
通常她不能夠按照期望來工作, 因為在循環以外的 <TMPL_VAR NORMAL>值在循環内是無效的.
global_vars 允許你訪問裝入循環的值.例如, 在本循環在中,内部循環将可以存取循環外部的值OUTER_VAR:
<TMPL_LOOP OUTER_LOOP>
OUTER: <TMPL_VAR OUTER_VAR>
<TMPL_LOOP INNER_LOOP>
INNER: <TMPL_VAR INNER_VAR>
INSIDE OUT: <TMPL_VAR OUTER_VAR>
</TMPL_LOOP>
</TMPL_LOOP>
=item *
filter - 該選項允許你指定一個你的模闆文件的過濾方法.
一個過濾其實是一個在模闆閱讀以後,但是在解析模闆标簽之前的子程序.
在大多數的簡單運用中, 你簡單的給過濾參數分配一個代碼引用.
該子程度将接受一個參數 - 一個指向模闆中的字符串引用. 下面是一個接受類似于
"!!!ZAP_VARFOO!!!"的标簽的例子,然後将他轉換為模闆的标簽:
my $filter = sub {
my $text_ref = shift;
$$text_ref =~ s/!!!ZAP_(.*?)!!!/<TMPL_$1>/g;
};
# open zap.tmpl using the above filter
my $template = Template->new(filename => 'zap.tmpl',
filter => $filter);
更多可能的使用方法都是可能的. 您可以要求您的過濾接受一個模闆文件
作為一個行數組而不是單獨的一個标量.要做的就是,你指點你的一個使用散列的過濾.
在這樣的形式,你使用"sub"關鍵字指定一個過濾和使用"format"關鍵字指定要求的參數形式.
有效的格式就是"scalar"和"array".
使用"array"格式将招緻執行錯誤,但是在很多情況下可以帶來方便.
my $template = Template->new(filename => 'zap.tmpl',
filter => { sub => $filter,
format => 'array' });
你可能使用多重的過濾. 為可過多的具體的功能,她可以允許簡單的過濾的組合.
僅僅要做的就是指定一個過濾數組. 過濾将按照他們被指定的順序來過濾.
my $template = Template->new(filename => 'zap.tmpl',
filter => [
{ sub => \&decompress,
format => 'scalar' },
{ sub => \&remove_spaces,
format => 'array' }
]);
類似于主模闆中,指定的過濾将會在任何TMPL_INCLUDE包含的文件中起作用.
=back
=back 4
=cut

use integer; # no floating point math so far!
use strict; # and no funny business, either.
use Carp; # generate better errors with more context
use File::Spec; # generate paths that work on all platforms

# define accessor constants used to improve readability of array
# accesses into "objects". I used to use 'use constant' but that
# seems to cause occasional irritating warnings in older Perls.
package Template::LOOP;
sub TEMPLATE_HASH () { 0; }
sub PARAM_SET () { 1 };

package Template::COND;
sub VARIABLE () { 0 };
sub VARIABLE_TYPE () { 1 };
sub VARIABLE_TYPE_VAR () { 0 };
sub VARIABLE_TYPE_LOOP () { 1 };
sub JUMP_IF_TRUE () { 2 };
sub JUMP_ADDRESS () { 3 };
sub WHICH () { 4 };
sub WHICH_IF () { 0 };
sub WHICH_UNLESS () { 1 };

# back to the main package scope.
package Template;

# open a new template and return an object handle
sub new {
my $pkg = shift;
my $self; { my %hash; $self = bless(\%hash, $pkg); }

# the options hash
my $options = {};
$self->{options} = $options;

# set default parameters in options hash
%$options = (
debug => 0,
stack_debug => 0,
timing => 0,
search_path_on_include => 0,
cache => 0,
blind_cache => 0,
file_cache => 0,
file_cache_dir => '',
file_cache_dir_mode => 0700,
cache_debug => 0,
shared_cache_debug => 0,
memory_debug => 0,
die_on_bad_params => 1,
vanguard_compatibility_mode => 0,
associate => [],
path => [],
strict => 1,
loop_context_vars => 0,
max_includes => 10,
shared_cache => 0,
double_cache => 0,
double_file_cache => 0,
ipc_key => 'TMPL',
ipc_mode => 0666,
ipc_segment_size => 65536,
ipc_max_size => 0,
global_vars => 0,
no_includes => 0,
case_sensitive => 0,
filter => [],
);

# load in options supplied to new()
for (my $x = 0; $x <= $#_; $x += 2) {
defined($_[($x + 1)]) or croak("Template->new() called with odd number of option parameters - should be of the form option => value");
$options->{lc($_[$x])} = $_[($x + 1)]
}

# blind_cache = 1 implies cache = 1
$options->{blind_cache} and $options->{cache} = 1;

# shared_cache = 1 implies cache = 1
$options->{shared_cache} and $options->{cache} = 1;

# file_cache = 1 implies cache = 1
$options->{file_cache} and $options->{cache} = 1;

# double_cache is a combination of shared_cache and cache.
$options->{double_cache} and $options->{cache} = 1;
$options->{double_cache} and $options->{shared_cache} = 1;

# double_file_cache is a combination of file_cache and cache.
$options->{double_file_cache} and $options->{cache} = 1;
$options->{double_file_cache} and $options->{file_cache} = 1;

# vanguard_compatibility_mode implies die_on_bad_params = 0
$options->{vanguard_compatibility_mode} and
$options->{die_on_bad_params} = 0;

# handle the "type", "source" parameter format (does anyone use it?)
if (exists($options->{type})) {
exists($options->{source}) or croak("Template->new() called with 'type' parameter set, but no 'source'!");
($options->{type} eq 'filename' or $options->{type} eq 'scalarref' or
$options->{type} eq 'arrayref' or $options->{type} eq 'filehandle') or
croak("Template->new() : type parameter must be set to 'filename', 'arrayref', 'scalarref' or 'filehandle'!");

$options->{$options->{type}} = $options->{source};
delete $options->{type};
delete $options->{source};
}

# associate should be an array of one element if it's not
# already an array.
if (ref($options->{associate}) ne 'ARRAY') {
$options->{associate} = [ $options->{associate} ]
}

# path should be an array if it's not already
if (ref($options->{path}) ne 'ARRAY') {
$options->{path} = [ $options->{path} ]
}

# filter should be an array if it's not already
if (ref($options->{filter}) ne 'ARRAY') {
$options->{filter} = [ $options->{filter} ]
}

# make sure objects in associate area support param()
foreach my $object (@{$options->{associate}}) {
defined($object->can('param')) or
croak("Template->new called with associate option, containing object of type " . ref($object) . " which lacks a param() method!");
}

# check for syntax errors:
my $source_count = 0;
exists($options->{filename}) and $source_count++;
exists($options->{filehandle}) and $source_count++;
exists($options->{arrayref}) and $source_count++;
exists($options->{scalarref}) and $source_count++;
if ($source_count != 1) {
croak("Template->new called with multiple (or no) template sources specified! A valid call to new() has exactly one filename => 'file' OR exactly one scalarref => \\\$scalar OR exactly one arrayref => \\\@array OR exactly one filehandle => \*FH");
}

# do some memory debugging - this is best started as early as possible
if ($options->{memory_debug}) {
# memory_debug needs GTop
eval { require GTop; };
croak("Could not load GTop. You must have GTop installed to use Template in memory_debug mode. The error was: [email protected]")
if ([email protected]);
$self->{gtop} = GTop->new();
$self->{proc_mem} = $self->{gtop}->proc_mem($$);
print STDERR "\n### Template Memory Debug ### START ", $self->{proc_mem}->size(), "\n";
}

if ($options->{file_cache}) {
# make sure we have a file_cache_dir option
croak("You must specify the file_cache_dir option if you want to use file_cache.")
unless defined $options->{file_cache_dir} and
length $options->{file_cache_dir};

# file_cache needs some extra modules loaded
eval { require Storable; };
croak("Could not load Storable. You must have Storable installed to use Template in file_cache mode. The error was: [email protected]")
if ([email protected]);
eval { require Digest::MD5; };
croak("Could not load Digest::MD5. You must have Digest::MD5 installed to use Template in file_cache mode. The error was: [email protected]")
if ([email protected]);
}

if ($options->{shared_cache}) {
# shared_cache needs some extra modules loaded
eval { require IPC::SharedCache; };
croak("Could not load IPC::SharedCache. You must have IPC::SharedCache installed to use Template in shared_cache mode. The error was: [email protected]")
if ([email protected]);

# initialize the shared cache
my %cache;
tie %cache, 'IPC::SharedCache',
ipc_key => $options->{ipc_key},
load_callback => [\&_load_shared_cache, $self],
validate_callback => [\&_validate_shared_cache, $self],
debug => $options->{shared_cache_debug},
ipc_mode => $options->{ipc_mode},
max_size => $options->{ipc_max_size},
ipc_segment_size => $options->{ipc_segment_size};
$self->{cache} = \%cache;
}

print STDERR "### Template Memory Debug ### POST CACHE INIT ", $self->{proc_mem}->size(), "\n"
if $options->{memory_debug};

# initialize data structures
$self->_init;

print STDERR "### Template Memory Debug ### POST _INIT CALL ", $self->{proc_mem}->size(), "\n"
if $options->{memory_debug};

# drop the shared cache - leaving out this step results in the
# template object evading garbage collection since the callbacks in
# the shared cache tie hold references to $self! This was not easy
# to find, by the way.
delete $self->{cache} if $options->{shared_cache};

return $self;
}

# an internally used new that receives its parse_stack and param_map as input
sub _new_from_loop {
my $pkg = shift;
my $self; { my %hash; $self = bless(\%hash, $pkg); }

# the options hash
my $options = {};
$self->{options} = $options;

# set default parameters in options hash - a subset of the options
# valid in a normal new(). Since _new_from_loop never calls _init,
# many options have no relevance.
%$options = (
debug => 0,
stack_debug => 0,
die_on_bad_params => 1,
associate => [],
loop_context_vars => 0,
);

# load in options supplied to new()
for (my $x = 0; $x <= $#_; $x += 2) {
defined($_[($x + 1)]) or croak("Template->new() called with odd number of option parameters - should be of the form option => value");
$options->{lc($_[$x])} = $_[($x + 1)]
}

$self->{param_map} = $options->{param_map};
$self->{parse_stack} = $options->{parse_stack};
delete($options->{param_map});
delete($options->{parse_stack});

return $self;
}

# a few shortcuts to new(), of possible use...
sub new_file {
my $pkg = shift; return $pkg->new('filename', @_);
}
sub new_filehandle {
my $pkg = shift; return $pkg->new('filehandle', @_);
}
sub new_array_ref {
my $pkg = shift; return $pkg->new('arrayref', @_);
}
sub new_scalar_ref {
my $pkg = shift; return $pkg->new('scalarref', @_);
}

# initializes all the object data structures, either from cache or by
# calling the appropriate routines.
sub _init {
my $self = shift;
my $options = $self->{options};

if ($options->{double_cache}) {
# try the normal cache, return if we have it.
$self->_fetch_from_cache();
return if (defined $self->{param_map} and defined $self->{parse_stack});

# try the shared cache
$self->_fetch_from_shared_cache();

# put it in the local cache if we got it.
$self->_commit_to_cache()
if (defined $self->{param_map} and defined $self->{parse_stack});
} elsif ($options->{double_file_cache}) {
# try the normal cache, return if we have it.
$self->_fetch_from_cache();
return if (defined $self->{param_map} and defined $self->{parse_stack});

# try the file cache
$self->_fetch_from_file_cache();

# put it in the local cache if we got it.
$self->_commit_to_cache()
if (defined $self->{param_map} and defined $self->{parse_stack});
} elsif ($options->{shared_cache}) {
# try the shared cache
$self->_fetch_from_shared_cache();
} elsif ($options->{file_cache}) {
# try the file cache
$self->_fetch_from_file_cache();
} elsif ($options->{cache}) {
# try the normal cache
$self->_fetch_from_cache();
}

# if we got a cache hit, return
return if (defined $self->{param_map} and defined $self->{parse_stack});

# if we're here, then we didn't get a cached copy, so do a full
# init.
$self->_init_template();
$self->_parse();

# now that we have a full init, cache the structures if cacheing is
# on. shared cache is already cool.
if($options->{file_cache}){
$self->_commit_to_file_cache();
}
$self->_commit_to_cache() if (($options->{cache}
and not $options->{shared_cache}
and not $options->{file_cache}) or
($options->{double_cache}) or
($options->{double_file_cache}));
}

# Caching subroutines - they handle getting and validating cache
# records from either the in-memory or shared caches.

# handles the normal in memory cache
use vars qw( %CACHE );
sub _fetch_from_cache {
my $self = shift;
my $options = $self->{options};

# return if there's no cache entry for this filename
return unless exists($options->{filename});
my $filepath = $self->_find_file($options->{filename});
return unless (defined($filepath) and
exists $CACHE{$filepath});

$options->{filepath} = $filepath;

# validate the cache
my $mtime = $self->_mtime($filepath);
if (defined $mtime) {
# return if the mtime doesn't match the cache
if (defined($CACHE{$filepath}{mtime}) and
($mtime != $CACHE{$filepath}{mtime})) {
$options->{cache_debug} and
print STDERR "CACHE MISS : $filepath : $mtime\n";
return;
}

# if the template has includes, check each included file's mtime
# and return if different
if (exists($CACHE{$filepath}{included_mtimes})) {
foreach my $filename (keys %{$CACHE{$filepath}{included_mtimes}}) {
next unless
defined($CACHE{$filepath}{included_mtimes}{$filename});

my $included_mtime = (stat($filename))Η]
if ($included_mtime != $CACHE{$filepath}{included_mtimes}{$filename}) {
$options->{cache_debug} and
print STDERR "### Template Cache Debug ### CACHE MISS : $filepath : INCLUDE $filename : $included_mtime\n";

return;
}
}
}
}

# got a cache hit!

$options->{cache_debug} and print STDERR "### Template Cache Debug ### CACHE HIT : $filepath\n";

$self->{param_map} = $CACHE{$filepath}{param_map};
$self->{parse_stack} = $CACHE{$filepath}{parse_stack};
exists($CACHE{$filepath}{included_mtimes}) and
$self->{included_mtimes} = $CACHE{$filepath}{included_mtimes};

# clear out values from param_map from last run
$self->_normalize_options();
$self->clear_params();
}

sub _commit_to_cache {
my $self = shift;
my $options = $self->{options};

my $filepath = $options->{filepath};
if (not defined $filepath) {
$filepath = $self->_find_file($options->{filename});
confess("Template->new() : Cannot open included file $options->{filename} : file not found.")
unless defined($filepath);
$options->{filepath} = $filepath;
}

$options->{cache_debug} and print STDERR "### Template Cache Debug ### CACHE LOAD : $filepath\n";

$options->{blind_cache} or
$CACHE{$filepath}{mtime} = $self->_mtime($filepath);
$CACHE{$filepath}{param_map} = $self->{param_map};
$CACHE{$filepath}{parse_stack} = $self->{parse_stack};
exists($self->{included_mtimes}) and
$CACHE{$filepath}{included_mtimes} = $self->{included_mtimes};
}

# generates MD5 from filepath to determine filename for cache file
sub _get_cache_filename {
my ($self, $filepath) = @_;

# hash the filename ...
my $hash = Digest::MD5::md5_hex($filepath);

# ... and build a path out of it. Using the first two charcters
# gives us 255 buckets. This means you can have 255,000 templates
# in the cache before any one directory gets over a few thousand
# files in it. That's probably pretty good for this planet. If not
# then it should be configurable.
if (wantarray) {
return (substr($hash,0,2), substr($hash,2))
} else {
return File::Spec->join($self->{options}{file_cache_dir},
substr($hash,0,2), substr($hash,2));
}
}

# handles the file cache
sub _fetch_from_file_cache {
my $self = shift;
my $options = $self->{options};
return unless exists($options->{filename});

# return if there's no cache entry for this filename
my $filepath = $self->_find_file($options->{filename});
return unless defined $filepath;
my $cache_filename = $self->_get_cache_filename($filepath);
return unless -e $cache_filename;

eval {
$self->{record} = Storable::lock_retrieve($cache_filename);
};
croak("Template::new() - Problem reading cache file $cache_filename (file_cache => 1) : [email protected]")
if [email protected];
croak("Template::new() - Problem reading cache file $cache_filename (file_cache => 1) : $!")
unless defined $self->{record};

($self->{mtime},
$self->{included_mtimes},
$self->{param_map},
$self->{parse_stack}) = @{$self->{record}};

$options->{filepath} = $filepath;

# validate the cache
my $mtime = $self->_mtime($filepath);
if (defined $mtime) {
# return if the mtime doesn't match the cache
if (defined($self->{mtime}) and
($mtime != $self->{mtime})) {
$options->{cache_debug} and
print STDERR "### Template Cache Debug ### FILE CACHE MISS : $filepath : $mtime\n";
($self->{mtime},
$self->{included_mtimes},
$self->{param_map},
$self->{parse_stack}) = (undef, undef, undef, undef);
return;
}

# if the template has includes, check each included file's mtime
# and return if different
if (exists($self->{included_mtimes})) {
foreach my $filename (keys %{$self->{included_mtimes}}) {
next unless
defined($self->{included_mtimes}{$filename});

my $included_mtime = (stat($filename))Η]
if ($included_mtime != $self->{included_mtimes}{$filename}) {
$options->{cache_debug} and
print STDERR "### Template Cache Debug ### FILE CACHE MISS : $filepath : INCLUDE $filename : $included_mtime\n";
($self->{mtime},
$self->{included_mtimes},
$self->{param_map},
$self->{parse_stack}) = (undef, undef, undef, undef);
return;
}
}
}
}

# got a cache hit!
$options->{cache_debug} and print STDERR "### Template Cache Debug ### FILE CACHE HIT : $filepath\n";

# clear out values from param_map from last run
$self->_normalize_options();
$self->clear_params();
}

sub _commit_to_file_cache {
my $self = shift;
my $options = $self->{options};

my $filepath = $options->{filepath};
if (not defined $filepath) {
$filepath = $self->_find_file($options->{filename});
confess("Template->new() : Cannot open included file $options->{filename} : file not found.")
unless defined($filepath);
$options->{filepath} = $filepath;
}

my ($cache_dir, $cache_file) = $self->_get_cache_filename($filepath);
$cache_dir = File::Spec->join($options->{file_cache_dir}, $cache_dir);
if (not -d $cache_dir) {
if (not -d $options->{file_cache_dir}) {
mkdir($options->{file_cache_dir},$options->{file_cache_dir_mode})
or croak("Template->new() : can't mkdir $options->{file_cache_dir} (file_cache => 1): $!");
}
mkdir($cache_dir,$options->{file_cache_dir_mode})
or croak("Template->new() : can't mkdir $cache_dir (file_cache => 1): $!");
}

$options->{cache_debug} and print STDERR "### Template Cache Debug ### FILE CACHE LOAD : $options->{filepath}\n";

my $result;
eval {
$result = Storable::lock_store([ $self->{mtime},
$self->{included_mtimes},
$self->{param_map},
$self->{parse_stack} ],
scalar File::Spec->join($cache_dir, $cache_file)
);
};
croak("Template::new() - Problem writing cache file $cache_dir/$cache_file (file_cache => 1) : [email protected]")
if [email protected];
croak("Template::new() - Problem writing cache file $cache_dir/$cache_file (file_cache => 1) : $!")
unless defined $result;
}

# Shared cache routines.
sub _fetch_from_shared_cache {
my $self = shift;
my $options = $self->{options};

my $filepath = $self->_find_file($options->{filename});
return unless defined $filepath;

# fetch from the shared cache.
$self->{record} = $self->{cache}{$filepath};

($self->{mtime},
$self->{included_mtimes},
$self->{param_map},
$self->{parse_stack}) = @{$self->{record}}
if defined($self->{record});

$options->{cache_debug} and defined($self->{record}) and print STDERR "### Template Cache Debug ### CACHE HIT : $filepath\n";
# clear out values from param_map from last run
$self->_normalize_options(), $self->clear_params()
if (defined($self->{record}));
delete($self->{record});

return $self;
}

sub _validate_shared_cache {
my ($self, $filename, $record) = @_;
my $options = $self->{options};

$options->{shared_cache_debug} and print STDERR "### Template Cache Debug ### SHARED CACHE VALIDATE : $filename\n";

return 1 if $options->{blind_cache};

my ($c_mtime, $included_mtimes, $param_map, $parse_stack) = @$record;

# if the modification time has changed return false
my $mtime = $self->_mtime($filename);
if (defined $mtime and defined $c_mtime
and $mtime != $c_mtime) {
$options->{cache_debug} and
print STDERR "### Template Cache Debug ### SHARED CACHE MISS : $filename : $mtime\n";
return 0;
}

# if the template has includes, check each included file's mtime
# and return false if different
if (defined $mtime and defined $included_mtimes) {
foreach my $fname (keys %$included_mtimes) {
next unless defined($included_mtimes->{$fname});
if ($included_mtimes->{$fname} != (stat($fname))Η]) {
$options->{cache_debug} and
print STDERR "### Template Cache Debug ### SHARED CACHE MISS : $filename : INCLUDE $fname\n";
return 0;
}
}
}

# all done - return true
return 1;
}

sub _load_shared_cache {
my ($self, $filename) = @_;
my $options = $self->{options};
my $cache = $self->{cache};

$self->_init_template();
$self->_parse();

$options->{cache_debug} and print STDERR "### Template Cache Debug ### SHARED CACHE LOAD : $options->{filepath}\n";

print STDERR "### Template Memory Debug ### END CACHE LOAD ", $self->{proc_mem}->size(), "\n"
if $options->{memory_debug};

return [ $self->{mtime},
$self->{included_mtimes},
$self->{param_map},
$self->{parse_stack} ]
}

# utility function - given a filename performs documented search and
# returns a full path of undef if the file cannot be found.
sub _find_file {
my ($self, $filename, $extra_path) = @_;
my $options = $self->{options};
my $filepath;

# first check for a full path
return File::Spec->canonpath($filename)
if (File::Spec->file_name_is_absolute($filename) and (-e $filename));

# try the extra_path if one was specified
if (defined($extra_path)) {
$extra_path->[$#{$extra_path}] = $filename;
$filepath = File::Spec->canonpath(File::Spec->catfile(@$extra_path));
return File::Spec->canonpath($filepath) if -e $filepath;
}

# try pre-prending HTML_Template_Root
if (exists($ENV{HTML_TEMPLATE_ROOT})) {
$filepath = File::Spec->catfile($ENV{HTML_TEMPLATE_ROOT}, $filename);
return File::Spec->canonpath($filepath) if -e $filepath;
}

# try "path" option list..
foreach my $path (@{$options->{path}}) {
$filepath = File::Spec->catfile($path, $filename);
return File::Spec->canonpath($filepath) if -e $filepath;
}

# try even a relative path from the current directory...
return File::Spec->canonpath($filename) if -e $filename;

# try "path" option list with HTML_TEMPLATE_ROOT prepended...
if (exists($ENV{HTML_TEMPLATE_ROOT})) {
foreach my $path (@{$options->{path}}) {
$filepath = File::Spec->catfile($ENV{HTML_TEMPLATE_ROOT}, $path, $filename);
return File::Spec->canonpath($filepath) if -e $filepath;
}
}

return undef;
}

# utility function - computes the mtime for $filename
sub _mtime {
my ($self, $filepath) = @_;
my $options = $self->{options};

return(undef) if ($options->{blind_cache});

# make sure it still exists in the filesystem
(-r $filepath) or Carp::confess("Template : template file $filepath does not exist or is unreadable.");

# get the modification time
return (stat(_))Η]
}

# utility function - enforces new() options across LOOPs that have
# come from a cache. Otherwise they would have stale options hashes.
sub _normalize_options {
my $self = shift;
my $options = $self->{options};

my @pstacks = ($self->{parse_stack});
while(@pstacks) {
my $pstack = pop(@pstacks);
foreach my $item (@$pstack) {
next unless (ref($item) eq 'Template::LOOP');
foreach my $template (values %{$item->[Template::LOOP::TEMPLATE_HASH]}) {
# must be the same list as the call to _new_from_loop...
$template->{options}{debug} = $options->{debug};
$template->{options}{stack_debug} = $options->{stack_debug};
$template->{options}{die_on_bad_params} = $options->{die_on_bad_params};
$template->{options}{case_sensitive} = $options->{case_sensitive};

push(@pstacks, $template->{parse_stack});
}
}
}
}

# initialize the template buffer
sub _init_template {
my $self = shift;
my $options = $self->{options};

print STDERR "### Template Memory Debug ### START INIT_TEMPLATE ", $self->{proc_mem}->size(), "\n"
if $options->{memory_debug};

if (exists($options->{filename})) {
my $filepath = $options->{filepath};
if (not defined $filepath) {
$filepath = $self->_find_file($options->{filename});
confess("Template->new() : Cannot open included file $options->{filename} : file not found.")
unless defined($filepath);
# we'll need this for future reference - to call stat() for example.
$options->{filepath} = $filepath;
}

confess("Template->new() : Cannot open included file $options->{filename} : $!")
unless defined(open(TEMPLATE, $filepath));
$self->{mtime} = $self->_mtime($filepath);

# read into scalar, note the mtime for the record
$self->{template} = "";
while (read(TEMPLATE, $self->{template}, 10240, length($self->{template}))) {}
close(TEMPLATE);

} elsif (exists($options->{scalarref})) {
# copy in the template text
$self->{template} = ${$options->{scalarref}};

delete($options->{scalarref});
} elsif (exists($options->{arrayref})) {
# if we have an array ref, join and store the template text
$self->{template} = join("", @{$options->{arrayref}});

delete($options->{arrayref});
} elsif (exists($options->{filehandle})) {
# just read everything in in one go
local $/ = undef;
$self->{template} = readline($options->{filehandle});

delete($options->{filehandle});
} else {
confess("Template : Need to call new with filename, filehandle, scalarref or arrayref parameter specified.");
}

print STDERR "### Template Memory Debug ### END INIT_TEMPLATE ", $self->{proc_mem}->size(), "\n"
if $options->{memory_debug};

# handle filters if necessary
$self->_call_filters(\$self->{template}) if @{$options->{filter}};

return $self;
}

# handle calling user defined filters
sub _call_filters {
my $self = shift;
my $template_ref = shift;
my $options = $self->{options};

my ($format, $sub);
foreach my $filter (@{$options->{filter}}) {
croak("Template->new() : bad value set for filter parameter - must be a code ref or a hash ref.")
unless ref $filter;

# translate into CODE->HASH
$filter = { 'format' => 'scalar', 'sub' => $filter }
if (ref $filter eq 'CODE');

if (ref $filter eq 'HASH') {
$format = $filter->{'format'};
$sub = $filter->{'sub'};

# check types and values
croak("Template->new() : bad value set for filter parameter - hash must contain \"format\" key and \"sub\" key.")
unless defined $format and defined $sub;
croak("Template->new() : bad value set for filter parameter - \"format\" must be either 'array' or 'scalar'")
unless $format eq 'array' or $format eq 'scalar';
croak("Template->new() : bad value set for filter parameter - \"sub\" must be a code ref")
unless ref $sub and ref $sub eq 'CODE';

# catch errors
eval {
if ($format eq 'scalar') {
# call
$sub->($template_ref);
} else {
# modulate
my @array = map { $_."\n" } split("\n", $$template_ref);
# call
$sub->(\@array);
# demodulate
$$template_ref = join("", @array);
}
};
croak("Template->new() : fatal error occured during filter call: [email protected]") if [email protected];
} else {
croak("Template->new() : bad value set for filter parameter - must be code ref or hash ref");
}
}
# all done
return $template_ref;
}

# _parse sifts through a template building up the param_map and
# parse_stack structures.
#
# The end result is a Template object that is fully ready for
# output().
sub _parse {
my $self = shift;
my $options = $self->{options};

$options->{debug} and print STDERR "### Template Debug ### In _parse:\n";

# setup the stacks and maps - they're accessed by typeglobs that
# reference the top of the stack. They are masked so that a loop
# can transparently have its own versions.
use vars qw(@pstack %pmap @ifstack @ucstack %top_pmap);
local (*pstack, *ifstack, *pmap, *ucstack, *top_pmap);

# the pstack is the array of scalar refs (plain text from the
# template file), VARs, LOOPs, IFs and ELSEs that output() works on
# to produce output. Looking at output() should make it clear what
# _parse is trying to accomplish.
my @pstacks = ([]);
*pstack = $pstacksΎ]
$self->{parse_stack} = $pstacksΎ]

# the pmap binds names to VARs, LOOPs and IFs. It allows param() to
# access the right variable. NOTE: output() does not look at the
# pmap at all!
my @pmaps = ({});
*pmap = $pmapsΎ]
*top_pmap = $pmapsΎ]
$self->{param_map} = $pmapsΎ]

# the ifstack is a temporary stack containing pending ifs and elses
# waiting for a /if.
my @ifstacks = ([]);
*ifstack = $ifstacksΎ]

# the ucstack is a temporary stack containing conditions that need
# to be bound to param_map entries when their block is finished.
# This happens when a conditional is encountered before any other
# reference to its NAME. Since a conditional can reference VARs and
# LOOPs it isn't possible to make the link right away.
my @ucstacks = ([]);
*ucstack = $ucstacksΎ]

# the loopstack is another temp stack for closing loops. unlike
# those above it doesn't get scoped inside loops, therefore it
# doesn't need the typeglob magic.
my @loopstack = ();

# the fstack is a stack of filenames and counters that keeps track
# of which file we're in and where we are in it. This allows
# accurate error messages even inside included files!
# fcounter, fmax and fname are aliases for the current file's info
use vars qw($fcounter $fname $fmax);
local (*fcounter, *fname, *fmax);

my @fstack = ([$options->{filepath} || "/fake/path/for/non/file/template",
1,
scalar @{[$self->{template} =~ m/(\n)/g]} + 1
]);
(*fname, *fcounter, *fmax) = \ ( @{$fstackΎ]} );

my $NOOP = Template::NOOP->new();
my $ESCAPE = Template::ESCAPE->new();
my $URLESCAPE = Template::URLESCAPE->new();

# all the tags that need NAMEs:
my %need_names = map { $_ => 1 }
qw(TMPL_VAR TMPL_LOOP TMPL_IF TMPL_UNLESS TMPL_INCLUDE);

# variables used below that don't need to be my'd in the loop
my ($name, $which, $escape, $default);

# handle the old vanguard format
$options->{vanguard_compatibility_mode} and
$self->{template} =~ s/%([-\w\/\.+]+)%/<TMPL_VAR NAME=$1>/g;

# now split up template on '<', leaving them in
my @chunks = split(m/(?=<)/, $self->{template});

# all done with template
delete $self->{template};

# loop through chunks, filling up pstack
my $last_chunk = $#chunks;
CHUNK: for (my $chunk_number = 0;
$chunk_number <= $last_chunk;
$chunk_number++) {
next unless defined $chunks[$chunk_number]
my $chunk = $chunks[$chunk_number]

# a general regex to match any and all TMPL_* tags
if ($chunk =~ /^<
(?:!--\s*)?
(
\/?[Tt][Mm][Pp][Ll]_
(?:
(?:[Vv][Aa][Rr])
|
(?:[Ll][Oo][Oo][Pp])
|
(?:[Ii][Ff])
|
(?:[Ee][Ll][Ss][Ee])
|
(?:[Uu][Nn][Ll][Ee][Ss][Ss])
|
(?:[Ii][Nn][Cc][Ll][Uu][Dd][Ee])
)
) # $1 => $which - start of the tag

\s*

# DEFAULT attribute
(?:
[Dd][Ee][Ff][Aa][Uu][Ll][Tt]
\s*=\s*
(?:
"([^">]*)" # $2 => double-quoted DEFAULT value "
|
'([^'>]*)' # $3 => single-quoted DEFAULT value
|
([^\s=>]*) # $4 => unquoted DEFAULT value
)
)?

\s*

# ESCAPE attribute
(?:
[Ee][Ss][Cc][Aa][Pp][Ee]
\s*=\s*
(?:
(?: 0 | (?:"0") | (?:'0') )
|
( 1 | (?:"1") | (?:'1') |
(?:[Hh][Tt][Mm][Ll]) |
(?:"[Hh][Tt][Mm][Ll]") |
(?:'[Hh][Tt][Mm][Ll]') |
(?:[Uu][Rr][Ll]) |
(?:"[Uu][Rr][Ll]") |
(?:'[Uu][Rr][Ll]') |
) # $5 => ESCAPE on
)
)* # allow multiple ESCAPEs

\s*

# DEFAULT attribute
(?:
[Dd][Ee][Ff][Aa][Uu][Ll][Tt]
\s*=\s*
(?:
"([^">]*)" # $6 => double-quoted DEFAULT value "
|
'([^'>]*)' # $7 => single-quoted DEFAULT value
|
([^\s=>]*) # $8 => unquoted DEFAULT value
)
)?

\s*

# NAME attribute
(?:
(?:
[Nn][Aa][Mm][Ee]
\s*=\s*
)?
(?:
"([^">]*)" # $9 => double-quoted NAME value "
|
'([^'>]*)' # $10 => single-quoted NAME value
|
([^\s=>]*) # $11 => unquoted NAME value
)
)?

\s*

# DEFAULT attribute
(?:
[Dd][Ee][Ff][Aa][Uu][Ll][Tt]
\s*=\s*
(?:
"([^">]*)" # $12 => double-quoted DEFAULT value "
|
'([^'>]*)' # $13 => single-quoted DEFAULT value
|
([^\s=>]*) # $14 => unquoted DEFAULT value
)
)?

\s*

# ESCAPE attribute
(?:
[Ee][Ss][Cc][Aa][Pp][Ee]
\s*=\s*
(?:
(?: 0 | (?:"0") | (?:'0') )
|
( 1 | (?:"1") | (?:'1') |
(?:[Hh][Tt][Mm][Ll]) |
(?:"[Hh][Tt][Mm][Ll]") |
(?:'[Hh][Tt][Mm][Ll]') |
(?:[Uu][Rr][Ll]) |
(?:"[Uu][Rr][Ll]") |
(?:'[Uu][Rr][Ll]') |
) # $15 => ESCAPE on
)
)* # allow multiple ESCAPEs

\s*

# DEFAULT attribute
(?:
[Dd][Ee][Ff][Aa][Uu][Ll][Tt]
\s*=\s*
(?:
"([^">]*)" # $16 => double-quoted DEFAULT value "
|
'([^'>]*)' # $17 => single-quoted DEFAULT value
|
([^\s=>]*) # $18 => unquoted DEFAULT value
)
)?

\s*

(?:--)?>
(.*) # $19 => $post - text that comes after the tag
$/sx) {

$which = uc($1); # which tag is it

$escape = defined $5 ? $5 : defined $15 ? $15 : 0; # escape set?

# what name for the tag? undef for a /tag at most, one of the
# following three will be defined
$name = defined $9 ? $9 : defined $10 ? $10 : defined $11 ? $11 : undef;

# is there a default?
$default = defined $2 ? $2 : defined $3 ? $3 : defined $4 ? $4 :
defined $6 ? $6 : defined $7 ? $7 : defined $8 ? $8 :
defined $12 ? $12 : defined $13 ? $13 : defined $14 ? $14 :
defined $16 ? $16 : defined $17 ? $17 : defined $18 ? $18 :
undef;

my $post = $19; # what comes after on the line

# allow mixed case in filenames, otherwise flatten
$name = lc($name) unless (not defined $name or $which eq 'TMPL_INCLUDE' or $options->{case_sensitive});

# die if we need a name and didn't get one
die "Template->new() : No NAME given to a $which tag at $fname : line $fcounter."
if ($need_names{$which} and (not defined $name or not length $name));

# die if we got an escape but can't use one
die "Template->new() : ESCAPE option invalid in a $which tag at $fname : line $fcounter." if ( $escape and ($which ne 'TMPL_VAR'));

# die if we got a default but can't use one
die "Template->new() : DEFAULT option invalid in a $which tag at $fname : line $fcounter." if ( defined $default and ($which ne 'TMPL_VAR'));

# take actions depending on which tag found
if ($which eq 'TMPL_VAR') {
$options->{debug} and print STDERR "### Template Debug ### $fname : line $fcounter : parsed VAR $name\n";

# if we already have this var, then simply link to the existing
# Template::VAR, else create a new one.
my $var;
if (exists $pmap{$name}) {
$var = $pmap{$name};
(ref($var) eq 'Template::VAR') or
die "Template->new() : Already used param name $name as a TMPL_LOOP, found in a TMPL_VAR at $fname : line $fcounter.";
} else {
$var = Template::VAR->new();
$pmap{$name} = $var;
$top_pmap{$name} = Template::VAR->new()
if $options->{global_vars} and not exists $top_pmap{$name};
}

# if a DEFAULT was provided, push a DEFAULT object on the
# stack before the variable.
if (defined $default) {
push(@pstack, Template::DEFAULT->new($default));
}

# if ESCAPE was set, push an ESCAPE op on the stack before
# the variable. output will handle the actual work.
if ($escape) {
if ($escape =~ /^"?[Uu][Rr][Ll]"?$/) {
push(@pstack, $URLESCAPE);
} else {
push(@pstack, $ESCAPE);
}
}

push(@pstack, $var);

} elsif ($which eq 'TMPL_LOOP') {
# we've got a loop start
$options->{debug} and print STDERR "### Template Debug ### $fname : line $fcounter : LOOP $name start\n";

# if we already have this loop, then simply link to the existing
# Template::LOOP, else create a new one.
my $loop;
if (exists $pmap{$name}) {
$loop = $pmap{$name};
(ref($loop) eq 'Template::LOOP') or
die "Template->new() : Already used param name $name as a TMPL_VAR, TMPL_IF or TMPL_UNLESS, found in a TMP_LOOP at $fname : line $fcounter!";

} else {
# store the results in a LOOP object - actually just a
# thin wrapper around another Template object.
$loop = Template::LOOP->new();
$pmap{$name} = $loop;
}

# get it on the loopstack, pstack of the enclosing block
push(@pstack, $loop);
push(@loopstack, [$loop, $#pstack]);

# magic time - push on a fresh pmap and pstack, adjust the typeglobs.
# this gives the loop a separate namespace (i.e. pmap and pstack).
push(@pstacks, []);
*pstack = $pstacks[$#pstacks]
push(@pmaps, {});
*pmap = $pmaps[$#pmaps]
push(@ifstacks, []);
*ifstack = $ifstacks[$#ifstacks]
push(@ucstacks, []);
*ucstack = $ucstacks[$#ucstacks]

# auto-vivify __FIRST__, __LAST__ and __INNER__ if
# loop_context_vars is set. Otherwise, with
# die_on_bad_params set output() will might cause errors
# when it tries to set them.
if ($options->{loop_context_vars}) {
$pmap{__first__} = Template::VAR->new();
$pmap{__inner__} = Template::VAR->new();
$pmap{__last__} = Template::VAR->new();
$pmap{__odd__} = Template::VAR->new();
$pmap{__counter__} = Template::VAR->new();
}

} elsif ($which eq '/TMPL_LOOP') {
$options->{debug} and print STDERR "### Template Debug ### $fname : line $fcounter : LOOP end\n";

my $loopdata = pop(@loopstack);
die "Template->new() : found </TMPL_LOOP> with no matching <TMPL_LOOP> at $fname : line $fcounter!" unless defined $loopdata;

my ($loop, $starts_at) = @$loopdata;

# resolve pending conditionals
foreach my $uc (@ucstack) {
my $var = $uc->[Template::COND::VARIABLE]
if (exists($pmap{$var})) {
$uc->[Template::COND::VARIABLE] = $pmap{$var};
} else {
$pmap{$var} = Template::VAR->new();
$top_pmap{$var} = Template::VAR->new()
if $options->{global_vars} and not exists $top_pmap{$var};
$uc->[Template::COND::VARIABLE] = $pmap{$var};
}
if (ref($pmap{$var}) eq 'Template::VAR') {
$uc->[Template::COND::VARIABLE_TYPE] = Template::COND::VARIABLE_TYPE_VAR;
} else {
$uc->[Template::COND::VARIABLE_TYPE] = Template::COND::VARIABLE_TYPE_LOOP;
}
}

# get pmap and pstack for the loop, adjust the typeglobs to
# the enclosing block.
my $param_map = pop(@pmaps);
*pmap = $pmaps[$#pmaps]
my $parse_stack = pop(@pstacks);
*pstack = $pstacks[$#pstacks]

scalar(@ifstack) and die "Template->new() : Dangling <TMPL_IF> or <TMPL_UNLESS> in loop ending at $fname : line $fcounter.";
pop(@ifstacks);
*ifstack = $ifstacks[$#ifstacks]
pop(@ucstacks);
*ucstack = $ucstacks[$#ucstacks]

# instantiate the sub-Template, feeding it parse_stack and
# param_map. This means that only the enclosing template
# does _parse() - sub-templates get their parse_stack and
# param_map fed to them already filled in.
$loop->[Template::LOOP::TEMPLATE_HASH]{$starts_at}
= Template->_new_from_loop(
parse_stack => $parse_stack,
param_map => $param_map,
debug => $options->{debug},
die_on_bad_params => $options->{die_on_bad_params},
loop_context_vars => $options->{loop_context_vars},
case_sensitive => $options->{case_sensitive},
);

} elsif ($which eq 'TMPL_IF' or $which eq 'TMPL_UNLESS' ) {
$options->{debug} and print STDERR "### Template Debug ### $fname : line $fcounter : $which $name start\n";

# if we already have this var, then simply link to the existing
# Template::VAR/LOOP, else defer the mapping
my $var;
if (exists $pmap{$name}) {
$var = $pmap{$name};
} else {
$var = $name;
}

# connect the var to a conditional
my $cond = Template::COND->new($var);
if ($which eq 'TMPL_IF') {
$cond->[Template::COND::WHICH] = Template::COND::WHICH_IF;
$cond->[Template::COND::JUMP_IF_TRUE] = 0;
} else {
$cond->[Template::COND::WHICH] = Template::COND::WHICH_UNLESS;
$cond->[Template::COND::JUMP_IF_TRUE] = 1;
}

# push unconnected conditionals onto the ucstack for
# resolution later. Otherwise, save type information now.
if ($var eq $name) {
push(@ucstack, $cond);
} else {
if (ref($var) eq 'Template::VAR') {
$cond->[Template::COND::VARIABLE_TYPE] = Template::COND::VARIABLE_TYPE_VAR;
} else {
$cond->[Template::COND::VARIABLE_TYPE] = Template::COND::VARIABLE_TYPE_LOOP;
}
}

# push what we've got onto the stacks
push(@pstack, $cond);
push(@ifstack, $cond);

} elsif ($which eq '/TMPL_IF' or $which eq '/TMPL_UNLESS') {
$options->{debug} and print STDERR "### Template Debug ###$fname : line $fcounter : $which end\n";

my $cond = pop(@ifstack);
die "Template->new() : found </${which}> with no matching <TMPL_IF> at $fname : line $fcounter." unless defined $cond;
if ($which eq '/TMPL_IF') {
die "Template->new() : found </TMPL_IF> incorrectly terminating a <TMPL_UNLESS> (use </TMPL_UNLESS>) at $fname : line $fcounter.\n"
if ($cond->[Template::COND::WHICH] == Template::COND::WHICH_UNLESS);
} else {
die "Template->new() : found </TMPL_UNLESS> incorrectly terminating a <TMPL_IF> (use </TMPL_IF>) at $fname : line $fcounter.\n"
if ($cond->[Template::COND::WHICH] == Template::COND::WHICH_IF);
}

# connect the matching to this "address" - place a NOOP to
# hold the spot. This allows output() to treat an IF in the
# assembler-esque "Conditional Jump" mode.
push(@pstack, $NOOP);
$cond->[Template::COND::JUMP_ADDRESS] = $#pstack;

} elsif ($which eq 'TMPL_ELSE') {
$options->{debug} and print STDERR "### Template Debug ### $fname : line $fcounter : ELSE\n";

my $cond = pop(@ifstack);
die "Template->new() : found <TMPL_ELSE> with no matching <TMPL_IF> or <TMPL_UNLESS> at $fname : line $fcounter." unless defined $cond;


my $else = Template::COND->new($cond->[Template::COND::VARIABLE]);
$else->[Template::COND::WHICH] = $cond->[Template::COND::WHICH]
$else->[Template::COND::JUMP_IF_TRUE] = not $cond->[Template::COND::JUMP_IF_TRUE]

# need end-block resolution?
if (defined($cond->[Template::COND::VARIABLE_TYPE])) {
$else->[Template::COND::VARIABLE_TYPE] = $cond->[Template::COND::VARIABLE_TYPE]
} else {
push(@ucstack, $else);
}

push(@pstack, $else);
push(@ifstack, $else);

# connect the matching to this "address" - thus the if,
# failing jumps to the ELSE address. The else then gets
# elaborated, and of course succeeds. On the other hand, if
# the IF fails and falls though, output will reach the else
# and jump to the /if address.
$cond->[Template::COND::JUMP_ADDRESS] = $#pstack;

} elsif ($which eq 'TMPL_INCLUDE') {
# handle TMPL_INCLUDEs
$options->{debug} and print STDERR "### Template Debug ### $fname : line $fcounter : INCLUDE $name \n";

# no includes here, bub
$options->{no_includes} and croak("Template : Illegal attempt to use TMPL_INCLUDE in template file : (no_includes => 1)");

my $filename = $name;

# look for the included file...
my $filepath;
if ($options->{search_path_on_include}) {
$filepath = $self->_find_file($filename);
} else {
$filepath = $self->_find_file($filename,
[File::Spec->splitdir($fstack[-1]Ύ])]
);
}
die "Template->new() : Cannot open included file $filename : file not found."
unless defined($filepath);
die "Template->new() : Cannot open included file $filename : $!"
unless defined(open(TEMPLATE, $filepath));

# read into the array
my $included_template = "";
while(read(TEMPLATE, $included_template, 10240, length($included_template))) {}
close(TEMPLATE);

# call filters if necessary
$self->_call_filters(\$included_template) if @{$options->{filter}};

if ($included_template) { # not empty
# handle the old vanguard format - this needs to happen here
# since we're not about to do a next CHUNKS.
$options->{vanguard_compatibility_mode} and
$included_template =~ s/%([-\w\/\.+]+)%/<TMPL_VAR NAME=$1>/g;

# collect mtimes for included files
if ($options->{cache} and !$options->{blind_cache}) {
$self->{included_mtimes}{$filepath} = (stat($filepath))Η]
}

# adjust the fstack to point to the included file info
push(@fstack, [$filepath, 1,
scalar @{[$included_template =~ m/(\n)/g]} + 1]);
(*fname, *fcounter, *fmax) = \ ( @{$fstack[$#fstack]} );

# make sure we aren't infinitely recursing
die "Template->new() : likely recursive includes - parsed $options->{max_includes} files deep and giving up (set max_includes higher to allow deeper recursion)." if ($options->{max_includes} and (scalar(@fstack) > $options->{max_includes}));

# stick the remains of this chunk onto the bottom of the
# included text.
$included_template .= $post;
$post = undef;

# move the new chunks into place.
splice(@chunks, $chunk_number, 1,
split(m/(?=<)/, $included_template));

# recalculate stopping point
$last_chunk = $#chunks;

# start in on the first line of the included text - nothing
# else to do on this line.
$chunk = $chunks[$chunk_number]

redo CHUNK;
}
} else {
# zuh!?
die "Template->new() : Unknown or unmatched TMPL construct at $fname : line $fcounter.";
}
# push the rest after the tag
if (defined($post)) {
if (ref($pstack[$#pstack]) eq 'SCALAR') {
${$pstack[$#pstack]} .= $post;
} else {
push(@pstack, \$post);
}
}
} else { # just your ordinary markup
# make sure we didn't reject something TMPL_* but badly formed
if ($options->{strict}) {
die "Template->new() : Syntax error in <TMPL_*> tag at $fname : $fcounter." if ($chunk =~ /<(?:!--\s*)?\/?[Tt][Mm][Pp][Ll]_/);
}

# push the rest and get next chunk
if (defined($chunk)) {
if (ref($pstack[$#pstack]) eq 'SCALAR') {
${$pstack[$#pstack]} .= $chunk;
} else {
push(@pstack, \$chunk);
}
}
}
# count newlines in chunk and advance line count
$fcounter += scalar(@{[$chunk =~ m/(\n)/g]});
# if we just crossed the end of an included file
# pop off the record and re-alias to the enclosing file's info
pop(@fstack), (*fname, *fcounter, *fmax) = \ ( @{$fstack[$#fstack]} )
if ($fcounter > $fmax);

} # next CHUNK

# make sure we don't have dangling IF or LOOP blocks
scalar(@ifstack) and die "Template->new() : At least one <TMPL_IF> or <TMPL_UNLESS> not terminated at end of file!";
scalar(@loopstack) and die "Template->new() : At least one <TMPL_LOOP> not terminated at end of file!";

# resolve pending conditionals
foreach my $uc (@ucstack) {
my $var = $uc->[Template::COND::VARIABLE]
if (exists($pmap{$var})) {
$uc->[Template::COND::VARIABLE] = $pmap{$var};
} else {
$pmap{$var} = Template::VAR->new();
$top_pmap{$var} = Template::VAR->new()
if $options->{global_vars} and not exists $top_pmap{$var};
$uc->[Template::COND::VARIABLE] = $pmap{$var};
}
if (ref($pmap{$var}) eq 'Template::VAR') {
$uc->[Template::COND::VARIABLE_TYPE] = Template::COND::VARIABLE_TYPE_VAR;
} else {
$uc->[Template::COND::VARIABLE_TYPE] = Template::COND::VARIABLE_TYPE_LOOP;
}
}

# want a stack dump?
if ($options->{stack_debug}) {
require 'Data/Dumper.pm';
print STDERR "### Template _param Stack Dump ###\n\n", Data::Dumper::Dumper($self->{parse_stack}), "\n";
}

# get rid of filters - they cause runtime errors if Storable tries
# to store them. This can happen under global_vars.
delete $options->{filter};
}

# a recursive sub that associates each loop with the loops above
# (treating the top-level as a loop)
sub _globalize_vars {
my $self = shift;

# associate with the loop (and top-level templates) above in the tree.
push(@{$self->{options}{associate}}, @_);

# recurse down into the template tree, adding ourself to the end of
# list.
push(@_, $self);
map { $_->_globalize_vars(@_) }
map {values %{$_->[Template::LOOP::TEMPLATE_HASH]}}
grep { ref($_) eq 'Template::LOOP'} @{$self->{parse_stack}};
}

# method used to recursively un-hook associate
sub _unglobalize_vars {
my $self = shift;

# disassociate
$self->{options}{associate} = undef;

# recurse down into the template tree disassociating
map { $_->_unglobalize_vars() }
map {values %{$_->[Template::LOOP::TEMPLATE_HASH]}}
grep { ref($_) eq 'Template::LOOP'} @{$self->{parse_stack}};
}

=head2 param()

param() can be called in a number of ways

1) To return a list of parameters in the template :

my @parameter_names = $self->param();


2) To return the value set to a param :

my $value = $self->param('PARAM');

3) To set the value of a parameter :

# For simple TMPL_VARs:
$self->param(PARAM => 'value');

# with a subroutine reference that gets called to get the value
# of the scalar. The sub will recieve the template object as a
# parameter.
$self->param(PARAM => sub { return 'value' });

# And TMPL_LOOPs:
$self->param(LOOP_PARAM =>
[
{ PARAM => VALUE_FOR_FIRST_PASS, ... },
{ PARAM => VALUE_FOR_SECOND_PASS, ... }
...
]
);

4) To set the value of a a number of parameters :

# For simple TMPL_VARs:
$self->param(PARAM => 'value',
PARAM2 => 'value'
);

# And with some TMPL_LOOPs:
$self->param(PARAM => 'value',
PARAM2 => 'value',
LOOP_PARAM =>
[
{ PARAM => VALUE_FOR_FIRST_PASS, ... },
{ PARAM => VALUE_FOR_SECOND_PASS, ... }
...
],
ANOTHER_LOOP_PARAM =>
[
{ PARAM => VALUE_FOR_FIRST_PASS, ... },
{ PARAM => VALUE_FOR_SECOND_PASS, ... }
...
]
);

5) To set the value of a a number of parameters using a hash-ref :

$self->param(
{
PARAM => 'value',
PARAM2 => 'value',
LOOP_PARAM =>
[
{ PARAM => VALUE_FOR_FIRST_PASS, ... },
{ PARAM => VALUE_FOR_SECOND_PASS, ... }
...
],
ANOTHER_LOOP_PARAM =>
[
{ PARAM => VALUE_FOR_FIRST_PASS, ... },
{ PARAM => VALUE_FOR_SECOND_PASS, ... }
...
]
}
);

=cut


sub param {
my $self = shift;
my $options = $self->{options};
my $param_map = $self->{param_map};

# the no-parameter case - return list of parameters in the template.
return keys(%$param_map) unless scalar(@_);

my $first = shift;
my $type = ref $first;

# the one-parameter case - could be a parameter value request or a
# hash-ref.
if (!scalar(@_) and !length($type)) {
my $param = $options->{case_sensitive} ? $first : lc $first;

# check for parameter existence
$options->{die_on_bad_params} and !exists($param_map->{$param}) and
croak("Template : Attempt to get nonexistent parameter '$param' - this parameter name doesn't match any declarations in the template file : (die_on_bad_params set => 1)");

return undef unless (exists($param_map->{$param}) and
defined($param_map->{$param}));

return ${$param_map->{$param}} if
(ref($param_map->{$param}) eq 'Template::VAR');
return $param_map->{$param}[Template::LOOP::PARAM_SET]
}

if (!scalar(@_)) {
croak("Template->param() : Single reference arg to param() must be a hash-ref! You gave me a $type.")
unless $type eq 'HASH' or
(ref($first) and UNIVERSAL::isa($first, 'HASH'));
push(@_, %$first);
} else {
unshift(@_, $first);
}

croak("Template->param() : You gave me an odd number of parameters to param()!")
unless ((@_ % 2) == 0);

# strangely, changing this to a "while(@_) { shift, shift }" type
# loop causes perl 5.004_04 to die with some nonsense about a
# read-only value.
for (my $x = 0; $x <= $#_; $x += 2) {
my $param = $options->{case_sensitive} ? $_[$x] : lc $_[$x]
my $value = $_[($x + 1)]

# check that this param exists in the template
$options->{die_on_bad_params} and !exists($param_map->{$param}) and
croak("Template : Attempt to set nonexistent parameter '$param' - this parameter name doesn't match any declarations in the template file : (die_on_bad_params => 1)");

# if we're not going to die from bad param names, we need to ignore
# them...
next unless (exists($param_map->{$param}));

# figure out what we've got, taking special care to allow for
# objects that are compatible underneath.
my $value_type = ref($value);
if (defined($value_type) and length($value_type) and ($value_type eq 'ARRAY' or ((ref($value) !~ /^(CODE)|(HASH)|(SCALAR)$/) and $value->isa('ARRAY')))) {
(ref($param_map->{$param}) eq 'Template::LOOP') or
croak("Template::param() : attempt to set parameter '$param' with an array ref - parameter is not a TMPL_LOOP!");
$param_map->{$param}[Template::LOOP::PARAM_SET] = &#[email protected]{$value}]
} else {
(ref($param_map->{$param}) eq 'Template::VAR') or
croak("Template::param() : attempt to set parameter '$param' with a scalar - parameter is not a TMPL_VAR!");
${$param_map->{$param}} = $value;
}
}
}

=pod

=head2 clear_params()

Sets all the parameters to undef. Useful internally, if nowhere else!

=cut

sub clear_params {
my $self = shift;
my $type;
foreach my $name (keys %{$self->{param_map}}) {
$type = ref($self->{param_map}{$name});
undef(${$self->{param_map}{$name}})
if ($type eq 'Template::VAR');
undef($self->{param_map}{$name}[Template::LOOP::PARAM_SET])
if ($type eq 'Template::LOOP');
}
}


# obsolete implementation of associate
sub associateCGI {
my $self = shift;
my $cgi = shift;
(ref($cgi) eq 'CGI') or
croak("Warning! non-CGI object was passed to Template::associateCGI()!\n");
push(@{$self->{options}{associate}}, $cgi);
return 1;
}


=head2 output()

output() returns the final result of the template. In most situations
you'll want to print this, like:

print $template->output();

When output is called each occurrence of <TMPL_VAR NAME=name> is
replaced with the value assigned to "name" via param(). If a named
parameter is unset it is simply replaced with ''. <TMPL_LOOPS> are
evaluated once per parameter set, accumlating output on each pass.

Calling output() is guaranteed not to change the state of the
Template object, in case you were wondering. This property is mostly
important for the internal implementation of loops.

You may optionally supply a filehandle to print to automatically as
the template is generated. This may improve performance and lower
memory consumption. Example:

$template->output(print_to => *STDOUT);

The return value is undefined when using the "print_to" option.

=cut

use vars qw(%URLESCAPE_MAP);
sub output {
my $self = shift;
my $options = $self->{options};
local $_;

croak("Template->output() : You gave me an odd number of parameters to output()!")
unless ((@_ % 2) == 0);
my %args = @_;

print STDERR "### Template Memory Debug ### START OUTPUT ", $self->{proc_mem}->size(), "\n"
if $options->{memory_debug};

$options->{debug} and print STDERR "### Template Debug ### In output\n";

# want a stack dump?
if ($options->{stack_debug}) {
require 'Data/Dumper.pm';
print STDERR "### Template output Stack Dump ###\n\n", Data::Dumper::Dumper($self->{parse_stack}), "\n";
}

# globalize vars - this happens here to localize the circular
# references created by global_vars.
$self->_globalize_vars() if ($options->{global_vars});

# support the associate magic, searching for undefined params and
# attempting to fill them from the associated objects.
if (scalar(@{$options->{associate}})) {
# prepare case-mapping hashes to do case-insensitive matching
# against associated objects. This allows CGI.pm to be
# case-sensitive and still work with asssociate.
my (%case_map, $lparam);
foreach my $associated_object (@{$options->{associate}}) {
# what a hack! This should really be optimized out for case_sensitive.
if ($options->{case_sensitive}) {
map {
$case_map{$associated_object}{$_} = $_
} $associated_object->param();
} else {
map {
$case_map{$associated_object}{lc($_)} = $_
} $associated_object->param();
}
}

foreach my $param (keys %{$self->{param_map}}) {
unless (defined($self->param($param))) {
OBJ: foreach my $associated_object (reverse @{$options->{associate}}) {
$self->param($param, scalar $associated_object->param($case_map{$associated_object}{$param})), last OBJ
if (exists($case_map{$associated_object}{$param}));
}
}
}
}

use vars qw($line @parse_stack); local(*line, *parse_stack);

# walk the parse stack, accumulating output in $result
*parse_stack = $self->{parse_stack};
my $result = '';

tie $result, 'Template::PRINTSCALAR', $args{print_to}
if defined $args{print_to} and not tied $args{print_to};

my $type;
my $parse_stack_length = $#parse_stack;
for (my $x = 0; $x <= $parse_stack_length; $x++) {
*line = \$parse_stack[$x]
$type = ref($line);

if ($type eq 'SCALAR') {
$result .= $$line;
} elsif ($type eq 'Template::VAR' and ref($$line) eq 'CODE') {
defined($$line) and $result .= $$line->($self);
} elsif ($type eq 'Template::VAR') {
defined($$line) and $result .= $$line;
} elsif ($type eq 'Template::LOOP') {
if (defined($line->[Template::LOOP::PARAM_SET])) {
eval { $result .= $line->output($x, $options->{loop_context_vars}); };
croak("Template->output() : fatal error in loop output : [email protected]")
if [email protected];
}
} elsif ($type eq 'Template::COND') {
if ($line->[Template::COND::JUMP_IF_TRUE]) {
if ($line->[Template::COND::VARIABLE_TYPE] == Template::COND::VARIABLE_TYPE_VAR) {
if (defined ${$line->[Template::COND::VARIABLE]}) {
if (ref(${$line->[Template::COND::VARIABLE]}) eq 'CODE') {
$x = $line->[Template::COND::JUMP_ADDRESS] if ${$line->[Template::COND::VARIABLE]}->($self);
} else {
$x = $line->[Template::COND::JUMP_ADDRESS] if ${$line->[Template::COND::VARIABLE]};
}
}
} else {
$x = $line->[Template::COND::JUMP_ADDRESS] if
(defined $line->[Template::COND::VARIABLE][Template::LOOP::PARAM_SET] and
scalar @{$line->[Template::COND::VARIABLE][Template::LOOP::PARAM_SET]});
}
} else {
if ($line->[Template::COND::VARIABLE_TYPE] == Template::COND::VARIABLE_TYPE_VAR) {
if (defined ${$line->[Template::COND::VARIABLE]}) {
if (ref(${$line->[Template::COND::VARIABLE]}) eq 'CODE') {
$x = $line->[Template::COND::JUMP_ADDRESS] unless ${$line->[Template::COND::VARIABLE]}->($self);
} else {
$x = $line->[Template::COND::JUMP_ADDRESS] unless ${$line->[Template::COND::VARIABLE]};
}
} else {
$x = $line->[Template::COND::JUMP_ADDRESS]
}
} else {
$x = $line->[Template::COND::JUMP_ADDRESS] if
(not defined $line->[Template::COND::VARIABLE][Template::LOOP::PARAM_SET] or
not scalar @{$line->[Template::COND::VARIABLE][Template::LOOP::PARAM_SET]});
}
}
} elsif ($type eq 'Template::NOOP') {
next;
} elsif ($type eq 'Template::DEFAULT') {
$_ = $x; # remember default place in stack

# find next VAR, there might be an ESCAPE in the way
*line = \$parse_stack[++$x]
*line = \$parse_stack[++$x] if ref $line eq 'Template::ESCAPE';

# either output the default or go back
if (defined $$line) {
$x = $_;
} else {
$result .= ${$parse_stack[$_]};
}
next;
} elsif ($type eq 'Template::ESCAPE') {
*line = \$parse_stack[++$x]
if (defined($$line)) {
$_ = $$line;

# straight from the CGI.pm bible.
s/&/&/g;
s/\"/"/g; #"
s/>/>/g;
s/</</g;
s/'/'/g; #'

$result .= $_;
}
next;
} elsif ($type eq 'Template::URLESCAPE') {
$x++;
*line = \$parse_stack[$x]
if (defined($$line)) {
$_ = $$line;
# Build a char->hex map if one isn't already available
unless (exists($URLESCAPE_MAP{chr(1)})) {
for (0..255) { $URLESCAPE_MAP{chr($_)} = sprintf('%%%02X', $_); }
}
# do the translation (RFC 2396 ^uric)
s!([^a-zA-Z0-9_.\-])!$URLESCAPE_MAP{$1}!g;
$result .= $_;
}
} else {
confess("Template::output() : Unknown item in parse_stack : " . $type);
}
}

# undo the globalization circular refs
$self->_unglobalize_vars() if ($options->{global_vars});

print STDERR "### Template Memory Debug ### END OUTPUT ", $self->{proc_mem}->size(), "\n"
if $options->{memory_debug};

return undef if defined $args{print_to};
return $result;
}

=pod

=head2 query()

This method allow you to get information about the template structure.
It can be called in a number of ways. The simplest usage of query is
simply to check whether a parameter name exists in the template, using
the C<name> option:

if ($template->query(name => 'foo')) {
# do something if a varaible of any type
# named FOO is in the template
}

This same usage returns the type of the parameter. The type is the
same as the tag minus the leading 'TMPL_'. So, for example, a
TMPL_VAR parameter returns 'VAR' from query().

if ($template->query(name => 'foo') eq 'VAR') {
# do something if FOO exists and is a TMPL_VAR
}

Note that the variables associated with TMPL_IFs and TMPL_UNLESSs will
be identified as 'VAR' unless they are also used in a TMPL_LOOP, in
which case they will return 'LOOP'.

C<query()> also allows you to get a list of parameters inside a loop
(and inside loops inside loops). Example loop:

<TMPL_LOOP NAME="EXAMPLE_LOOP">
<TMPL_VAR NAME="BEE">
<TMPL_VAR NAME="BOP">
<TMPL_LOOP NAME="EXAMPLE_INNER_LOOP">
<TMPL_VAR NAME="INNER_BEE">
<TMPL_VAR NAME="INNER_BOP">
</TMPL_LOOP>
</TMPL_LOOP>

And some query calls:

# returns 'LOOP'
$type = $template->query(name => 'EXAMPLE_LOOP');

# returns ('bop', 'bee', 'example_inner_loop')
@param_names = $template->query(loop => 'EXAMPLE_LOOP');

# both return 'VAR'
$type = $template->query(name => ['EXAMPLE_LOOP', 'BEE']);
$type = $template->query(name => ['EXAMPLE_LOOP', 'BOP']);

# and this one returns 'LOOP'
$type = $template->query(name => ['EXAMPLE_LOOP',
'EXAMPLE_INNER_LOOP']);

# and finally, this returns ('inner_bee', 'inner_bop')
@inner_param_names = $template->query(loop => ['EXAMPLE_LOOP',
'EXAMPLE_INNER_LOOP']);

# for non existent parameter names you get undef
# this returns undef.
$type = $template->query(name => 'DWEAZLE_ZAPPA');

# calling loop on a non-loop parameter name will cause an error.
# this dies:
$type = $template->query(loop => 'DWEAZLE_ZAPPA');

As you can see above the C<loop> option returns a list of parameter
names and both C<name> and C<loop> take array refs in order to refer
to parameters inside loops. It is an error to use C<loop> with a
parameter that is not a loop.

Note that all the names are returned in lowercase and the types are
uppercase.

Just like C<param()>, C<query()> with no arguements returns all the
parameter names in the template at the top level.

=cut

sub query {
my $self = shift;
$self->{options}{debug} and print STDERR "### Template Debug ### query(", join(', ', @_), ")\n";

# the no-parameter case - return $self->param()
return $self->param() unless scalar(@_);

croak("Template::query() : Odd number of parameters passed to query!")
if (scalar(@_) % 2);
croak("Template::query() : Wrong number of parameters passed to query - should be 2.")
if (scalar(@_) != 2);

my ($opt, $path) = (lc shift, shift);
croak("Template::query() : invalid parameter ($opt)")
unless ($opt eq 'name' or $opt eq 'loop');

# make path an array unless it already is
$path = [$path] unless (ref $path);

# find the param in question.
my @objs = $self->_find_param(@$path);
return undef unless scalar(@objs);
my ($obj, $type);

# do what the user asked with the object
if ($opt eq 'name') {
# we only look at the first one. new() should make sure they're
# all the same.
($obj, $type) = (shift(@objs), shift(@objs));
return undef unless defined $obj;
return 'VAR' if $type eq 'Template::VAR';
return 'LOOP' if $type eq 'Template::LOOP';
croak("Template::query() : unknown object ($type) in param_map!");

} elsif ($opt eq 'loop') {
my %results;
while(@objs) {
($obj, $type) = (shift(@objs), shift(@objs));
croak("Template::query() : Search path [", join(', ', @$path), "] doesn't end in a TMPL_LOOP - it is an error to use the 'loop' option on a non-loop parameter. To avoid this problem you can use the 'name' option to query() to check the type first.")
unless ((defined $obj) and ($type eq 'Template::LOOP'));

# SHAZAM! This bit extracts all the parameter names from all the
# loop objects for this name.
map {$results{$_} = 1} map { keys(%{$_->{'param_map'}}) }
values(%{$obj->[Template::LOOP::TEMPLATE_HASH]});
}
# this is our loop list, return it.
return keys(%results);
}
}

# a function that returns the object(s) corresponding to a given path and
# its (their) ref()(s). Used by query() in the obvious way.
sub _find_param {
my $self = shift;
my $spot = $self->{options}{case_sensitive} ? shift : lc shift;

# get the obj and type for this spot
my $obj = $self->{'param_map'}{$spot};
return unless defined $obj;
my $type = ref $obj;

# return if we're here or if we're not but this isn't a loop
return ($obj, $type) unless @_;
return unless ($type eq 'Template::LOOP');

# recurse. this is a depth first seach on the template tree, for
# the algorithm geeks in the audience.
return map { $_->_find_param(@_) }
values(%{$obj->[Template::LOOP::TEMPLATE_HASH]});
}

# Template::VAR, LOOP, etc are *light* objects - their internal
# spec is used above. No encapsulation or information hiding is to be
# assumed.

package Template::VAR;

sub new {
my $value;
return bless(\$value, $_Ύ]);
}

package Template::DEFAULT;

sub new {
my $value = $_Ώ]
return bless(\$value, $_Ύ]);
}

package Template::LOOP;

sub new {
return bless([], $_Ύ]);
}

sub output {
my $self = shift;
my $index = shift;
my $loop_context_vars = shift;
my $template = $self->[TEMPLATE_HASH]{$index};
my $value_sets_array = $self->[PARAM_SET]
return unless defined($value_sets_array);

my $result = '';
my $count = 0;
my $odd = 0;
foreach my $value_set (@$value_sets_array) {
if ($loop_context_vars) {
if ($count == 0) {
@{$value_set}{qw(__first__ __inner__ __last__)} = (1,0,$#{$value_sets_array} == 0);
} elsif ($count == $#{$value_sets_array}) {
@{$value_set}{qw(__first__ __inner__ __last__)} = (0,0,1);
} else {
@{$value_set}{qw(__first__ __inner__ __last__)} = (0,1,0);
}
$odd = $value_set->{__odd__} = not $odd;
$value_set->{__counter__} = $count + 1;
}
$template->param($value_set);
$result .= $template->output;
$template->clear_params;
@{$value_set}{qw(__first__ __last__ __inner__ __odd__ __counter__)} =
(0,0,0,0)
if ($loop_context_vars);
$count++;
}

return $result;
}

package Template::COND;

sub new {
my $pkg = shift;
my $var = shift;
my $self = []
$self->[VARIABLE] = $var;

bless($self, $pkg);
return $self;
}

package Template::NOOP;
sub new {
my $unused;
my $self = \$unused;
bless($self, $_Ύ]);
return $self;
}

package Template::ESCAPE;
sub new {
my $unused;
my $self = \$unused;
bless($self, $_Ύ]);
return $self;
}

package Template::URLESCAPE;
sub new {
my $unused;
my $self = \$unused;
bless($self, $_Ύ]);
return $self;
}

# scalar-tying package for output(print_to => *HANDLE) implementation
package Template::PRINTSCALAR;
use strict;

sub TIESCALAR { bless \$_Ώ], $_Ύ] }
sub FETCH { }
sub STORE {
my $self = shift;
local *FH = $$self;
print FH @_;
}
1;
__END__

=head1 FREQUENTLY ASKED QUESTIONS

In the interest of greater understanding I've started a FAQ section of
the perldocs. Please look in here before you send me email.

=over 4

=item 1

Q: Is there a place to go to discuss Template and/or get help?

A: There's a mailing-list for discussing Template at
[email protected] To join:

http://lists.sourceforge.net/.../html-template-users

If you just want to get email when new releases are available you can
join the announcements mailing-list here:

http://lists.sourceforge.net/...ml-template-announce

=item 2

Q: Is there a searchable archive for the mailing-list?

A: Yes, you can find an archive of the SourceForge list here:

http://www.geocrawler.com/...SourceForge/23294/0/

For an archive of the old vm.com list, setup by Sean P. Scanlon, see:

http://bluedot.net/mail/archive/

=item 3

Q: I want support for <TMPL_XXX>! How about it?

A: Maybe. I definitely encourage people to discuss their ideas for
Template on the mailing list. Please be ready to explain to me
how the new tag fits in with Template's mission to provide a
fast, lightweight system for using HTML templates.

NOTE: Offering to program said addition and provide it in the form of
a patch to the most recent version of Template will definitely
have a softening effect on potential opponents!

=item 4

Q: I found a bug, can you fix it?

A: That depends. Did you send me the VERSION of Template, a test
script and a test template? If so, then almost certainly.

If you're feeling really adventurous, Template has a publically
available CVS server. See below for more information in the PUBLIC
CVS SERVER section.

=item 5

Q: <TMPL_VAR>s from the main template aren't working inside a
<TMPL_LOOP>! Why?

A: This is the intended behavior. <TMPL_LOOP> introduces a separate
scope for <TMPL_VAR>s much like a subroutine call in Perl introduces a
separate scope for "my" variables.

If you want your <TMPL_VAR>s to be global you can set the
'global_vars' option when you call new(). See above for documentation
of the 'global_vars' new() option.

=item 6

Q: Why do you use /[Tt]/ instead of /t/i? It's so ugly!

A: Simple - the case-insensitive match switch is very inefficient.
According to _Mastering_Regular_Expressions_ from O'Reilly Press,
/[Tt]/ is faster and more space efficient than /t/i - by as much as
double against long strings. //i essentially does a lc() on the
string and keeps a temporary copy in memory.

When this changes, and it is in the 5.6 development series, I will
gladly use //i. Believe me, I realize [Tt] is hideously ugly.

=item 7

Q: How can I pre-load my templates using cache-mode and mod_perl?

A: Add something like this to your startup.pl:

use Template;
use File::Find;

print STDERR "Pre-loading HTML Templates...\n";
find(
sub {
return unless /\.tmpl$/;
Template->new(
filename => "$File::Find::dir/$_",
cache => 1,
);
},
'/path/to/templates',
'/another/path/to/templates/'
);

Note that you'll need to modify the "return unless" line to specify
the extension you use for your template files - I use .tmpl, as you
can see. You'll also need to specify the path to your template files.

One potential problem: the "/path/to/templates/" must be EXACTLY the
same path you use when you call Template->new(). Otherwise the
cache won't know they're the same file and will load a new copy -
instead getting a speed increase, you'll double your memory usage. To
find out if this is happening set cache_debug => 1 in your application
code and look for "CACHE MISS" messages in the logs.

=item 8

Q: What characters are allowed in TMPL_* NAMEs?

A: Numbers, letters, '.', '/', '+', '-' and '_'.

=item 9

Q: How can I execute a program from inside my template?

A: Short answer: you can't. Longer answer: you shouldn't since this
violates the fundamental concept behind Template - that design
and code should be seperate.

But, inevitably some people still want to do it. If that describes
you then you should take a look at
L<Template::Expr|Template::Expr>. Using
Template::Expr it should be easy to write a run_program()
function. Then you can do awful stuff like:

<tmpl_var expr="run_program('foo.pl')">

Just, please, don't tell me about it. I'm feeling guilty enough just
for writing Template::Expr in the first place.

=item 10

Q: Can I get a copy of these docs in Japanese?

A: Yes you can. See Kawai Takanori's translation at:

http://member.nifty.ne.jp/...ps/html/template.htm

=item 11

Q: What's the best way to create a <select> form element using
Template?

A: There is much disagreement on this issue. My personal preference
is to use CGI.pm's excellent popup_menu() and scrolling_list()
functions to fill in a single <tmpl_var select_foo> variable.

To some people this smacks of mixing HTML and code in a way that they
hoped Template would help them avoid. To them I'd say that HTML
is a violation of the principle of separating design from programming.
There's no clear separation between the programmatic elements of the
<form> tags and the layout of the <form> tags. You'll have to draw
the line somewhere - clearly the designer can't be entirely in charge
of form creation.

It's a balancing act and you have to weigh the pros and cons on each side.
It is certainly possible to produce a <select> element entirely inside the
template. What you end up with is a rat's nest of loops and conditionals.
Alternately you can give up a certain amount of flexibility in return for
vastly simplifying your templates. I generally choose the latter.

Another option is to investigate FillInForm which some have
reported success using to solve this problem.

=back

=head1 BUGS

I am aware of no bugs - if you find one, join the mailing list and
tell us about it. You can join the Template mailing-list by
visiting:

http://lists.sourceforge.net/.../html-template-users

Of course, you can still email me directly ([email protected]) with bugs,
but I reserve the right to forward bug reports to the mailing list.

When submitting bug reports, be sure to include full details,
including the VERSION of the module, a test script and a test template
demonstrating the problem!

If you're feeling really adventurous, Template has a publically
available CVS server. See below for more information in the PUBLIC
CVS SERVER section.

=head1 CREDITS

This module was the brain child of my boss, Jesse Erlbaum
( [email protected] ) at Vanguard Media ( http://vm.com ) . The most original
idea in this module - the <TMPL_LOOP> - was entirely his.

Fixes, Bug Reports, Optimizations and Ideas have been generously
provided by:

Richard Chen
Mike Blazer
Adriano Nagelschmidt Rodrigues
Andrej Mikus
Ilya Obshadko
Kevin Puetz
Steve Reppucci
Richard Dice
Tom Hukins
Eric Zylberstejn
David Glasser
Peter Marelas
James William Carlson
Frank D. Cringle
Winfried Koenig
Matthew Wickline
Doug Steinwand
Drew Taylor
Tobias Brox
Michael Lloyd
Simran Gambhir
Chris Houser <[email protected]>
Larry Moore
Todd Larason
Jody Biggs
T.J. Mather
Martin Schroth
Dave Wolfe
uchum
Kawai Takanori
Peter Guelich
Chris Nokleberg
Ralph Corderoy
William Ward
Ade Olonoh
Mark Stosberg
Lance Thomas
Roland Giersig
Jere Julian
Peter Leonard
Kenny Smith
Sean P. Scanlon
Martin Pfeffer
David Ferrance
Gyepi Sam
Darren Chamberlain

Thanks!

=head1 WEBSITE

You can find information about Template and other related modules at:

http://html-template.sourceforge.net

=head1 PUBLIC CVS SERVER

Template now has a publicly accessible CVS server provided by
SourceForge (www.sourceforge.net). You can access it by going to
http://sourceforge.net/cvs/?group_id=1075. Give it a try!

=head1 AUTHOR

Sam Tregar, [email protected]

=head1 LICENSE

Template : A module for using HTML Templates with Perl
Copyright (C) 2000-2002 Sam Tregar ([email protected])

This module is free software; you can redistribute it and/or modify it
under the terms of either:

a) the GNU General Public License as published by the Free Software
Foundation; either version 1, or (at your option) any later version,

or

b) the "Artistic License" which comes with this module.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either
the GNU General Public License or the Artistic License for more details.

You should have received a copy of the Artistic License with this
module, in the file ARTISTIC. If not, I'll be glad to provide one.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA

=cut