VBScript
asp

【ASP】半角1バイト・全角2バイト換算で文字列の桁数を取得する

はじめに

別システムとの桁数が一致しないことでエラーが発生するというのを防ぐために、桁数を一致させる改修作業を行いました。
CSV(Shift-JIS)ファイルを経由してASPにパラメーターが渡ってきます。

実際にテストしてみると想定した桁数を超えてもエラーになってくれませんでした、この原因を探ってみました。

文字数の取得関数

ASPで言語はVBScriptでコードページは65001でUTF-8を使用しています。
※レガシーASPを使用しています。ASP.NETではありません。

Len関数

指定した文字列の文字数を返します。

LenB関数

指定した文字列のバイト数を返します。
ASPでは内部でUNICODEを使用しているため、LenBは半角でも2バイトとして扱います。

ユーザー関数

標準の関数では、想定した半角1バイト・全角2バイトで返してくれないため、ユーザー関数を作成することになります。

引用:VBScript でシフト JIS の文字列のバイト数を数える (unibon)

Dim s
s = "abcあい"
Call MsgBox("MyLen = " & MyLen(s))  ' 結果 MyLen = 7

Function MyLen(ByVal a)
    Dim c
    c = 0
    Dim i
    For i = 0 To Len(a) - 1
        Dim k
        k = Mid(a, i + 1, 1)
        If (Asc(k) And &HFF00) = 0 Then
            c = c + 1
        Else
            c = c + 2
        End If
    Next
    MyLen = c
End Function

落とし穴

ユーザー関数を使うと確かに想定した結果を返してくれたのですが、これには落とし穴がありました。
それが上述した「実際にテストしてみると想定した桁数を超えてもエラーになってくれなかった」の原因でもあります。

ASPファイルをShift-JISで保存していた時は想定した結果を返してくれていましたが、この処理を組み込んだASPファイルはUTF-8(BOM付き)で保存されていたため、全て1バイトとして結果を返してくるようになったのです。

ソースリストをgitで管理する際に文字化けを避けるためにUTF-8(BOM付き)に保存形式を変えたのですが、ここに影響するとは思ってなかったです。

対応

当初は、ADODB.Streamを使って文字列をShift-JISに変換を試みるも上手くいかなくて、いろいろ模索した中で最終的に出来たのが下記コードになります。
半角カタカナは2バイト圏なので、1バイトに補正して件数をカウントしています。

参考:文字コード関係で遊ぶ ASC関数とASCW関数
参考サイトはShift-JISで保存されて運用されているので、UTF-8(BOM付き)で保存してローカル上のIIS上で確認しました。

Dim s
s = "abcあい"
Call MsgBox("LenByte = " & LenByte(s))  ' 結果 LenByte = 7

Function LenByte(value)
    Dim i, str
    Dim bytes, code
    LenByte = 0

    If Trim(value) <> "" Then
        For i = 1 To Len(value)
            str = Mid(value, i, 1)
            bytes = 1
            code = AscW(str)
            If (code And &HFF00) <> 0 Then
                '半角カタカナ以外は、2バイト
                If code < &HFF61 or code > &HFF9F Then bytes = 2
            End If
            LenByte = LenByte + bytes
        Next
    End If

End Function

サロゲートペア

𩸽(ホッケ)は、サロゲートペアで4バイトの文字となり、Len関数でも2文字分(0xD867,0xDE3D)として扱われます。

参考:連載! とことん VB: 第 5 回 4 バイト長の文字 (サロゲート ペア) の文字列操作の方法 ~ホッケが仲間はずれの理由~

Dim s
s = "𩸽"
Call MsgBox("LenByte = " & LenByte(s))  ' 結果 LenByte = 4

ユーザが入力した値にサロゲートペアが含まれていたらエラーにしてしまうでもいいかも知れません。そうすると𠮷野屋の「𠮷」もサロゲートペアなので入力できなくなりますね。

最後に

データベース(Oracle)では、桁数の3倍の領域を確保しています。桁数チェックをShift-JIS扱いで換算しておけば、企業で使用する上では運用上は超えることはないでしょう。
個人向けサイトではきっちり桁数チェックしておかないと危ういですが、そもそもレガシーASPを使用してないですよね。

仕様で文字数なのか文字数(バイト)とするのか決めておかないと、あとあと問題になるので、設計段階で確認しておきましょう。