解析原生ASP代码的模板引擎(完善版)
前两天发布过一篇介绍刚写的ASP原生代码解析生成静态的类,由于代码写有匆忙,其中很多细节都没有斟酌。这两天又作了些完善,现在发布新版本出来,希望给大家有些用处。
原文地址:将ASP原生代码编译生成静态类
使用范例代码:
'计算起始时间 Dim StartTime StartTime = Timer '生成对象 Dim C Set C=New xCompile '//这个文件会被预先运行,文件路径写法不重要,比较时是按实际磁盘路径比较的 C.AddInclude "TestFunctions.asp" '//ASP中调用到这个函数的地方会被替换成字符串连接 C.AddOutFun "Echo" C.Param("param")="这是传过去的参数" '//设置是否以全局模式执行 C.GlobalMode = False '//编译文件 C.Compile "Test.asp" C.Clear '//保存编译后的文件到内容 C.SaveTo "index.html" '//输出编译后的内容 Response.Write C '输出执行时间 Response.Write CCur(Timer-StartTime)
下面是新版本的类原码:
'+++++++++++++++++++++++++++++++++++ 'ASP编译类 '可按设定直接将ASP文件编译运行返回结果 '在原生ASP中用来生成静态文件,而不采用获取Http页面的方法 'shirne@126.com 'http://www.shirne.com '+++++++++++++++++++++++++++++++++++ Class xCompile Private Includes '预包含文件 Private Ignores '忽略的包含文件 Private OutFun '输出函数 Private strHtml '最终的HTML字符串 Private Params '参数,仅支持QueryString参数,生成静态不建议使用其它参数 Private FuncStr '定义的全局函数名 Private ParamStr Private Classes '已加载的类列表,ASP中类不能重复加载 Private Prepared Private arrStr '提取出的字符串数组 Private arrStrLength Private StrRep '占位符 Private EndRep Private ClearRep '值:True/False 'True:将在全局模式下执行代码,注意变量与当前文件的变量定义冲突 'False:将把最终代码封装进函数执行,注意其它函数体内不能使用全局变量 Public GlobalMode Private Sub Class_Initialize OutFun = Array("Response.Write") FuncStr = "Outer__Html__Str" ParamStr= "In_Param_Str" Set Classes=Server.CreateObject("Scripting.Dictionary") Classes.CompareMode=1 Set Params=new xDictionary Params.Init Request.QueryString Prepared = -1 GlobalMode = True arrStr = Split(Empty) arrStrLength = UBound(arrStr) StrRep = Chr(1) '字符串占位 EndRep = Chr(3) '结束动作占位 ClearRep=Chr(2) '清除占位 End Sub Private Sub Class_Terminate Classes.RemoveAll Set Classes= Nothing Set Params = Nothing End Sub Public Default Property Get Html Html = strHtml End Property '主要动作是处理预包含文件 Public Property Get Prepare If IsArray(Includes) Then Dim i,L:L=UBound(Includes) If Prepared>=L Then Exit Property For i=Prepared+1 To L Require Includes(i) Next Prepared = L End If End Property '添加/获取参数 Public Property Let Param(key, val) Params.Replace key,val End Property Public Property Get Param(key) Param = Params(key) End Property '添加预包含文件 Public Sub AddInclude( File) Includes = Merge(Includes,CheckTruePath(File)) End Sub '添加忽略文件 Public Sub AddIgnore( File) Ignores = Merge(Ignores,CheckTruePath(File)) End Sub '添加输出函数 Public Sub AddOutFun( Fun) OutFun = Merge(OutFun,Fun) End Sub '保存 Public Sub SaveTo( path) WriteFile path, strHtml End Sub '检查是否磁盘路径,返回磁盘路径,可接收数组 Private Function CheckTruePath( obj) If IsArray(obj) Then Dim i For i=0 To UBound(obj) If InStr(obj(i),":")<1 Then If obj(i)&""<>"" Then obj(i) = Server.MapPath(obj(i)) End If Next CheckTruePath = obj Else obj = obj&"" If InStr(obj,":")>0 Then CheckTruePath = obj Else If obj<>"" Then CheckTruePath = Server.MapPath(obj) End If End If End Function '编译文件 Public Sub Compile(File) '读取文件内容 strHtml = ReadFile(File) If strHtml="" Then Exit Sub End If '非全局模式下防止call调用了直接输出的函数输出内容被遗漏 If GlobalMode = False Then AddOutFun "Call" End If '包含文件 strHtml = Include(strHtml,File) '去除页面指令 strHtml = RegReplace("<%@[^>]+%\>",strHtml,"") '整理换行符 strHtml = Replace(strHtml,Chr(13)&Chr(10),Chr(13)) strHtml = Replace(strHtml,Chr(10),Chr(13)) strHtml = Replace(strHtml,Chr(13),vbCrLf) Dim arrHtml, i, j, k, l i = InStr(strHtml,"<%") If i>0 Then '存在ASP标签,则逐个解析 ReDim arrHtml(0) j = 0 k = 1 Do Until i<1 l = InStr(i+1,strHtml,"%\>") If l<1 Then Err.Raise 7,"ASP语法错误,不正确的闭合标签" ReDim Preserve arrHtml(j+1) arrHtml(j) = FuncStr &" = "& FuncStr &" &"""& StrRep &"""" PutString EscapeHtml(Mid(strHtml,k,i-k)) arrHtml(j+1)=Mid(strHtml,i+2,l-i-2) If Left(arrHtml(j+1),1)="=" Then arrHtml(j+1) = FuncStr &" = "& FuncStr &" &"& EscapeAsp(Mid(arrHtml(j+1),2)) Else arrHtml(j+1) = EscapeAsp(arrHtml(j+1)) End If j = j + 2 k = l + 2 i = InStr(l+1,strHtml,"<%") Loop '最后的html内容 ReDim Preserve arrHtml(j) arrHtml(j) = FuncStr &" = "& FuncStr &" &"""& StrRep &"""" PutString EscapeHtml(Mid(strHtml,l+2)) strHtml = Join(arrHtml,vbCrLf) '执行预包含文件 PrePare If GlobalMode Then strHtml = RestoreString( strHtml, 0) '传递参数到全局 Call GlobalParam '在全局执行,如果在库函数中使用了定义的全局变量,要这样运行 ExecuteGlobal "Dim "& FuncStr & vbCrLf & strHtml & vbCrLf strHtml = Eval(FuncStr) Else '过滤类 strHtml = FilterClass(strHtml) '过滤Sub strHtml = FilterSub(strHtml) '过滤Function strHtml = FilterFunction(strHtml) strHtml = RestoreString( strHtml, 0) '用一个函数包装执行,如果结构严谨,库函数中没有使用全局变量,用这种方式最好 ExecuteGlobal "Function "& FuncStr &"("& ParamStr &")" & vbCrLf & strHtml & vbCrLf &"End Function" strHtml = Eval(FuncStr&"(Params)") End If i = InStr(strHtml, EndRep) j = InStr(strHtml, ClearRep) If (i<j Or j<0) And i>0 Then 'Response.End在前 strHtml = Mid(strHtml, 1, i-1) ElseIf j>0 Then 'Response.Clear在前 If i>0 Then '隐含条件 i>j strHtml = Mid(strHtml, j+1, i-j-1) Else strHtml = Mid(strHtml, j+1) End If End If End If End Sub '清理处理后的内容 Sub Clear Dim i '去除前后空白字符 strHtml = xTrim(strHtml,Chr(32)&Chr(9)&Chr(10)&Chr(13)) '清除注释 strHtml = RegReplace("<!--[\s\S]*?-->",strHtml,"") '清除空行 strHtml = RegExpReplace("^\s*$","gm",strHtml,"") '清理多余的空格 ' i = InStr(strHtml,Chr(32) & Chr(32)) ' While i>0 ' strHtml = Replace(strHtml,Chr(32) & Chr(32),Chr(32)) ' i = InStr(strHtml,Chr(32) & Chr(32)) ' Wend '清理多余的空行 ' i = InStr(strHtml,vbCrLf & vbCrLf) ' While i>0 ' strHtml = Replace(strHtml,vbCrLf & vbCrLf,vbCrLf) ' i = InStr(strHtml,vbCrLf & vbCrLf) ' Wend End Sub '执行预包含文件,忽略asp标签外的所有内容 Private Function Require(File) Dim html, absPath html = ReadFile(File) absPath = Mid(File,Len(Server.MapPath("/"))) html = Include(html,absPath) Dim arrHtml, i, j, k, l i = InStr(html,"<%") If i>0 Then '存在ASP标签,则逐个解析,否则忽略该文件 ReDim arrHtml(0) j = 0 k = 1 Do Until i<1 l = InStr(i+1,html,"%\>") If l<1 Then Err.Raise 7,"ASP语法错误,不正确的闭合标签" ReDim Preserve arrHtml(j+1) arrHtml(j) = "" '忽略所有非asp内容 arrHtml(j+1)=Mid(html,i+2,l-i-2) j = j + 2 k = l + 2 i = InStr(l+1,html,"<%") Loop ExecuteGlobal Join(arrHtml,vbCrLf) End If End Function Private Sub GlobalParam ExecuteGlobal "Dim "& ParamStr & vbCrLf Execute "Set "& ParamStr &"= Params" End Sub '将html代码替换成VBS字符串 Private Function EscapeHtml( html) If InStr(html,"""")>0 Then html = Replace(html,"""","""""") If InStr(html,vbCrLf)>0 Then html = Replace(html,vbCrLf,"""& vbCrLf &""") EscapeHtml = html 'Replace(html,Chr(0),"") End Function '重新编码ASP代码,替换输出函数,替换参数 Private Function EscapeAsp( html) Dim i '去除注释,以免影响下面替换 html = FilterComment(html) For i=0 To UBound(OutFun) '先替换带括号的,这里不够严谨,没有判断括号嵌套的情况 '其实后面带不带括号都可以看作一个整体,所以下面一个替换就好了 'If InStr(1,html,OutFun(i)&"(", 1)>0 Then ' html = RegReplace("\b"& OutFun(i) &"\(([^()]+?)\)",html,FuncStr &" = "& FuncStr &" & $1") 'End If If InStr(1,html,OutFun(i),1)>0 Then html = RegReplace("\b"& OutFun(i) &"\b",html,FuncStr &" = "& FuncStr &" &") End If Next If InStr(1,html,"Request.QueryString(",1)>0 Then html = RegReplace("\bRequest\.QueryString\(",html,ParamStr &"(") End If If InStr(1,html,"Response.End",1)>0 Then html = RegReplace("\bResponse\.End(\(\s*\))?",html,FuncStr &" = "& FuncStr &" &"""& EndRep &"""") End If If InStr(1,html,"Response.Clear",1)>0 Then html = RegReplace("\bResponse\.Clear(\(\s*\))?",html,FuncStr &" = "& FuncStr &" &"""& ClearRep &"""") End If EscapeAsp = html End Function '恢复ASP代码中的字符串 Private Function RestoreString(html, j) Dim i, iEnd, oHtml iEnd = 1 i=InStr(html,StrRep) Do Until i<1 '略过已恢复的 Do While IsEmpty(arrStr(j)) j = j + 1 If j>arrStrLength Then Exit Do Loop oHtml = oHtml & Mid(html, iEnd, i-iEnd) & arrStr(j) '已恢复的设为空 arrStr(j) = Empty iEnd = i + 1 i = InStr(iEnd, html, strRep) j = j + 1 If j>arrStrLength Then Exit Do Loop If iEnd>0 Then oHtml = oHtml & Mid(html, iEnd) RestoreString = oHtml End Function '包含入文件 Private Function Include(html,ByVal path) Dim Matches,Match,iHtml, iPath, oHtml, lastIndex Dim iStart, iEnd Set Matches=REObject("<!--\s*#include\s+(file|virtual)=""([^*?<>=:""|]+)""\s*-->","gi").Execute(html) If Matches.Count>0 Then lastIndex = 1 iStart = 1 iEnd = 1 For Each Match In Matches '存在asp标签,且asp结束标签在Match.FirstIndex之前,则查找下一组asp标签 '直到不存在asp标签或结束标签在Match.FirstIndex之后 '保证当前匹配到的包含语法不在asp标签内部 Do Until iStart<1 Or Match.FirstIndex<iEnd iStart = InStr(iEnd, html, "<%") If iStart>0 Then iEnd = InStr(iStart,html,"%\>") Loop If iStart<1 Or Match.FirstIndex<iStart Then oHtml = oHtml & Mid(html,lastIndex,Match.FirstIndex+1-lastIndex) If StrComp(Match.SubMatches(0),"file",1)=0 Then iPath = getDir(path) & Match.SubMatches(1) ElseIf StrComp(Match.SubMatches(0),"virtual",1)=0 Then iPath = Match.SubMatches(1) Else iPath = "" End If If CheckNeedInclude(iPath) Then iHtml = ReadFile(iPath) iHtml = Include(iHtml,iPath) oHtml = oHtml & iHtml End If Else oHtml = oHtml & Mid(html, lastIndex,Match.FirstIndex+Match.Length+1 - lastIndex) End If lastIndex = Match.FirstIndex+Match.Length+1 Next oHtml = oHtml & Mid(html, lastIndex) Include = oHtml Else Include = html End If End Function '检查是否需要包含 Private Function CheckNeedInclude(ByVal path) CheckNeedInclude = True If path="" Then CheckNeedInclude = False:Exit Function path = CheckTruePath(path) Dim i '先检查预包含文件 If IsArray(Includes) Then For i=0 To UBound(Includes) If StrComp(Includes(i),path,1)=0 Then CheckNeedInclude = False Exit Function End If Next End If '再检查忽略含文件 If IsArray(Ignores) Then For i=0 To UBound(Ignores) If StrComp(Ignores(i),path,1)=0 Then CheckNeedInclude = False Exit Function End If Next End If End Function '移除注释,并提取字符串 Private Function FilterComment( html) Dim intStart, intEnd, intQuot, intPos, ohtml, L L = Len(html) intPos = 1 Do While intPos < L And intPos>0 intQuot=InStr(intPos,html,"'") intStart=Instr(intPos,html,"""") If (intQuot<intStart Or intStart<1) And intQuot>0 Then '是注释 ohtml = ohtml & Mid(html,intPos,intQuot-intPos) '找出注释结尾 intPos = InStr(intQuot,html,vbCrLf) ElseIf (intQuot>intStart Or intQuot<1) And intStart>0 Then '跳过字符串的动作 intEnd=InStr(intStart+1,html,"""") Do While intEnd<L If Mid(html,intEnd+1,1)<>"""" Then Exit Do intEnd=InStr(intEnd+2,html,"""") If intEnd<1 Then Err.Raise 7,"ASP语法错误,未结束的字符串" End If Loop '提取ASP代码 ohtml = ohtml & Mid(html,intPos,intStart-intPos+1) If intEnd - intPos > 1 Then '提取字符串 PutString Mid(html,intStart+1,intEnd-intStart-1) ohtml = ohtml & StrRep &"""" '字符串占位 Else '空字符串无需提取 ohtml = ohtml & """" End If intPos = intEnd+1 Else '没有字符串,没有注释 Exit Do End If Loop If intPos>0 Then ohtml = ohtml & Mid(html,intPos) FilterComment = ohtml End Function Private Sub PutString( str) arrStrLength = arrStrLength + 1 ReDim Preserve arrStr(arrStrLength) arrStr(arrStrLength) = str End Sub Private Function getIndex(html, iPos,ByVal i,ByVal j) i = InStr(i + 1, html, strRep) Do Until i<1 Or i>iPos i = InStr(i+1, html, strRep) j = j + 1 If j>arrStrLength Then Exit Do Do While IsEmpty(arrStr(j)) j = j + 1 If j>arrStrLength Then Exit Do Loop Loop getIndex = j End Function '移除类,并尝试在全局执行移除的类 Private Function FilterClass( html) Dim Matches,Match,ClassName,oHtml,LastIndex, ClassStr, iStart Set Matches=REObject("\bClass\s+([\w\d\_]+)\b[\s\S]+?\bEnd\s+Class\b","ig").Execute(html) If Matches.Count>0 Then iStart = 0 LastIndex = 1 For Each Match In Matches oHtml = oHtml & Mid(html,LastIndex,Match.FirstIndex+1-LastIndex) ClassName = Match.SubMatches(0) If Classes.Exists(ClassName)=False Then ClassStr = Match.Value iStart = getIndex(html,Match.FirstIndex,LastIndex-1,iStart) ClassStr = RestoreString(ClassStr, iStart) '直接执行类代码,没有处理类内部的属性,函数等功能的输出,尽量事先处理好 ExecuteGlobal ClassStr Classes.Add ClassName,1 End If LastIndex = Match.FirstIndex+Match.Length+1 Next oHtml = oHtml & Mid(html, LastIndex) FilterClass = oHtml Else FilterClass = html End If End Function '移除Sub,并尝试将Sub转换为Function在全局执行 Private Function FilterSub( html) Dim Matches,Match,SubName,SubStr,oHtml,LastIndex, iStart Set Matches=REObject("\bSub\s+([\w\d\_]+)\b[\s\S]+?\bEnd\s+Sub\b","ig").Execute(html) If Matches.Count>0 Then iStart = 0 LastIndex = 1 For Each Match In Matches oHtml = oHtml & Mid(html,LastIndex,Match.FirstIndex+1-LastIndex) SubName = Match.SubMatches(0) '将Sub替换成Function SubStr = RegReplace("\bSub\b",Match.Value,"Function") SubStr = RegReplace("\b"& FuncStr &"\b",SubStr,SubName) iStart = getIndex(html,Match.FirstIndex,LastIndex-1,iStart) SubStr = RestoreString(SubStr, iStart) ExecuteGlobal SubStr LastIndex = Match.FirstIndex+Match.Length+1 Next oHtml = oHtml & Mid(html, lastIndex) FilterSub = oHtml Else FilterSub = html End If End Function '移除函数,并尝试在全局执行移除的函数 Private Function FilterFunction( html) Dim Matches,Match,FunctionName,FunctionStr,oHtml,LastIndex, iStart Set Matches=REObject("\bFunction\s+([\w\d\_]+)\b[\s\S]+?\bEnd\s+Function\b","ig").Execute(html) If Matches.Count>0 Then iStart = 0 LastIndex = 1 For Each Match In Matches oHtml = oHtml & Mid(html,LastIndex,Match.FirstIndex+1-LastIndex) FunctionName = Match.SubMatches(0) FunctionStr = RegReplace("\b"& FuncStr &"\b",Match.Value,FunctionName) iStart = getIndex(html,Match.FirstIndex,LastIndex-1,iStart) FunctionStr = RestoreString(FunctionStr, iStart) ExecuteGlobal FunctionStr LastIndex = Match.FirstIndex+Match.Length+1 Next oHtml = oHtml & Mid(html, lastIndex) FilterFunction = oHtml Else FilterFunction = html End If End Function End Class
下载地址:解析原生ASP代码的模板引擎(完善版)