【VB.Net】Replace関数 同文字列を出現ごとに置換

市販の見積作成ソフトを自社開発の見積作成ソフトに更改する案件でのこと。
データベースをコンバートしたら、コンバート元のデータベースで変なデータの持ち方をしている項目がありました。

見積書内容のデータベースにおいて、以下のような感じ。

DataId納期
1別途ご協議の上決定
2令和_##年_##月_##日迄;6;5;20
3受注後_&&&&&&日以内;90
4受注後_&&&&&日以内;5
db_document

やりたいこと

新しいデータベースでは以下のようなデータの持ち方をしたい。

DataId納期
1別途ご協議の上決定
2令和65月20日迄
3受注後90日以内
4受注後5日以内
db_document

元データでは数字部分が「_&&」もしくは「_##」に置き換えられ、数字部分は「;」区切りで文字列の最後尾にデータ保持をしている。
ただし「&」の個数はデータによってばらつきがある。
また「_&」「_#」の違いはよく分からない(分からなくても問題なし)

こんな感じのデータベースをVB.Netにて作成しているコンバートシステムにて文字列操作しながらコンバートします。

以下のコードで実現しました

とりあえずReplace関数を使うイメージはあったのですが、問題は「令和_##年_##月_##日迄;6;5;20」のようなデータ。「_##」がデータ中に3回登場し、それぞれ1回目は「6」、2回目は「5」3回目は「20」に書き換えなければなりません。

これをどうやって実現しようか少し悩みましたが、以下のコードでうまくいきました。若干泥臭いやり方です。
なお私の言語環境はVB.Netですが、おそらくVBAでも同様の考え方でいけるのではないかと思います。

'O_wRowData:コンバート元データベース
'N_wRowData:コンバート先データベース

Dim w(4) As String
Dim w1 As String
Dim r As Integer

If InStr(O_wRowData("納期"), ";") > 0 Then
   w = Split(O_wRowData("納期"), ";")
   w1 = w(0)

   For r = 1 To w.Length - 1
      If w(r) <> "" Then
         If InStr(w(0), "&") > 0 Then
             w1 = Replace(w1, "_&", w(r), 1, r)
         End If
         If InStr(w(0), "#") > 0 Then
             w1 = Replace(w1, "_#", w(r), 1, r)
         End If
       End If
    Next

Else
   w1 = O_wRowData("納期")
End If

   w1 = Replace(w1, "&", "")
   w1 = Replace(w1, "#", "")

N_wRowData("納期") = w1

コード解説

①旧データを「;」でSplit

If InStr(O_wRowData("納期"), ";") > 0 Then
   w = Split(O_wRowData("納期"), ";")
   w1 = w(0)

~省略~

Else
   w1 = O_wRowData("納期")
End If

旧データに「;」が存在する場合(Instr)、「;」でSplitし、一次元配列として宣言している変数wに格納。

「令和_##年_##月_##日迄;6;5;20」の場合、この時点で以下のように格納されています。

'w(0):令和_##年_##月_##日迄
'w(1):6
'w(2):5
'w(3):20


string型の変数w1に、データ本体のw(0)を持たせておきます。

旧データの「;」が存在しない場合(Ellse以下)、w1には旧データをそのまま格納しておきます。

②一次元配列wの要素数分ループ処理にて置換していく

   For r = 1 To w.Length - 1
      If w(r) <> "" Then
         If InStr(w(0), "&") > 0 Then
             w1 = Replace(w1, "_&", w(r), 1, 1)
         End If
         If InStr(w(0), "#") > 0 Then
             w1 = Replace(w1, "_#", w(r), 1, 1)
         End If
       End If
    Next

一次元配列wの要素数分(length)、ループ処理を行う。

まずは「_&」の置換から。「Replace(w1, “_&”, w(r), 1, 1)」が今回のミソです。

Replace関数の記述は以下の通り。

VB.Net
Replace(expression, find, replace[, start[, count[, compare]]])


それぞれの引数の説明は以下の通り。

引数定数(値)説明
expression置換元の文字列
find検索する文字列
replace置換する文字列
start検索開始位置を指定
省略した場合、1とみなす
count検索する回数を指定
省略した場合、すべて置換
comparevbBinaryCompare(0)バイナリモードで比較
大文字と小文字を区別する
vbBinaryCompare(1)テキストモードで比較
大文字と小文字を区別しない

Replace(w1, “_&”, w(r))だとシンプルで、変数w1の文字列から「_&」をすべて変数w(r)の文字列に置換します。
しかし今回は前から順番に置換していきたいので、Replace(w1, “_&”, w(r), 1, 1)と4つ目、5つ目の引数に数値を渡します。

こうすることで文字列を頭から検索し、1回のみ置換するように指定されます。

一次元配列wの要素数分(length)、ループ処理を行うことで順番に置換されていきます。

同様のことを「_#」においても行う。ループ中の変数w1の結果は以下のような遷移をたどるはず。

'r=1のときのループ処理後の結果
'w1=令和6#年_##月_##日迄

'r=2のときのループ処理後の結果
'w1=令和6#年5#月_##日迄

'r=3のときのループ処理後の結果
'w1=令和6#年5#月20#日迄

③②で置換されていない「&」「#」を削除し、新DBに格納

   w1 = Replace(w1, "&", "")
   w1 = Replace(w1, "#", "")

N_wRowData("納期") = w1


②では「_&」「_#」で検索してReplaceしましたが、実際のデータは「_&&&&&日後」など記号が複数並ぶため、不要な「&」「#」をReplaceで削除します。

綺麗なデータになった変数w1を新DBに格納して完了。

以上です。

Replace関数はしょっちゅう使用しますが4つ目、5つ目の引数を使うのは意外と初めてだったので新たな発見でした。

参考サイト

以下のサイトを参考にさせていただいています。ありがとうございます。

Office TANAKA Replace関数