「123,田中,tanaka」というデータを"123"と"田中"と"tanaka"に分割するのは、Split関数で一発です。詳しくは「Split関数で文字列を区切る」をご覧ください。
では「ABC12345DE678」という固定長のデータを"ABC"と"12345"と"DE"と"678"に分割するには、どうしたらいいでしょう。今回は、そういうお話です。
たぶん、多くの人がこう考えるでしょうね。Excelには「区切り位置」という機能があります。それを使えば、手動だったら簡単です。
まずは簡単に、元の文字列がセルA1に入っていて、それをB列から右に分割してみました。さて、この操作をマクロ記録すると、次のようなコードが記録されます。
Sub Macro1() Selection.TextToColumns Destination:=Range("B1"), DataType:=xlFixedWidth, _ FieldInfo:=Array(Array(0, 1), Array(3, 1), Array(8, 1), Array(10, 1)), _ TrailingMinusNumbers:=True End Sub
TextToColumnsメソッドを使うと分かりました。ただ問題は、
FieldInfo:=Array(Array(0, 1), Array(3, 1), Array(8, 1), Array(10, 1))
です。これは、いったい何を言っているのでしょう。
まずは左端にArray関数がありますから、これは配列を作っています。しかし、その配列の各要素が、また配列になっています。このように、配列の要素が配列である配列のことを「ジャグ配列」と呼びます。1次元配列でさえイメージするのが難しい人が、ジャグ配列をイメージするのは無理でしょう。さて、要素に指定している配列は、次の意味です。
Array(0, 1) → 区切る位置の先頭が0文字目+形式は"標準"
Array(3, 1) → 区切る位置の先頭が3文字目+形式は"標準"
Array(8, 1) → 区切る位置の先頭が8文字目+形式は"標準"
Array(10, 1) → 区切る位置の先頭が10文字目+形式は"標準"
この配列の、2番目の要素には、次の定数または数値を指定します。
定数 | 値 | 形式 |
---|---|---|
xlGeneralFormat | 1 | 標準 |
xlTextFormat | 2 | 文字列 |
xlMDYFormat | 3 | MDY (月日年) 形式の日付 |
xlDMYFormat | 4 | DMY (日月年) 形式の日付 |
xlYMDFormat | 5 | YMD (年月日) 形式の日付 |
xlMYDFormat | 6 | MYD (月年日) 形式の日付 |
xlDYMFormat | 7 | DYM (日年月) 形式の日付 |
xlYDMFormat | 8 | YDM (年日月) 形式の日付 |
xlEMDFormat | 10 | EMD (台湾年月日) 形式の日付 |
xlSkipColumn | 9 | スキップ列 |
定数xlEMDFormatは、簡易字中国語サポートがインストールされている環境で使用可能です。
ここで指定する形式は、ウィザードの3/3で指定する「列のデータ形式」です。
ちなみに、記録されたコードの最後
TrailingMinusNumbers:=True
は、ウィザード3/3画面にある[詳細]ボタンをクリックして指定できる[負の数はマイナスを表示する]オプションです。
この記録されたコードを、そのまま使ったり、これを応用してマクロを作成することは、お勧めしません。理由は次の3つです。
まぁ、ほとんどの人は1.と2.で断念するでしょうけど、問題は3.です。この「区切り位置」機能は、セル内のデータを分割して、指定したセルに出力する機能です。元データがセルに入っていなければなりませんし、出力先もセルに限定されます。テキストファイルを読み込んで、それを分割して…という使い方には不向きです。
そもそも、文字列の中で、指定した位置から、指定した文字数分の文字を抜き出すには、Mid関数を使えます。だから、マクロ記録した「区切り位置」機能なんか使わずに、Mid関数を使って区切ればいいんです。たとえば、今回のケースでしたら、次のように考えられます。
これを、まずはベタに書いてみましょう。
Sub Macro2() Range("B1") = Mid(Range("A1"), 1, 3) Range("C1") = Mid(Range("A1"), 4, 5) Range("D1") = Mid(Range("A1"), 9, 2) Range("E1") = Mid(Range("A1"), 11, 3) End Sub
ここから法則性を探します。このように区切るためには、
の、いずれかが情報として必要です。ここでは、1.の「それぞれの項目は何文字目から始まるか」が情報として与えられたとします。
この4つの数値「1, 4, 9, 11」から、先のMid関数を作れればいいわけです。まず、Mid関数の第2引数
項目1:Mid(文字列, 1, 3) 項目2:Mid(文字列, 4, 5) 項目3:Mid(文字列, 9, 2) 項目4:Mid(文字列, 11, 3)
は、いいですね。分かっています。では、3番目の引数は、どういう関係になっているのでしょうか。
つまり、最後の項目を除いて、それ以外は「次の開始位置から、自分の開始位置を引いた数」が、文字数になります。では、最後の項目の文字数は、どうやって求めればいいのでしょうか。上には"できない"と書きましたが、まぁ、ここまで抜き出した文字数を合計して、元の文字数から差し引けば計算できますけど、そんなの面倒くさいです。実は、超簡単な方法があります。
最後の項目「11文字目から3文字分」というのは「11文字目から後ろ全部」とも言えます。Mid関数は、第3引数の"文字数"を省略すると、後ろ全部を抜き出します。したがって、最後の項目は
項目1:Mid(文字列, 1, 3) 項目2:Mid(文字列, 4, 5) 項目3:Mid(文字列, 9, 2) 項目4:Mid(文字列, 11)
で、いいんです。ここまでを、簡単なマクロで確認してみましょう。
Sub Macro3() Dim i As Long, Pos(3) As Long, Result As String Pos(0) = 1 Pos(1) = 4 Pos(2) = 9 Pos(3) = 11 For i = 0 To UBound(Pos) - 1 Result = Result & Mid(Range("A1"), Pos(i), Pos(i + 1) - Pos(i)) & vbCrLf Next i Result = Result & Mid(Range("A1"), Pos(i)) MsgBox Result End Sub
うまくいきました。最後にやっている変数iの使い方は、ちょっと高度です。さて、こうした作業を実務で行うのでしたら、この処理はFunctionにしておくといいですね。
Sub Macro4() Dim A As Variant, i As Long A = FixedWidth(Range("A1"), Array(1, 4, 9, 11)) For i = 0 To UBound(A) MsgBox A(i) Next i End Sub Function FixedWidth(A As String, Pos) Dim i As Long ReDim Result(UBound(Pos)) For i = 0 To UBound(Pos) - 1 Result(i) = Mid(A, Pos(i), Pos(i + 1) - Pos(i)) Next i Result(i) = Mid(A, Pos(i)) FixedWidth = Result End Function
Functionには、2つの引数を指定します。ひとつめは、分割する前の「固定長文字列」です。ふたつめの引数には、何文字目から区切るかを示す数値を配列形式で指定します。ここではArray関数を使っています。分割した結果は、Split関数のように配列で返します。Split関数と同じように、バリアント型で受ければいいでしょう。
最後にオマケで、固定長データのテキストファイルを読み込んで、セルに分割代入してみましょう。ここでは、次のようなデータとします。とりあえず100行作りました。
次の位置で区切るものとします。
Sub Macro5() Dim buf As String, i As Long, A Open "C:\Work\Sample.txt" For Input As #1 Do Until EOF(1) Line Input #1, buf A = FixedWidth(buf, Array(1, 11, 17, 21, 22)) i = i + 1 Cells(i, 1).Resize(, UBound(A) + 1) = A Loop Close #1 End Sub
Function FixedWidthは、上に書いたやつです。実行結果は以下のとおり。
日付が「yyyy/m/d」になっているのは、Excelの(というかセルの)仕様ですね。お好みの表示形式を設定すればいいでしょう。