<% ' +---------------------------------------------------------------------- ' | 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. ' ' @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 %>