クラシックASPのコーディング考慮点 その2
If文の書き方
On Error Resume Nextの動作について。
If文の評価式で実行時エラーが発生した場合の動作を確認してみた。
On Error Resume Next If Foo() = True Then Response.Write "<p>returned True.</p>" Else Response.Write "<p>returned False.</p>" End If Function Foo() Err.Raise 5 'Foo = False End Function
Foo関数で実行時エラーが発生しているので、Falseの評価をしてほしいのだが、実際は「returned True.」と表示される。
この点を考慮すると、vbscriptでIf文を使う場合は、以下のようにするとよいだろう。
If 「エラーかどうかの判定」 Then 'エラー時の処理 Else '正常時の処理 End If
具体的には、
If Not Foo() Then Response.Write "<p>returned False.</p>" Else Response.Write "<p>returned True.</p>" End If
となる。
同様にDo While文でも評価式で実行時エラーが発生するとループの中の処理が実行されるので注意する。
ちなみに、Select Case文だと、End Select行の次行から処理される。
入力値の表示と復元
PostメソッドでFormデータをsubmitすることが前提。
Request.Formから入力値をそれぞれ取得するのは効率が悪い。 表示時に使用したgDataに復元できる関数を用意する。
' Request.FormからCollectionにデータを復元する。 ' arListId : 一覧識別子の配列 Function CreateFormCollection(arListId) Dim i Dim o Dim listCnt Dim colNmInfo Dim isList,lstId,idx, childNm Dim nm Set o = CreateCollection() listCnt = UBound(arListId) + 1 For i = 0 To listCnt - 1 o.Add arListId(i), CreateCollection() Next isList = False lstId = "" idx = 0 childNm = "" For Each nm In Request.Form isList = False For i = 0 To listCnt - 1 '一覧識別子 + "_" + index + "_" + name の分解 Set colNmInfo = GetIndexAndNameFromNameInList(nm, arListId(i)) If colNmInfo.Count > 0 Then isList = True lstId = arListId(i) idx = colNmInfo("idx") childNm = colNmInfo("nm") End If Next If Not isList Then o.Add nm, Request.Form(nm) Else If Not o(lstId).Exists(idx) Then o(lstId).Add idx, CreateCollection() End If o.Item(lstId)(idx).Add childNm, Request.Form(nm) End If Next Set CreateFormCollection = o End Function Const LISTID_SEPARATOR = "_" '一覧識別子 + "_" + index + "_" + name の分解 Function GetIndexAndNameFromNameInList(nm, lstId) Dim o Dim idx, childNm Set o = CreateCollection() If Mid(nm, 1, Len(lstId) + 1) = lstId & LISTID_SEPARATOR Then If InStr((Len(lstId) + 1) + 1, nm, LISTID_SEPARATOR) > (Len(lstId) + 1) + 1 Then idx = CLng(Mid(nm, Len(lstId) + 1 + 1 _ , InStr(Len(lstId) + 1 + 1, nm, LISTID_SEPARATOR) _ - (Len(lstId) + 1) - 1 _ )) childNm = Mid(nm, Len(lstId & LISTID_SEPARATOR & idx & LISTID_SEPARATOR) + 1) o.Add "idx", idx o.Add "nm", childNm End If End If Set GetIndexAndNameFromNameInList = o End Function
簡単なものであるが、一覧データを復元できるような仕組みにしている。 1階層のみで一覧の中の一覧は対応していない。
使用例
<% '省略 Sub InitializePage() TemplateInitializePage Dim i Set gData = CreateFormCollection(Array("lst1", "lst2")) If gData("lst1").Count = 0 Then For i = 0 To 5 - 1 gData("lst1").Add i, CreateCollection() gData("lst1")(i).Add "txtCol1", Right("0000" & (i+1), 4) gData("lst1")(i).Add "txtCol2", "あいうえお" & i Next End If End Sub Sub RenderHtml() If Not gNoErrorHandlingForDebug Then On Error Resume Next End If Dim i, s, row, nm %> <html> <head> <title>sample04</title> </head> <body> <form name="f" method="post"> <input type="text" name="txtVal1" value="<%= Server.HTMLEncode(gData("txtVal1")) %>"><br> <input type="text" name="txtVal2" value="<%= Server.HTMLEncode(gData("txtVal2")) %>"><br> <table> <tr><th>Index</th> <th>列1</th> <th>列2</th> </tr> <% For i = 0 To gData("lst1").Count - 1 Set row = gData("lst1")(i) %> <tr><td><%=i%></td> <td><input type="text" name="<%="lst1_" & i & "_txtCol1"%>" value="<%=Server.HTMLEncode(row("txtCol1"))%>"></td> <td><input type="text" name="<%="lst1_" & i & "_txtCol2"%>" value="<%=Server.HTMLEncode(row("txtCol2"))%>"></td> </tr> <% Next %> </table> <div><input type="button" name="btnPost" value="post" onclick="document.f.submit();"> </div> %> </form> </body> </html> <% End Sub 'RenderHtml %>
ポイントは、gDataのKey名とinputタグのname属性の値を一致させる。こうすることで、CreateFormCollection()で処理してくれる。
一覧形式のデータについては、name属性を「(一覧の識別子)+"_"+index+"_"+(名前)」の形式とすることで、一覧をコレクション配列として復元している。
javascript出力時のエスケープ処理
htmlに文字を埋め込む場合、< > & をエスケープする必要がある。
aspでは、Server.HTMLEncode()が用意されているのでこれを利用する。
javascriptの場合についても、入力値などの文字列をjavascriptに埋め込む場合にはエスケープが必要である。
とりあえず、下記のような関数を用意した。
Function JsEscape(v) Dim s s = Replace(v, "\" , "\\") s = Replace(s, vbCrLf , "\n") s = Replace(s, vbCr , "\n") s = Replace(s, vbLf , "\n") s = Replace(s, """" , "\""") s = Replace(s, "'" , "\'") s = Replace(s, Chr(&H8), "\b") s = Replace(s, Chr(&HC), "\f") s = Replace(s, Chr(&H9), "\t") s = Replace(s, Chr(&HB), "\v") s = Replace(s, "&" , "\x26") s = Replace(s, "<" , "\x3c") s = Replace(s, ">" , "\x3e") JsEscape = s End Function
htmlに埋め込むことが前提なので、< > & を追加している。 エスケープ対象文字はほかにもあって、Unicode文字のLine SeparatorやParagraph Separatorがあるとのこと。
JSONの場合には、javascriptと同じエスケープでは駄目なので、以下を用意した。
Function JsonEscape(v) Dim s s = Replace(v, "\" , "\\") s = Replace(s, vbCrLf , "\n") s = Replace(s, vbCr , "\n") s = Replace(s, vbLf , "\n") s = Replace(s, """" , "\""") s = Replace(s, "/" , "\/") s = Replace(s, Chr(&H8), "\b") s = Replace(s, Chr(&HC), "\f") s = Replace(s, Chr(&H9), "\t") s = Replace(s, Chr(&HB), "\u000b") s = Replace(s, "'" , "\u0027") s = Replace(s, "&" , "\u0026") s = Replace(s, "<" , "\u003c") s = Replace(s, ">" , "\u003e") JsonEscape = s End Function
単純にエスケープ対象の文字をReplace関数で置き換えているが、もっといい方法があるだろう。 少なくとも1文字ずつ処理したほうが速い気がする。