【ExcelVBA】文字列操作用のユーティリティ関数を自作してみた

# はじめに

JavaやC#といった言語に比べて、VBAは文字列操作の関数が貧弱だと感じることが多いため、これまでに作った関数にプラスアルファで付け足したものをまとめてみました。
今後良い処理を思いついたら、随時書き足していく予定です。

なお、関数名の命名はJavaのStringクラスなどを参考にしたため、あまりVBっぽくないかもしれませんがご了承ください。

※とりあえず関数はABC順に並べていますが、後で読みやすくするために順番を変えるかもしれません。

# 作成した関数群

## Compare

* mougの[大文字/小文字や全角/半角を区別しない](https://www.moug.net/tech/exvba/0140044.html)という記事を参考にして、処理の一部分を関数として切り出したものです。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 大文字/小文字、全角/半角の区別なく、2つの文字列を辞書的に比較する。
'
' targetString：比較対象の文字列。
' anotherString：もう一方の比較対象の文字列。
' return：比較対象の文字列が等しい場合はtrueを返す。
'--------------------------------------------------------------------------------
Public Function Compare(targetString As String, anotherString As String) As Boolean
    ' 2つの文字列を「大文字の半角文字」に直して比較する。
    Compare = (StrConv(UCase(targetString), vbNarrow) = StrConv(UCase(anotherString), vbNarrow))
End Function
```

## Contains

* 組み込みの`InStr`関数を使えば簡単に実現できる処理ですが、他の言語に慣れている人には`InStr`という名称が分かりづらいため、`Contains`という名前の関数を作ってラップしました。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 探索対象の文字列中に、探索する部分文字列が存在するかを調べる。探索する部分文字列が空文字の場合もtrueを返す。
'
' targetString：探索対象の文字列
' substring：探索する部分文字列。
' return：探索対象の文字列中に、部分文字列が存在すればtrueを返す。
'--------------------------------------------------------------------------------
Public Function Contains(targetString As String, substring As String) As Boolean
    Contains = (InStr(targetString, substring) > 0)
End Function
```

## ContainsAny

* 第一引数で指定した探索対象の文字列に、第二引数で指定した部分文字列"群"が存在するかを返します。
 * `ContainsAny("すもももももももものうち", "りんご", "みかん", "すもも")`だと`True`が返されます。
 * `ContainsAny("すもももももももものうち", "りんご", "みかん", "ぶどう")`だと`False`が返されます。
* VBAの`ParamArray`という可変長引数の仕組みを活用しているため、第二引数は1ないし複数個を指定できるのがポイントです。
 * 可変長引数の使い方については、[こちらのサイト](https://www.tipsfound.com/vba/02023)などが参考になります。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 探索対象の文字列中に、1ないし複数個の部分文字列が存在するかを調べる。
'
' targetString：対象の文字列。
' searchTexts：探索する部分文字列。1ないし複数個を指定できる。
' return：探索対象の文字列中に、部分文字列が存在すればtrueを返す。
'--------------------------------------------------------------------------------
Function ContainsAny(targetString As String, ParamArray searchTexts() As Variant) As Boolean
    Dim txt As Variant
    Dim result As Boolean
    For Each txt In searchTexts
        If (InStr(targetString, CStr(txt)) > 0) Then
            result = True
            Exit For
        End If
    Next txt
    
    ContainsAny = result
End Function
```


## CountMatches

* 第1引数の文字列中に、第2引数の部分文字列が何回出現したかを返します。
* 例えば`CountMatches("すもももももももものうち", "もも")`と書くと、`4`が返されます。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 引数の文字列中に、探索文字列が何回出現したかを返す。
'
' targetString：対象の文字列。
' subStr：探索する部分文字列。
' return：探索文字列が出現した回数を返す。
'--------------------------------------------------------------------------------
Public Function CountMatches(targetString As String, subStr As String) As String
    MsgBox Replace(targetString, subStr, "")
    CountMatches = (Len(targetString) - Len(Replace(targetString, subStr, ""))) / Len(subStr)
End Function
```


## DeleteWhitespace

* 引数の文字列から空白文字を除去します。
* `Trim`との相違点は以下の通りです。
 * 「スペース」だけでなく、「タブ」「改行コード」も除去します。
 * 文字列の前後だけでなく、文字列の途中にある空白文字も除去します。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 引数の文字列から空白を除去する。
'
' targetString：比較対象の文字列。
' return：文字列の前後、および文字列中から以下の文字を除去した文字列を返す。
'         全角空白、半角空白、タブ、改行コード（CR,LF,CRLF）
'--------------------------------------------------------------------------------
Public Function DeleteWhitespace(targetString As String) As String
    ' 正規表現オブジェクトの作成
    Dim reg As Object
    Set reg = CreateObject("VBScript.RegExp")
    
    ' 正規表現のオプションの指定
    With reg
        .Pattern = "　| |\t|\r|\n"
        .IgnoreCase = False         '大文字と小文字を区別する
        .Global = True              '文字列全体を検索する
    End With
    
    DeleteWhitespace = reg.Replace(targetString, "")
End Function
```

## EndsWith

* `StartsWith`に比べてあまり使われる機会がない気もしますが、`StartsWith`と対になる処理なのでセットで作成しました。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 探索対象の文字列が、指定された接尾辞で終わるかを判定する。
'
' targetString：探索対象の文字列
' suffix：探索する接尾辞。
' return：探索対象の文字列が、指定された接尾辞で終わればtrueを返す。接尾辞が空文字の場合もtrueを返す。
'--------------------------------------------------------------------------------
Public Function EndsWith(targetString As String, suffix As String) As Boolean
    If (Len(targetString) >= Len(suffix)) Then
        ' 文字数が【探索対象の文字列】≧【接尾辞】の時、探索対象の文字列の末尾が接尾辞と一致すればtrueを返す。
        Dim subStr As String
        subStr = Right(targetString, Len(suffix))
        EndsWith = (subStr = suffix)
    Else
        ' 文字数が【探索対象の文字列】＜【接尾辞】の時、falseを返す。
        EndsWith = False
    End If
End Function
```

## Lines

* 引数の文字列を改行コードで区切って、区切られた文字列を配列の要素として返します。
* 改行コードは`CR`、`LF`、`CRLF`に対応しています。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 引数の文字列を改行コード（行終了記号）で区切った配列として返す。
'
' targetString：対象の文字列。
' return：改行コードの単位で区切られた配列。
'--------------------------------------------------------------------------------
Public Function Lines(targetString) As String()
    ' 正規表現オブジェクトの作成
    Dim reg As Object
    Set reg = CreateObject("VBScript.RegExp")
    
    '正規表現の指定（CRLF/CR/LF）
    With reg
        .Pattern = "\r\n|\r|\n"
        .IgnoreCase = False         '大文字と小文字を区別する
        .Global = True              '文字列全体を検索する
    End With
    
    ' CRLF/CR/LFをCRLFに統一する
    Dim buff As String
    buff = reg.Replace(targetString, vbCrLf)
    
    Lines = Split(buff, vbCrLf)
End Function
```

## Repeat

* 第1引数の文字列を、第2引数で指定した回数繰り返した文字列を生成して返します。
* 例えば`Repeat("*", 5)`と書くと、`*****`が返されます。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 引数の文字列を指定した回数繰り返す。
'
' targetString：対象の文字列。
' repeatCount：繰り返しの回数。1以上の値を指定する。
' return：引数の文字列を指定した回数繰り返した結果の文字列を返す。
'--------------------------------------------------------------------------------
Public Function Repeat(targetString As String, repeatCount As Integer) As String
    Dim buff As String
    Dim i As Integer
    
    For i = 1 To repeatCount
        buff = buff + targetString
    Next i
    
    Repeat = buff
End Function
```

## StartsWith

* 自作した`Contains`関数と同様に、組み込みの`InStr`関数を使えば簡単に実現できますが、処理内容を理解しやすくするために`StartsWith`という名前の関数を作ってラップしました。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 探索対象の文字列が、指定された接頭辞で始まるかを判定する。
'
' targetString：探索対象の文字列
' prefix：探索する接頭辞。
' return：探索対象の文字列が、指定された接頭辞で始まればtrueを返す。接頭辞が空文字の場合もtrueを返す。
'--------------------------------------------------------------------------------
Public Function StartsWith(targetString As String, prefix As String) As Boolean
    StartsWith = (InStr(targetString, prefix) = 1)
End Function
```


## ToCharArray

* 引数で指定した文字列を一文字ずつに分解して、それを文字配列として返します。
* ある文字列の2文字目、4文字目、6文字目...と飛び石で取得したい時などは、Mid関数を直接使うよりもこの関数の戻り値を使った方が楽だと思います。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 引数の文字列を文字配列に変換して返す。
'
' targetString：対象の文字列。
' return：文字配列。
'--------------------------------------------------------------------------------
Public Function ToCharArray(targetString As String) As String()
​
    Dim aryLength As Long
    Dim charAry() As String
    
    aryLength = Len(targetString)
    ReDim charAry(aryLength - 1)
    
    Dim i As Long
    For i = 1 To aryLength
        charAry(i - 1) = Mid(targetString, i, 1)
    Next i
    
    ToCharArray = charAry
End Function
```

## Truncate

* 第1引数の文字列を、第2引数で指定した文字数に切り詰めた文字列を返します。
 * 例えば、表示可能な文字数に制限がある時などに使えると思います。
* 組み込みの`Left`関数を使えば簡単に実現できますが、処理内容を理解しやすくするために`Truncate`という名前の関数を作ってラップしました。

```vb:StringUtils.bas
'--------------------------------------------------------------------------------
' 引数の文字列を指定した文字数に切り詰める。
'
' targetString：対象の文字列。
' maxLength：文字列の最大文字数。この文字数を超えた部分は削除される。
' return：引数の文字列を指定した文字数に切り詰めた文字列を返す。
'--------------------------------------------------------------------------------
Public Function Truncate(targetString As String, maxLength As Integer) As String
    Truncate = Left(targetString, maxLength)
End Function
```