%
' +----------------------------------------------------------------------
' | POPASP [ ASP MVC ]
' +----------------------------------------------------------------------
' | Copyright (c) 2016 http://popasp.com All rights reserved.
' +----------------------------------------------------------------------
' | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
' +----------------------------------------------------------------------
' | Author: popasp <1737025626@qq.com>
' +----------------------------------------------------------------------
Class POPASP_TEMPLATE
public tpl_vars
'是否启用模板布局
Public layout_on
'模板布局文件
Public layout_file
'模板布局替换标签
Public layout_label
'不需要模板布局的替换标签
Public nolayout_label
' The name of the diretory where templates are located
' @var string
public template_dir
' The name of the directory for cache files.
' @var string
public cache_dir
' The left delimiter used for the template tags.
' 用于Simtpl_Compiler类
' @var string
public left_delimiter
' The right delimiter used for the template tags.
' 用于Simtpl_Compiler类
' @var string
public right_delimiter
' This is the number of seconds cached content will persist.
'
' - -1 = 永久缓存
' - 0 = 不缓存
' - N = 缓存N秒钟
'
' @var integer
public cache_lifetime
' 模板文件名的后缀
public tpl_suffix
' 是否使用带有控制器样式的tpl名称
public use_ctrl_tpl
' 文件是否过期的数字表现形式,-1是重新生成,0为过期,1为在缓存期内
Private is_cached__
Private Pub_tpl,Pub_id
' assigns values to template variables
' @param Dictionary|string tpl_var the template variable name(s)
' @param mixed value the value to assign
public Sub assign( tpl_var, value )
on error resume next
dim arr,stype,bool 'arr为dictionary对象
dim dict,i,temp
stype = typename(tpl_var)
set arr = D_
if stype = "Dictionary" Then '直接分配Dictionary,此时Value值无效,赋值时随便给个什么
set arr = POP_MVC.dict.clone( tpl_var )
elseif stype = "Recordset" Then
for i = 0 to tpl_var.fields.count-1
arr( tpl_var.Fields(i).Name ) = tpl_var.Fields(i).Value
next
elseif stype = "String" Then '既有变量名,又有变量值
bool = false
if isArray(value) then
if ubound(value)>=0 then
'如果第一个元素为Recordset,格式应为 Array( rs,需删除前缀的字符串,是否转小写 )
if typename( value(0) ) = "Recordset" then
POP_MVC.dict.edit arr,tpl_var,rs2dict(value)
bool = true
'如果Array(...,rs)
elseif typename( value( ubound(value) ) ) = "Recordset" then
'如果第一个元素为1,则按 POP_MVC.rs2dict( array(rs) )处理
if TypeName( value(0) ) = "Integer" then
if value(0) = 1 then
POP_MVC.dict.edit arr,tpl_var,POP_MVC.rs2dict( array( value( ubound(value) ) ) )
bool = true
end if
else '对应格式Array( 需删除前缀的字符串,是否转小写,rs )
set temp = value( ubound(value) )
call POP_MVC.Arr.Pop( value )
call POP_MVC.Arr.unshift( value , temp )
POP_MVC.dict.edit arr,tpl_var,POP_MVC.rs2dict( value )
bool = true
end if
end if
end if
end if
if not bool then
select case typename(value)
case "Recordset"
POP_MVC.dict.edit arr,tpl_var,POP_MVC.rs2dict(value)
case "Field"
POP_MVC.dict.edit arr,tpl_var,value.value
case else
POP_MVC.dict.edit arr,tpl_var,value
end select
end if
End If
set tpl_vars = POP_MVC.dict.merge(tpl_vars,arr)
set arr = nothing
call L_("POPASP_TEMPLATE.assign")
End Sub 'end Sub assign
' test to see if valid cache exists for this template
' @param string id
' if cached return false , or true
Public function expired( byval id )
dim tpl
if cache_lifetime <= 0 then
expired = true
exit function
end if
'模板文件为入口文件名
tpl = POP_MVC.File.baseName( array( Request.ServerVariables( "SCRIPT_NAME" ) , true ) )
'过滤掉不能为文件名的字符
if id <> "" then
id = POP_MVC.String.reg_replace( id , "" , "[:|/\\*<>?""]" , "g" )
end if
Pub_tpl = tpl
Pub_id = id
expired = ( not is_data_cached( tpl,id ) )
call data_cache_get_contents( tpl ,id )
End Function
' test to see if valid cache exists for this template
' @param string id
' if cached return true , or false
Public function is_cached( byval id )
dim tpl
'模板文件为入口文件名
tpl = POP_MVC.File.baseName( array( Request.ServerVariables( "SCRIPT_NAME" ) , true ) )
'过滤掉不能为文件名的字符
if id <> "" then
id = POP_MVC.String.reg_replace( id , "" , "[:|/\\*<>?""]" , "g" )
end if
is_cached = is_data_cached( tpl,id )
End Function
'清空与模板引擎相关的文件夹, cache_dir
Public Sub clear()
if C_("DATA_CACHE_FOLDER") <> "" then
call POP_MVC.file.remove( C_("DATA_CACHE_FOLDER") )
else
call POP_MVC.file.remove( cache_dir )
end if
End Sub
'加载模板文件
Public Property Get [Load]( byval tpl,byval id )
if tpl = "" Then
tpl = POP_MVC.Url.get_action_name()
End If
call data_cache_get_contents( tpl ,id )
End Property
'显示模板文件
Public Property Get Display ( tpl )
if tpl = "" Then
tpl = POP_MVC.Url.get_action_name()
End If
Response.Write get_parse_context(tpl)
End Property
'获取模板文件的路径
Private Function get_template_file( ByVal tpl )
Dim template_file
if POP_MVC.String.iEndsWith( template_dir , "popasp/Tpl/" ) then '如果用的是系统跳转
template_file = template_dir & tpl & tpl_suffix
else
tpl = get_tpl_name( tpl )
template_file = template_dir & tpl
end if
if Not file_exists(template_file) Then '如果模板文件不存在
get_template_file = Empty
Else
get_template_file = template_file
get_template_file = replace( get_template_file,"/","\" )
End If
End Function
' 获取tpl的完整名称
' 为了得到这样的index/header.html,可以采用下列几种方式输入
' index/header.html
' index/header
' header
function get_tpl_name( ByVal tpl )
dim c,a
tpl = Replace(tpl, tpl_suffix, "" )
if use_ctrl_tpl Then
c = get_ctrl_name(tpl)
a = get_action_name(tpl)
tpl = c & "/" & a
End If
tpl = tpl & tpl_suffix
get_tpl_name = tpl
End Function
'获得复杂的文件名
Function get_poptpl_file_name ( tpl , id )
dim str,length,ret
str = replace( id, " " , "" )
str = replace( str, VBCRLF , "" )
if str = "" then
ret = Replace(Replace(tpl,"/","_"),TPL_SUFFIX,"")
elseif POP_MVC.String.reg_test( str,"^[^:|/\\*<>?""]{1,128}$","" ) then 'windows下完全限定文件名必须少于260个字符,目录名必须小于248个字符。
ret = Replace(Replace(tpl,"/","_"),TPL_SUFFIX,"") & "_" & str
else
ret = Replace(Replace(tpl,"/","_"),TPL_SUFFIX,"") & "_" & md5(str)
end if
get_poptpl_file_name = ret
End Function
Function filemtime(filename)
filemtime = POP_MVC.File.mtime(filename)
End Function
'如果date1>date2返回正,否则返回负数
Function dateCompare( date1,date2 )
dateCompare = DateDiff("s",date2,date1)
End Function
'判断文件或目录是否存在
Function file_exists( fileName )
file_exists = POP_MVC.File.isExists(fileName)
End Function
' 将数据缓存中的文件内容,与POP_MVC.tpl_vars合并,后分配的会覆盖模板中已经有的
Private sub data_cache_get_contents( byRef tpl,byRef id)
if file_exists( get_data_cache_file(tpl,id) ) Then
dim temp
set temp = js_decode(POP_MVC.file_get_contents( get_data_cache_file(tpl,id) ))
set tpl_vars = POP_MVC.Dict.Merge( tpl_vars,temp )
End If
End Sub 'end Sub data_cache_get_contents
'判断是否数据缓存
' @param string $tpl
' @如果开启数据缓存,且数据缓存有效,则返回 True
Private function is_data_cached( byval tpl,byval id)
if cache_lifetime <= 0 then
is_data_cached = false
exit function
end if
dim data_cache_file
data_cache_file = get_data_cache_file(tpl,id) '得到数据缓存文件路径
' 缓存文件不存在
if Not file_exists(data_cache_file) Then
is_data_cached = false
is_cached__ = -1 '需要重新生成数据文件
Exit Function
' 缓存文件过期
ElseIf dateCompare(now(),filemtime(data_cache_file)) > cache_lifetime Then
is_data_cached = false
is_cached__ = 0 '需要更新数据文件中的数据
Exit Function
End If
is_data_cached = true
is_cached__ = 1
End Function
' 得到数据缓存文件路径
Private function get_data_cache_file( byVal tpl , byVal id )
dim dstname,path
dstname = tpl
if id <> "" then
dstname = dstname & "/" & POP_MVC.trim(POP_MVC.trim(id , "/" ) , "\")
end if
if C_("DATA_CACHE_FOLDER") = "" Then
path = POP_MVC.appPath & "/Runtime/Cache/" & dstname & ".asp"
else
path = POP_MVC.rtrim( POP_MVC.rtrim( C_("DATA_CACHE_FOLDER") , "/" ) , "\" ) & "\" & dstname & ".asp"
end if
get_data_cache_file = path
End Function 'end function get_data_cache_file
' 获取字典对象中的元素个数
Private Function count( byref dict )
count = dict.Count
End Function
Private Function get_parse_layout_include( context ,first )
on error resume next
dim matches,pattern
dim layout_on_ : layout_on_ = layout_on
dim layout_file_ : layout_file_ = layout_file
dim layout_context
dim include_file,include_context
dim i,j : j=0
dim bool:bool = false
if inStr( context,nolayout_label ) > 0 then
context = replace( context,nolayout_label,"" ) '替换 nolayout_label
context = replace( context,layout_label,"" ) '替换 layout_label
else
pattern = left_delimiter & "\s*layout\s+(?:file\s*=\s*|name\s*=\s*)?(""|" & chr(39) & "|)(.*?)\1\s*" & right_delimiter
' layout 文件的处理
j = 0
do while ( POP_MVC.String.reg_test(context,pattern,"i") and j<100 )
set matches = POP_MVC.reg.Execute( context )
layout_file_ = matches(0).SubMatches(1)
layout_file_ = get_template_file(layout_file_) '得到layout文件路径
if not isEmpty( layout_file_ ) then '文件存在
layout_context = POP_MVC.file_get_contents(layout_file_) '得到layout文件内容
if NOT POP_MVC.String.exists( layout_context , layout_label ) then
Call POP_MVC.Warning( "在模板布局文件 " & layout_file_ & " 中未找到替换标签 " & layout_label )
else
context = replace( layout_context,layout_label,context ) '替换{__CONTENT__}
context = replace( context, matches(0), "" )
end if
bool = true
else '文件不存在
context = replace( context,matches(0),"" ) '替换
Call POP_MVC.Warning( "布局文件 " & layout_file_ & " 不存在")
end if
j = j+1
loop
if layout_on_ and layout_file_ <> "" and not bool and first then
layout_file_ = get_template_file(layout_file_) '得到layout文件路径
if not isEmpty( layout_file_ ) then '文件存在
layout_context = POP_MVC.file_get_contents(layout_file_) '得到layout文件内容
if NOT POP_MVC.String.exists( layout_context , layout_label ) then
Call POP_MVC.Warning( "在模板布局文件 " & layout_file_ & " 中未找到替换标签 " & layout_label )
else
context = replace( layout_context,layout_label,context ) '替换{__CONTENT__}
end if
else
Call POP_MVC.Warning( "布局文件 " & layout_file_ & " 不存在")
end if
end if
end if
'include文件的处理
pattern = left_delimiter & "\s*include\s+(?:file\s*=\s*)?(""|" & chr(39) & "|)(.*?)\1\s*" & right_delimiter
do while (POP_MVC.String.reg_test(context,pattern,"gi") and j<2000 )
set matches = POP_MVC.reg.Execute( context )
for i = 0 to matches.count-1
include_file = matches(i).SubMatches(1)
include_file = get_template_file(include_file) '得到 include 文件路径
if not isEmpty( include_file ) then
include_context = POP_MVC.file_get_contents(include_file) '得到 include 文件内容
context = replace( context,matches(i),include_context ) '替换
else
context = replace( context,matches(i),"" ) '替换
Call POP_MVC.Warning( "include 文件 " & matches(i).SubMatches(1) & " 不存在")
end if
next
j = j+1
loop
set matches = nothing
get_parse_layout_include = context
call L_("POPASP_TEMPLATE.get_parse_layout_include")
end Function
function get_tpl_context( tpl )
dim template_file,context
template_file = get_template_file(tpl) '得到模板文件路径
context = POP_MVC.file_get_contents(template_file) '得到模板文件内容
context = get_parse_layout_include( context ,true )
context = get_parse_layout_include( context , false )
context = get_parse_layout_include( context , false )
context = get_parse_layout_include( context , false )
get_tpl_context = context
end function
' sets PHP tag to the compiled source
' @param string $tpl(template file)
Private function get_parse_context ( tpl )
on error resume next
Call G_("compileStartTime")
dim template_file,context,newtext
dim compiler
template_file = get_template_file(tpl) '得到模板文件路径
context = get_tpl_context( tpl )
' 下面要使用解析器对模板文件进行解析了
set compiler = P_("TEMPLATE_COMPILER")
compiler.init left_delimiter,right_delimiter
compiler.set "template",get_tpl_name(tpl)
compiler.set "compiler",get_poptpl_file_name(tpl,"") & ".asp"
newtext = compiler.compile( context )
'解析文件内容完成
if ( ""<>trim(context) AND ""=trim(newtext)) Then
Call POP_MVC.Exit( "模板引擎不能正常解析" & template_file & "文件,出现该问题的原因有可能是:1,文件编码未采用无BOM的utf-8;2,未依据poptpl要求进行编写模板文件" )
End If
get_parse_context = newtext
Call G_("compileEndTime")
call L_("POPASP_TEMPLATE.get_parse_context")
End Function 'end function get_parse_context
'从模板名中得到控制器名称
Private Function get_ctrl_name(tpl_name)
get_ctrl_name = POP_MVC.get_ctrl_name(tpl_name)
End Function
'从模板名中得到操作名称
Private Function get_action_name(tpl_name)
get_action_name = POP_MVC.get_action_name(tpl_name)
End Function
Public Sub Cache()
if cache_lifetime <= 0 then
exit sub
end if
if not isEmpty( is_cached__ ) then
if is_cached__ <= 0 then
if typename(tpl_vars) = "Dictionary" then
Call POP_MVC.file_put_contents(get_data_cache_file(Pub_tpl,Pub_id),js_encode(tpl_vars))
end if
end if
end if
end sub
Private Sub Class_Terminate
set tpl_vars = nothing
End Sub
Private Sub Class_Initialize
set tpl_vars = POP_MVC.Dict.Create()
'是否启用模板布局
layout_on = false
layout_label = "{__CONTENT__}"
nolayout_label = "{__NOLAYOUT__}"
'模板布局文件
layout_file = ""
' The name of the diretory where templates are located
template_dir = "./templates/"
' The name of the directory for cache files.
cache_dir = "./cache/"
' The left delimiter used for the template tags.
left_delimiter = "{"
' The right delimiter used for the template tags.
right_delimiter = "}"
' This is the number of seconds cached content will persist.
cache_lifetime = 3600
' 模板文件名的后缀
tpl_suffix = ".html"
' 是否使用带有控制器样式的tpl名称
use_ctrl_tpl = false
call init
End Sub
' 将Recordset结果集转化成Dictionary,每行记录的键名均为数字类型,且为Dictionary类型
Public Function rs2dict(byref arg)
on error resume next
dim rs,prefix,bLcase,bound,i,k,m,key,dict,start,sql,j:j=0
set rs2dict = D_
'如果是数组,则第一个元素对应rs,第二个对应前缀,第三个对应是否将名称转为小写
if isArray( arg ) then
bound = ubound(arg)
set rs = arg(0)
if bound > 1 then
prefix = arg(1)
bLcase = arg(2)
elseif bound > 0 then
prefix = arg(1)
bLcase = C_("TMPL_ASSIGN_RS_BLCASE")
elseif bound = 0 then
prefix = C_("TMPL_ASSIGN_RS_PREFIX")
bLcase = C_("TMPL_ASSIGN_RS_BLCASE")
else
POP_MVC.Exit( "POPASP_MVC.rs2dict分配参数错误" )
end if
elseif typename(arg) = "Recordset" then
set rs = arg
prefix = ""
bLcase = False
end if
if not isArray(prefix) then
prefix = split(prefix,",")
end if
bound = ubound( prefix )
'如果显示控制台,则进行计时
if not is_empty( C_("SHOW_PAGE_TRACE") ) Then
start = timer()
end if
'如果匹配出top1 或者limit 1,则取出一条记录,返回一维的Dictionary对象
if POP_MVC.String.reg_test(rs.Source,"^\s*SELECT\s+TOP\s+1\s+.+|^\s*SELECT\s+.*?LIMIT\s+1\s*;?$","gi") then
set dict = D_
for i = 0 to rs.fields.count-1
key = rs.Fields(i).Name
'去掉前缀
if bound>=0 then
for m = 0 to bound
if POP_MVC.String.StartsWith(key, prefix(m) ) then
key = mid(key, len( prefix(m)) +1 )
exit for
end if
next
end if
'将键名转为小写
if not is_empty(bLcase) then
key = LCase(key)
end if
if LCase(C_("DB_TYPE")) = "mysql" then
Call typename( rs.Fields(i).Value )
if err.number = 458 then
if IsNumeric(( CLng(rs.Fields(i).Value) )) then
POP_MVC.Dict.Edit dict,key,CLng(rs.Fields(i).Value)
else
POP_MVC.Exit( "发现了不能被ASP解析的MySQL数据类型,请联系POPASP作者,以解决此BUG" )
end if
err.clear
else
POP_MVC.Dict.Edit dict,key,rs.Fields(i).Value
end if
else
POP_MVC.Dict.Edit dict,key,rs.Fields(i).Value
end if
next
set rs2dict = dict
else '取出二维的Dictionary对象
set rs2dict = D_
if rs.recordCount <> 0 then
for k = 1 to rs.pageSize
if not rs.BOF and not rs.EOF Then
set dict = D_
for i = 0 to rs.fields.count-1
key = rs.Fields(i).Name
'去掉前缀
if bound>=0 then
for m = 0 to bound
if POP_MVC.String.StartsWith(key, prefix(m) ) then
key = mid(key, len( prefix(m)) +1 )
exit for
end if
next
end if
'将键名转为小写
if not is_empty(bLcase) then
key = LCase(key)
end if
if LCase(C_("DB_TYPE")) = "mysql" then
Call typename( rs.Fields(i).Value )
if err.number = 458 then
if IsNumeric(( CLng(rs.Fields(i).Value) )) then
POP_MVC.Dict.Edit dict,key,CLng(rs.Fields(i).Value)
else
POP_MVC.Exit( "发现了不能被ASP解析的MySQL数据类型,请联系POPASP作者,以解决此BUG" )
end if
err.clear
else
POP_MVC.Dict.Edit dict,key,rs.Fields(i).Value
end if
else
POP_MVC.Dict.Edit dict,key,rs.Fields(i).Value
end if
next
rs2dict.add j,dict
rs.MoveNext
j = j+1
end If
next
end if
end if
if not is_empty( C_("SHOW_PAGE_TRACE") ) Then '保存sql信息
set sql = D_
sql("time") = round((timer() - start) * 1000,0)
sql("sql") = rs.Source & " ; -- Recordset to Dictionary "
POP_MVC.Dict.Push POP_MVC.dSql,"",sql
end if
set dict = nothing
call L_("POPASP_TEMPLATE.rs2dict")
End Function
Private Sub init()
dim key,temp
layout_on = C_("TMPL_LAYOUT_ON")
layout_file = C_("TMPL_LAYOUT_FILE")
layout_label = C_("TMPL_LAYOUT_LABEL")
nolayout_label = C_("TMPL_NOLAYOUT_LABEL")
cache_lifetime = C_("TMPL_CACHE_LIFETIME")
left_delimiter = POP_MVC.getConfig("TMPL_L_DELIM")
right_delimiter = POP_MVC.getConfig("TMPL_R_DELIM")
tpl_suffix = POP_MVC.getConfig("TMPL_TEMPLATE_SUFFIX")
'下面这几项是MVC配置好的
template_dir = POP_MVC.appPath & "/Tpl/"
cache_dir = POP_MVC.appPath & "/Runtime/Cache/"
End sub
End Class
%>