ファイル | Excel作業をVBAで効率化 https://vbabeginner.net いつものExcel作業はVBAを使えば数秒で終わるかもしれませんよ Sun, 10 Nov 2024 07:27:53 +0000 ja hourly 1 https://wordpress.org/?v=6.6.2 https://vbabeginner.net/wp-content/uploads/2019/02/favicon-150x150.png ファイル | Excel作業をVBAで効率化 https://vbabeginner.net 32 32 VBAでUTF-8ファイルのBOM有無を調べる https://vbabeginner.net/bom-exists/ Mon, 12 Jun 2023 15:35:40 +0000 https://vbabeginner.net/?p=6775 UTF-8のテキストファイルにはBOM有無の2種類がある

文字コードがUTF-8のテキストファイルには、BOMがあるファイルと無いファイルの2種類があります。

BOM(ボム)とは「バイトオーダーマーク(Byte Order Mark)」の通称で、Unicodeのテキストファイルであることを明示するためにテキストファイルの先頭から2~4バイトを使って特殊文字を埋め込まれたデータのことを言います。

WindowsでのUnicodeは一般的にはUTF-8のことを指すことがほとんどです。UTF-8のBOMは、テキストファイルの先頭に「0xEF 0xBB 0xBF」の3バイトが埋め込まれます。

なお、UTF-7やUTF-16などの他の符号化形式の場合のBOMはUTF-8とは違うデータになります。

このBOMですが、ExcelでUTF-8のテキストファイルを扱う際に結構重要だったりします。というのも、文字コードがUTF-8でBOM無しのCSVファイルをダブルクリックした場合、UTF-8のデータであることを判定できず、Shift-JISとして開いてしまい文字化けしてしまいます。BOMが付いていればUTF-8と判定して文字化けは起こりません。

このような問題を回避するために、文字化けが発生しそうなテキストファイルかどうかの判定をするには、イチかバチかダブルクリックして文字化けするかどうかを見るか、ってことになってきます。

いちいちExcelで開くのは面倒なので、事前にVBAでBOMが付いているかどうかを判定する方法を紹介します。

BOMの判定を行うコード

以下の関数は、引数で渡されたテキストファイルの先頭3バイトにBOMが付いているかどうかを判定します。戻り値として、BOMがあればTrue、なければFalseを返します。

処理の内容はコメントにほとんど書いてますが、やっていることの概要は、引数のテキストファイルを先頭3バイト読み込んで、「0xEF 0xBB 0xBF」と同じかどうかを調べて、同じであればBOMが付いている、と判定しています。

なお、引数のテキストファイルが3バイト未満の場合はBOMなしを返します。

処理中でのテキストファイルの操作はバイナリデータを扱う方法で行っています。

バイナリデータを扱う方法の詳細については「VBAでバイナリファイルを読み込む(一括、1バイト、指定バイト)」をご参照ください。

'// 引数:テキストファイルパス
'// 戻り値:BOM存在チェック結果:True:BOMあり、False:BOMなし
Function IsBomExists(a_sPath As String) As Boolean
    Dim fn                  '// ファイル番号
    Dim iSize               '// ファイルサイズ
    Dim i                   '// ループカウンタ
    Dim byData()    As Byte '// バイナリデータ
    
    '// ファイル番号取得
    fn = FreeFile
    
    '// バイナリ形式で引数のテキストファイルを開く
    Open a_sPath For Binary Access Read As #fn
    
    '// ファイルサイズ取得
    iSize = LOF(fn)
    
    '/ 3バイト未満の場合
    If iSize < 3 Then
        '// ファイルクローズ
        Close #fn
        
        '// BOMなし
        IsBomExists = False
        Exit Function
    End If
    
    '// 先頭3バイト読み込み
    ReDim byData(2)
    
    Get #fn, 1, byData(0)
    Get #fn, 2, byData(1)
    Get #fn, 3, byData(2)
    
    '// ファイルクローズ
    Close #fn
    
    '// 先頭3バイトがBOMの場合(0xEF(239)、0xBB(187)、0xBF(191)の場合)
    If byData(0) = 239 And byData(1) = 187 And byData(2) = 191 Then
        '// BOMあり
        IsBomExists = True
    '// 先頭3バイトがBOMではない場合
    Else
        '// BOMなし
        IsBomExists = False
    End If
End Function

使い方

上のIsBomExists関数の使い方です。

BOMがあるかどうかを調べたいテキストファイルを引数として渡して、Boolean型の変数で受け取ります。BOMがついていれば戻り値にTrueが設定され、ついてなければFalseが設定されます。

Sub IsBomExistsTest()
    Dim sPath   As String       '// ファイルパス
    Dim bResult As Boolean      '// 処理結果
    
    '// sPath = "D:\test\UTF8-No-BOM.csv"  '// BOMなしのファイルはFalseが返ってくる
    sPath = "D:\test\UTF8-BOM.csv"     '// BOMありのファイルはTrueが返ってくる
    
    bResult = IsBomExists(sPath)
    
    Debug.Print bResult
End Sub

]]>
VBAでファイルの末尾の文字を取得する https://vbabeginner.net/get-the-last-character-of-the-file/ Tue, 11 May 2021 16:56:24 +0000 https://vbabeginner.net/?p=5526 ファイルの末尾の文字を調べるには

ファイルの末尾がどういう文字か調べるには、Linuxの場合であればコマンドで「tail -c 1 a.txt | xxd -p」と実行して”0a”と出力されれば改行コードLFである、などと判定できますが、VBAでは残念ながらそういう方法は採れません。

ただ、考え方としては似たような方法は可能です。具体的にはFileSystemObjectクラスを使ってファイルを開き、そのファイルの終端の直前までジャンプ(シーク)し、それ以降の文字を取得する、という方法です。

この方法であればファイルサイズにほとんど影響されることなく、ファイルの末尾の文字を取得することが可能です。

以下ではその方法でのファイルの末尾の文字を調べる方法を紹介します。

事前設定

以下のサンプルコードでは参照設定でFileSystemObjectクラスを利用できるようにしています。

FileSystemObjectクラスを利用するには、事前にVBA画面→ツールメニュー→参照設定、を選択し、参照設定ダイアログで「Microsoft Scripting Runtime」にチェックを付けます。

詳細は「VBAでのFileSystemObjectとTextStreamの使い方」の事前設定をご参照ください。

コード

以下のコードは、指定されたファイルパスの末尾の文字を取得します。終端が改行コードの場合も考慮して、文字コードで取得するようにしています。

'// 引数はファイルパス文字列
'// 戻り値はファイルパス終端文字の文字コード値
Function GetCharEndOfFile(a_sPath As String) As Integer
    Dim fso     As New FileSystemObject     '// FileSystemObject
    Dim ts      As TextStream               '// TextStream
    Dim oFile   As File                     '// File
    Dim iSize   As Long                     '// ファイルサイズ
    Dim iCode   As Integer                  '// 文字コード
    
    '// ファイルが存在しない場合
    If (fso.FileExists(a_sPath) = False) Then
        '// 末尾文字なしとして-1を返す
        GetCharEndOfFile = -1
        '// 以降を処理せず抜ける
        Exit Function
    End If
    
    '// 指定ファイルのFileオブジェクトを取得
    Set oFile = fso.GetFile(a_sPath)
    
    '// 指定ファイルのサイズを取得
    iSize = oFile.Size
    
    '// 空ファイルの場合
    If (iSize = 0) Then
        '// 末尾文字なしとして-1を返す
        GetCharEndOfFile = -1
        '// 以降を処理せず抜ける
        Exit Function
    End If
    
    '// ファイルを開く
    Set ts = fso.OpenTextFile(a_sPath, ForReading, True)
    
    '// ファイルの終端直前に移動
    Call ts.Skip(iSize - 1)
    
    '// 最後の1文字を文字コードで取得
    iCode = Asc(ts.Read(1))
    
    '// ファイルを閉じる
    ts.Close
    
    GetCharEndOfFile = iCode
End Function

使い方

上の関数の使い方の例です。ファイルパスを指定して引数に渡し、戻り値として文字コード変数で結果を受けています。

取得した文字コードをイミディエイトウィンドウにそのまま出力しています。例えば終端文字が改行LFであれば10が出力されます。

Sub GetCharEndOfFileTest()
    Dim sPath   As String                   '// ファイルパス
    Dim iCode   As Integer                  '// 文字コード
    
    sPath = "V:\aaaa\a.txt"
    
    iCode = GetCharEndOfFile(sPath)
    
    Debug.Print iCode
End Sub

実行結果

10(終端が改行コードCRLFであれば、LFの10が出力される)

問題点

上のコードはFileSystemObjectを利用しており、テキストファイルがShift-JISであることを前提としています。また、終端に全角文字がないことも前提です。

参照するファイルの文字コードがUTF-8だったり、終端が全角文字の場合はエラーになります。

ただ、業務で扱うデータをファイル化したものは処理しやすいようにShift-JISであることの方が多いことと、住所や氏名などの全角文字が終端にある場合もあまりないと思われるため、そうであれば上のコードのままで問題ないと思います。

バイナリファイルを扱うのであればFileSystemObjectの仕組みではなく、Openステートメントを利用してファイルを開き、終端に達した際に直前の文字がなんだったか、という判定を行う方法を採るなど、別の方法が必要になります。

バイナリファイルの扱い方については「VBAでバイナリファイルを読み込む(一括、1バイト、指定バイト)」をご参照ください。

]]>
VBAでバイナリファイルを読み込む(一括、1バイト、指定バイト) https://vbabeginner.net/read-binary-file/ Tue, 10 Mar 2020 16:51:24 +0000 https://vbabeginner.net/?p=4728 バイナリデータを読み込むには

VBAではファイルを扱う場合はFileSystemObjectクラスを利用することが一般的ですが、バイナリファイルに関してはFileSystemObjectクラスでは扱えません。そのため、Openステートメントでバイナリファイルを開きます

具体的な書き方は後述のサンプルコードになりますが、概要だけを書くとOpenステートメントを実行する際のMode引数でバイナリファイルであることを指定します。

バイナリデータを扱う場合は、だいたい以下の3パターンのいずれかでの読み込み方法になります。

  • 全データを1度に読み込む方法
  • 1バイトずつ読み込む方法
  • 一定のサイズ(例えば500バイト)ごと読み込む方法

それぞれ少し書き方が異なる点があり、バイナリファイルを扱うときに一番ハマるのが「データ取得の書き方」なので、それぞれについて説明します。

なお、Openステートメントなどの詳細については「VBAでテキストファイルの読み書きを行う」をご参照ください。

バイナリファイルデータを1度に全て読み込む場合

全データを1度に読み込む方法は一番簡単な方法です。バイナリファイルのサイズが1KB程度などのように小さいことが事前に分かっている場合は、この方法をオススメします。

Sub ReadBinaryAllData()
    Dim sFilePath   As String       '// バイナリファイルパス
    Dim fn                          '// ファイル番号
    Dim byAry()     As Byte         '// 読み込みデータ配列
    Dim byData      As Byte         '// バイナリデータ1バイト分
    Dim iSize                       '// ファイルサイズ
    Dim i                           '// ループカウンタ
    
    '// バイナリファイルパス設定
    sFilePath = "C:\aaa\b.bin"
    
    '// ファイル番号取得
    fn = FreeFile
    
    '// ファイルオープン
    Open sFilePath For Binary Access Read As #fn
    
    '// ファイルサイズ取得
    iSize = LOF(fn)
    
    '// 配列の領域確保
    ReDim byAry(iSize - 1)
    
    '// バイナリデータ読み込み
    byAry = InputB(iSize, fn)
    
    '// ファイルクローズ
    Close #fn
    
    '// 1バイトずつ読み込んで出力
    For i = 0 To iSize - 1
        '// 現ループの配列値を取得
        byData = byAry(i)
        
        '// 改行コードの場合
        If byData = 10 Or byData = 13 Then
            Debug.Print "改行です"
        End If
        
        '// 出力
        Debug.Print "No." & CStr(i) & " - 0x" & byData & " - Char[" & Chr(byData) & "]"
    Next
End Sub

ポイントは「Byte配列 = InputB(ファイルサイズ, ファイル番号)」の部分です。ここで全データを配列にセットしています。Byte配列の領域を事前に「Redim byAry(iSize – 1)」で確保していますが、確保しなくてもInputB()で確保された状態で格納されます。ただ、プログラミングの作法として配列の領域を確保しないのは意図的なのかコーディング漏れなのかが読みにくいため、確保しておく方がよいでしょう。

Byte型配列に格納したあとは必要な処理を行います。ここではくるくる回して1バイトずつ出力しているだけですが、実際のバイナリデータを扱う場合はByte型の16進数値がどうなのでこういうことをやる、という処理になることが多いと思います。

1バイトのByte型データの判定方法の書き方として改行コードの場合のサンプルを書いてます。

バイナリファイルデータを1バイトずつ読み込む場合

1バイトずつ判定して処理を行いたい場合はGetステートメントかInputB関数を使ってファイル終端まで1バイトずつ読み込むコードを書きます。

以下のコードはGetステートメントとInputB関数の両方を使って同じバイト位置を1バイトずつ取得して出力しています。もちろん実際に使う場合はGetかInputBのどちらか一方のみで構いません。

Sub ReadBinary1Byte()
    Dim sFilePath   As String       '// バイナリファイルパス
    Dim fn                          '// ファイル番号
    Dim byData      As Byte         '// バイナリデータ1バイト分(Get用)
    Dim iSize                       '// ファイルサイズ
    Dim i                           '// ループカウンタ
    Dim s           As String       '// 読み込み文字列(InputB用)
    
    '// バイナリファイルパス設定
    sFilePath = "C:\aaa\b.bin"
    
    '// ファイル番号取得
    fn = FreeFile
    
    '// ファイルオープン
    Open sFilePath For Binary Access Read As #fn
    
    '// ファイルサイズ取得
    iSize = LOF(fn)
    
    '// 1バイトずつ読み込んで出力
    i = 0
    Do Until EOF(fn)
        '// <Getステートメントでの書き方>
        '// 現ループの1バイトを取得
        Get #fn, i + 1, byData
        
        '// 出力
        Debug.Print "No." & CStr(i) & " - 0x" & byData & " - Char[" & Chr(byData) & "]"
        
        '// <InputB関数での書き方>
        '// 現ループの1バイトを取得
        s = InputB(1, #fn)
        
        '// 出力
        Debug.Print "No." & CStr(i) & " - 0x" & AscB(s) & " - Char[" & Chr(AscB(s)) & "]"
        
        i = i + 1
    Loop
    
    '// ファイルクローズ
    Close #fn
End Sub

バイナリデータを取得する際にGetステートメントとInputB関数のどちらを使ってもいいのですが、当然違いがあるため用途に合わせて使い分けをした方がいいでしょう。

Getステートメントは指定したファイルデータの位置(Seek関数での位置)から変数の領域分だけ読み取ります。言い方を変えると、変数に入る分だけ読み取ります。

InputB関数は第一引数で指定したサイズ分をバイナリデータのまま文字列として読み込みます。バイナリデータのまま、というのは似た関数にInput関数がありますが、こちらはUnicodeに変換した文字列が変数に格納されますが、InputB関数はなにも変換せず変数に文字列として格納します。

GetステートメントとInputB関数のいずれの場合も、可変サイズの取得を行うときは、ファイル終端を超えないように考慮が必要になります。

ちなみに私はバイナリデータを1バイトずつ取得するのであればGetステートメントを使うことが多いです。理由は、バイナリデータを扱うのであればByte型を使いたいところですが、InputB関数は文字列型(String型)を返すため感覚的に嫌、というのがあります。これは好みとしか言いようがないかもしれません。

バイナリファイルデータを一定サイズごとに読み込む場合

一番コードとして複雑になるのがここで紹介する一定サイズごとに読み込む場合です。どうしてもループ終端に達するかどうかの判定や読み込みサイズの調整が必要になります。

やらなくていいならこの方法はしない方がいいですが、バイナリファイルのサイズが大きい場合やサイズが不明の場合はこの方法しかない場合があります。

ここではGetステートメントでの書き方を紹介します。もちろんInputB関数でもコーディング可能です。

Sub ReadBinaryXByte()
    Dim sFilePath   As String       '// バイナリファイルパス
    Dim fn                          '// ファイル番号
    Dim byAry()     As Byte         '// 読み込みデータ配列
    Dim byData      As Byte         '// バイナリデータ1バイト分(Get用)
    Dim iArySize                    '// 配列サイズ
    Dim iSize                       '// ファイルサイズ
    Dim i                           '// ループカウンタ
    Dim iReadSize                   '// 読み込み済みサイズ
        
    '// バイナリファイルパス設定
    sFilePath = "C:\aaa\b.bin"
    
    '// ファイル番号取得
    fn = FreeFile
    
    '// ファイルオープン
    Open sFilePath For Binary Access Read As #fn
    
    '// ファイルサイズ取得
    iSize = LOF(fn)
    
    '// 指定サイズを21バイトとして配列を確保(20と書いたら21バイト分確保される)
    iArySize = 20
    ReDim byAry(iArySize)
    
    '// 1バイトずつ読み込んで出力
    i = 0
    Do Until EOF(fn)
        '// 配列サイズ分を読み込み
        Get #fn, , byAry
        
        i = 0
        For i = 0 To iArySize
            '// 配列から1バイト取得
            byData = byAry(i)
            
            '// 出力
            Debug.Print "No." & CStr(i) & " - 0x" & byData & " - Char[" & Chr(byData) & "]"
        Next
        
        '// 読み込み済みのサイズを設定(配列は20 + 1なのでその+1分も加算)
        iReadSize = iReadSize + iArySize + 1
        
        '// 読み込みサイズがファイルサイズより小さい場合で、次の読み込みで終端に達する場合
        If (iReadSize <= iSize) And (iSize - iReadSize < iArySize + 1) Then
            '// 次に読み込むサイズを計算
            iArySize = iSize - iReadSize
            
            '// 配列を再構築
            ReDim byAry(iArySize)
        End If
    Loop
    
    '// ファイルクローズ
    Close #fn
End Sub

ここでは21バイト(20 + 1)を配列のサイズとしてByte型配列を用意し、それをバイナリファイルのEOFに達するまで21バイトずつ取得していくコードです。

例えばバイナリファイルのサイズが150バイトだとした場合、ループごとで21、42、63、84、105、126、147、と読み込んでいきますが、終端の直前では150バイトから読み込み済みの147バイトを引いた3バイト+終端の4バイトが読み込むサイズになります。なお、終端はNULL(0x00)が1バイト読み込まれます。

そのため、ループの最後の部分で読み込みサイズが終端の直前かどうかの判定を行い、最後に読み込む配列サイズをRedimで調整しています。

ちなみに調整しなくても21バイトずつ取得しても終端に達するとループは抜けますが、その時の配列には終端の0x00以降の要素には全て0x00が設定されます。扱っているバイナリデータに0x00がないことが分かっているのであれば終端直前のRedimは不要ですが、その後で1バイトずつ処理することをあったり、0x00がデータとして存在している可能性があるなら配列のサイズ調整はしておいた方がよいでしょう。

]]>
VBAでファイル名やフォルダ名を変更する(Name) https://vbabeginner.net/file-folder-name/ Sat, 17 Nov 2018 19:25:22 +0000 https://vbabeginner.net/?p=3696 Nameステートメント

Nameステートメントを使うとファイル名やフォルダ名を変更することが出来ます。

パスを含めて変更できるため、その場合はファイルやフォルダを別のディレクトリに移動することが出来ます。

残念ながらワイルドカードの指定は出来ません。

対象のファイルやフォルダが他のアプリケーションによって排他状態で開かれている場合はファイル名の変更や移動は出来ずエラーになります。

構文

Name Oldpathname As Newpathname

Oldpathname 変更前のファイル名やフォルダ名を指定します。

パス名を含めずファイル名だけを指定した場合はカレントフォルダのファイルまたはフォルダが対象になります。

Newpathname 変更後のファイル名やフォルダ名を指定します。

パス名を含めずファイル名だけを指定した場合はカレントフォルダのファイルまたはフォルダが対象になります。

変更前と異なるパスを指定するとファイルやフォルダが移動します。

 

エラー

存在しない場合のエラー:エラー53

引数のパスに存在しないファイル名やフォルダ名やパスが含まれている場合は「実行時エラー’53’: ファイルが見つかりません。」が発生します。

エラーメッセージは「ファイルが見つかりません。」となっていますが、フォルダのエラーも同じメッセージになります。

開いているファイルの場合のエラー:エラー75

他のアプリケーションで開いているファイルの名称変更や移動を行おうとすると「実行時エラー’75’: パス名が無効です。」が発生します。

サンプルコード

b.txtをaフォルダに移動させてファイル名をc.txtに変更するサンプルです。

Sub NameStateTest()
    Dim sOld
    Dim sNew
    
    sOld = "C:\web\b.txt"
    sNew = "C:\web\a\c.txt"
    
    Name sOld As sNew
End Sub

]]>
VBAでCSVファイルを高速にシートに貼り付ける https://vbabeginner.net/paste-csv-file-to-sheet/ Sun, 19 Aug 2018 07:05:49 +0000 https://vbabeginner.net/?p=3357 外部データの取り込み機能

Excelには外部データの取り込み機能が標準で実装されています。

CSVファイルもこの機能で取り込むことが出来るのですが、VBAで1行ずつ処理するよりもとても高速に取り込めることが特徴です。

外部データの取り込み機能はVBAではQueryTableクラスを使います。QueryTableクラスには多くのメソッドとプロパティがありますが、あまり気にする必要はありません。というのも、QueryTableクラスを直接書くよりも、マクロの記録機能を使って外部データの取り込み機能を記録させた方が都合がいいためです。

実際私も何度かこの機能を使ったことがありますが、マクロの記録機能のままで作成されたコードをほとんどそのまま使っていますし、それで問題が起きたことはありません。

外部データの取り込み機能のマクロの記録

CSVファイルの取り込みは以下の手順で行います。

  1. 開発タブで「マクロの記録」を押す。記録先はどこでもいいがとりあえず作業中のブックとする。開発タブがない場合はファイルタブ→オプション→リボンのユーザ設定 で「開発」にチェックを付ける。
  2. データタブ→テキストファイル を押す。ファイル選択ダイアログが表示される。
  3. ファイル選択ダイアログで取り込むCSVファイルを選択し、インポートボタンを押す。ファイルの種類のコンボボックスは「テキストファイル」のままでOK。
  4. テキストファイルウィザードを以下のようにCSVファイルの内容に合わせて適宜設定する。3ページ目は下段の「データのプレビュー」から列を選択して、左上の「列のデータ形式」で列ごとに書式を設定する。ここでは一番左の列を日付YMDで、一番右の列を文字列に設定。


  5. データの取り込みで出力先のシートとセルを設定する。
  6. 開発タブで「記録終了」を押す。

以上でQueryTableクラスを使ったCSVファイルの取り込むコードが記録されます。

コードの修正点

上記手順を実行すると以下のようなコードが出力されます。

Sub Macro3()
    With ActiveSheet.QueryTables.Add(Connection:= _
        "TEXT;C:\test\a.csv", Destination:= _
        Range("$A$1"))
        .Name = "a"
        .FieldNames = True
        .RowNumbers = False
        .FillAdjacentFormulas = False
        .PreserveFormatting = True
        .RefreshOnFileOpen = False
        .RefreshStyle = xlInsertDeleteCells
        .SavePassword = False
        .SaveData = True
        .AdjustColumnWidth = True
        .RefreshPeriod = 0
        .TextFilePromptOnRefresh = False
        .TextFilePlatform = 932
        .TextFileStartRow = 1
        .TextFileParseType = xlDelimited
        .TextFileTextQualifier = xlTextQualifierDoubleQuote
        .TextFileConsecutiveDelimiter = False
        .TextFileTabDelimiter = False
        .TextFileSemicolonDelimiter = False
        .TextFileCommaDelimiter = True
        .TextFileSpaceDelimiter = False
        .TextFileColumnDataTypes = Array(5, 1, 1, 1, 1, 1, 1, 1, 1, 2)
        .TextFileTrailingMinusNumbers = True
        .Refresh BackgroundQuery:=False
    End With
End Sub

プロパティがたくさん出力されていますが、無くてもいいものもあります。

元のままでも構いませんが、最低限必要な設定だけでよければ以下のように不要なプロパティを削除できます。

Sub Macro3()
    With ActiveSheet.QueryTables.Add(Connection:= _
        "TEXT;C:\test\a.csv", Destination:= _
        Range("$A$1"))
        .TextFilePlatform = 932
        .TextFileCommaDelimiter = True
        .TextFileColumnDataTypes = Array(5, 1, 1, 1, 1, 1, 1, 1, 1, 2)
        .Refresh BackgroundQuery:=False
    End With
End Sub

あとは作成した関数を実行するとシートにCSVファイルの内容が取り込まれます。

プロパティについてはMicrosoftのヘルプの以下がありますので参考にしてください。
https://msdn.microsoft.com/ja-jp/library/office/dn254409.aspx

CSVファイルを外部データ取り込み機能で取り込む場合の注意点

QueryTableクラスでの取り込みを行うと、取り込んだ部分のセルは外部データ範囲として扱われます。

外部データ範囲は元データの関係を保つことが可能です。

そのため、RefreshOnFileOpenプロパティがTrueの場合などで、元のCSVファイルが更新されると、ブックを開く度に最新状態が反映されることになります。

元のCSVファイルとの関係を解除することも可能ですが、それをするかどうかは個別の条件によって異なると思います。

このように、元データとの関係が続くこともありえるため、取り込み後に独立したデータとして扱いたい場合は注意が必要です。

]]>
VBAでテキストファイルの読み書きを行う https://vbabeginner.net/read-and-write-text-files/ Wed, 15 Aug 2018 20:24:33 +0000 https://vbabeginner.net/?p=3351 VBAでテキストファイルの操作を行うには

VBAでテキストファイルの読み書きを行う場合、特殊な方法を除くと2つの方法があります。

  1. Open、Close、Input、Line Input、Get、Write、Print、Putステートメントを使う。
  2. FileSystemObjectクラスを利用する。

用途によってどちらを使うかを決めることになります。

Open等ステートメントはコードが単純ですが、FileSystemObjectクラスにあるような便利な機能はありません。バイナリファイルを扱う場合はこちらを使います。

FileSystemObjectクラスはメソッドやプロパティの使い方に癖がありますが、ファイルの存在をチェックできるメソッドなどの便利なメソッドが用意されています。

ここではOpen等のステートメントを使ったテキストファイルの操作について説明します。

OpenからCloseまでの流れ

テキストファイルを開いてから読み書きを行いファイルを閉じるまでには以下のいずれかの流れになります。

1. 読み込み(通常ファイル)

1.Open → 2.Line Input → 3.Close

2. 読み込み(CSVファイル)

1.Open → 2.Input → 3.Close

3. 書き込み(通常ファイル)

1.Open → 2.Print → 3.Close

4. 書き込み(CSVファイル)

1.Open → 2.Write → 3.Close

Openステートメント

Open Pathname For Mode [Access access] [lock] As [#] Filenumber [Len = reclength]

Openステートメントを使うと既存のテキストファイルを開いたり、新規テキストファイルを作成したりすることが出来ます。

複数の引数がありますが、ほとんどの場合は必須引数の3つ(PathnameとModeとFilenumber)だけを指定すればOKです。

Pathname 開くテキストファイルのパスをダブルクォーテーションで囲んで指定します。

ドライブ名からファイル名までのフルパスを指定するか、ファイル名だけのどちらかを指定します。

ファイル名だけの場合はカレントフォルダにあるファイルが開かれます。

カレントフォルダはCurDir関数で「CurDir(“C:”)」のようにドライブ名を指定すると調べられます

Mode ファイルをどのように開くのかをキーワードで指定します。

Input シーケンシャル入力モード テキストファイルの内容を先頭から順に参照
Output シーケンシャル出力モード 元のデータをクリアして先頭から書き込み
Append 追加モード 元のデータの後ろに追加書き込み
Binary バイナリモード 16進数のデータの読み書き
Random ランダムアクセスモード テキストファイルの任意の位置から読み書き

シーケンシャル入力、出力モードとはテキストファイルを先頭から終端に向かって読み書きすることを言います。

OutputモードとAppendモードの場合にパスのテキストファイルが存在しない場合はその名前のテキストファイルを新規作成します。

Access ファイルの読み書きを指定します。引数Modeとの組み合わせが不正な場合は構文エラーになります。

たとえば書き込みモードのAppendを指定して読み込みのReadアクセスを指定するなどです。

Read 読み込みのみ
Write 書き込みのみ
Read Write 読み書き両方
lock 開いたファイルを他のアプリケーションなどからアクセスできるようにするかどうかの制限を行います。

Shared 他プロセスからの読み書きを認める
Lock Read 他プロセスからの読み込みを禁止する
Lock Write 他プロセスからの書き込みを禁止する
Lock Read Write 他プロセスからの読み書きを禁止する
Filenumber テキストファイルを一意に認識するための番号を#1から#511の値で指定します。

1ファイルしか扱わないのであれば「#1」としても構いません。

reclength Randamモードを指定した場合は読み書きを行うデータ長を指定します。

それ以外の場合は読み書きを行う変数サイズをInteger型の範囲内(32767以下)で指定します。

Filenumberの指定

引数Modeが書き込みを行う「Output」または「Append」の場合、同じテキストファイルを異なるファイル番号で開こうとしても、先に開いた方に書き込み権限があるためエラーとなり開けません。

引数Modeが「Input」「Binary」「Random」の場合、同じテキストファイルを異なるファイル番号で開くことが可能です。

ただ、いずれにしてもよほどのことが無い限り、同じテキストファイルに対して複数のアクセスを同時に行うようなことは避けた方が無難です。

FreeFile関数

FreeFile関数はテキストファイルを開く際のOpenステートメントで使うことが出来るファイル番号を返します。

ファイル番号が重複しないのであればFreeFile関数を使わずに#1などと書いても構いません。

Closeステートメント

Close #Finenumber

CloseステートメントはOpenステートメントで開かれたファイルを閉じます。

Closeステートメントで閉じられた際のファイル番号は次のテキストファイルのOpenステートメントで使うことが出来るようになります。

Filenumber Openステートメントで指定したファイル番号を指定します。

複数のファイル番号を指定することで一度に複数のファイルを閉じることが可能です。

また、ファイル番号を省略した場合はそれまでにOpenステートメントで開いていたファイルを全て閉じます。

開いていないファイル番号をCloseで指定した場合

Openステートメントとは異なるファイル番号をCloseで指定した場合、エラーは発生せず何も閉じられることなくCloseステートメントは終了します。

手書きで「#1」などとファイル番号を書いている場合にはファイルが閉じられないバグになります。

EOF関数

EOF関数はOpenステートメントでInputモードかRandomモードで開いたテキストファイルの読み込み位置がファイルの終端(End Of File)に達しているかを判定する関数です。

ファイル終端に達している場合はTrueを返し、そうでない場合はFalseを返します。

引数にOpenステートメント時に指定したファイル番号を設定します。

通常はDo Untilループ条件として利用します。

Line Inputステートメント(1行読み込み用)

Line Input #Filenumber , varname

Filenumber Openステートメントで指定したファイル番号を指定します。
varname 読み込んだテキストファイルの行データを格納する変数を指定します。

変数の型はVariant型かString型のいずれかです。

Line InputステートメントはOpenステートメントでInputモードで開いたテキストファイルを改行コード(CRLF、LF、CRのいずれか)までの行単位で読み込みます。

次に読み込むときは次の行の先頭からになります。

改行コード自体は読み込んだデータには含まれません。

EOF(End Of File)まで読み込み可能ですが、それ以上読み込みを行うとエラーになります。

そのためEOF関数を使ってファイル終端かどうかをチェックする必要があります。

Inputステートメント(CSV読み込み用)

Input #Filenumber, Varlist, ・・・

Filenumber Openステートメントで指定したファイル番号を指定します。
Varlist 読み込む文字列を指定します。

Inputステートメントはテキストファイルからカンマ(,)や改行(CRLF、LF、CRのいずれか)があるまでのデータを読み込みます。

読み込んだあとに再度Inputステートメントを使うと次のカンマや改行までが読み込まれます。

引数Varlistを複数指定している場合はカンマが見つかるまでの文字列を各変数に設定します。

InputステートメントはCSVファイルの1項目ずつを読み込む際には適していますが、1行ごとに取得したい場合はLine Inputステートメントの方が適しています。

読み込まれた元のデータがダブルクォーテーションで囲まれていても、Inputステートメントで取得したデータはダブルクォーテーションの囲みは無い状態になります。

読み込む対象のテキストファイルは以下のようなCSVファイルと形式になります。
“aaa”,”bbb”,”ccc”
“1111”,”2222″,”3333″
“aaa”,”bbb”,”ccc”

Line Inputステートメントと同様で、EOF(End Of File)まで読み込み可能ですが、それ以上読み込みを行うとエラーになります。

そのためEOF関数を使ってファイル終端かどうかをチェックする必要があります。

Printステートメント(1行書き込み用)

Print #Filenumber , [ outputlist ]

Filenumber Openステートメントで指定したファイル番号を指定します。
Outputlist 書き込むデータを指定します。

引数outputlistは以下の構文で構成されます。

[ { Spc(n) | Tab [ (n) ] } ] [ expression ] [ charpos ]

Outputlistを構成するSpcとTabとcharposですが、挙動が分かりにくく使い勝手が悪いため使わない方が無難です。

例えばTabですが、タブ文字を書き込むように思いますが実際は空白スペースを書き込みます。

また、引数nもSpnとTabでは設定する値に際があり、Spnは引数nの数だけスペースを書き込みますが、Tabは引数nから1引いた数のスペースを書き込みます。

こういうのは個人的には触りたくないので使わないようにしています。

書き込みたい文字列を事前に編集して、expressionとしてそれを渡す方が柔軟なコードになります。

Writeステートメント(CSV書き込み用)

Write #Filenumber, Outputlist, ・・・

Filenumber Openステートメントで指定したファイル番号を指定します。
Outputlist 書き込むデータを指定します。

複数書き込む場合はカンマで区切って指定します。

WriteステートメントはOpenステートメントでOutputモードやAppendモードで開かれたテキストファイルにCSVファイル形式のデータを書き込みます。

書き込まれたデータはダブルクォーテーションで囲まれます。データ間はカンマで区切られます。

1行単位の書き込みはPrintステートメントの方が適しています。

サンプルコード

行単位で追加書き込みのサンプル

Sub TextfileAppendTest()
    Dim n
    Dim s
    Dim ar()
    Dim i
    
    s = "C*\test\a.txt"
    
    ReDim ar(3)
    ar(0) = "aaa"
    ar(1) = "bbb"
    ar(2) = "ccc"
    ar(3) = "ddd"
    
    '// ファイル番号取得
    n = FreeFile
    
    '// 追加モードでファイルを開く
    Open s For Append As #n
    
    '// 配列ループ
    For i = 0 To UBound(ar)
        '// 配列要素をタブ文字に続けて書き込み
        Print #n, vbTab & ar(i)
    Next
    
    '// ファイルを閉じる
    Close #n
End Sub

 

行単位で読み込むサンプル

Sub TextfileLineInputTest()
    Dim n
    Dim s
    Dim v
    
    s = "C*\test\a.txt"
    
    '// ファイル番号取得
    n = FreeFile
    
    '// シーケンシャル入力モードでファイルを開く
    Open s For Input As #n
    
    '// 配列ループ
    Do Until EOF(n)
        Line Input #n, v
        Debug.Print v
    Loop
    
    '// ファイルを閉じる
    Close #n
End Sub

 

CSVファイル形式で書き込むサンプル

Sub CsvWriteTest()
    Dim s
    Dim n
    
    s = "C*\test\a.txt"
    
    '// ファイル番号取得
    n = FreeFile
    
    '// シーケンシャル出力モードでファイルを開く
    Open s For Output As #n
    
    '// カンマ区切りデータを書き込み
    Write #n, "aaa", "bbb", "ccc"
    Write #n, "ddd"
    Write #n, "eee"
    Write #n,
    
    '// ファイルを閉じる
    Close #n
End Sub

 

CSVファイル形式で読み込むサンプル

Sub CsvInputTest()
    Dim s
    Dim n
    Dim v
    
    s = "C*\test\a.txt"
    
    '// ファイル番号取得
    n = FreeFile
    
    '// シーケンシャル入力モードでファイルを開く
    Open s For Input As #n
    
    Do Until EOF(n)
        '// カンマ区切りごとにデータを取得
        Input #n, v
        Debug.Print v
    Loop
    
    '// ファイルを閉じる
    Close #n
End Sub

]]>
VBAでファイルが読み取り専用か判定する https://vbabeginner.net/determines-read-only/ Mon, 06 Aug 2018 17:00:47 +0000 https://vbabeginner.net/?p=3335 読み取り専用のファイル

ファイルのプロパティの設定で読み取り専用の設定を行うことが出来ます。

読み取り専用のファイルを書き込みモードのOpenメソッドで開こうとするとエラーになります。

そのエラーを回避するために、事前に読み取り専用かどうかを調べる方法を紹介します。

ネットワークドライブの読み取り専用ファイルの注意点

上にも書きましたが、読み取り専用ファイルを書き込みモードでOpenしようとするとエラーになります。

ところが、これはローカルディスクにあるファイルの話であり、ネットワークドライブにある読み取り専用ファイルの書き込みOpenはエラーになりません。

そしてなんと、Openが正常のためかPrintメソッドで書き込みできてしまいます。私もこれに気が付いたときには驚きました。読み取り専用の意味ないじゃん、と。

少ししか調べてませんが、どういう理屈なのかは結局分かっていません。

いずれにしても、読み取り専用+Openメソッド=エラー、という認識はやめておいた方がいいと思われます。

ファイルの属性を調べて読み取り専用かどうか判定する

GtAttr関数を使うとファイルの属性を取得することが出来ます。

その属性はVbFileAttribute列挙型の定数で、以下の種類があります。

VbFileAttribute列挙型

定数 内容
vbNormal 0 通常ファイル
vbReadOnly 1 読み取り専用ファイル
vbHidden 2 隠しファイル
vbSystem 4 システムファイル
vbVolume 8 ボリュームファイル(使いません)
vbDirectory 16 フォルダ
vbArchive 32 アーカイブ
vbAlias 64 エイリアス(Macのみ。Windowsのショートカット)

GetAttr関数については「ファイルやフォルダの属性を調べる(GetAttr)」をご参照ください。

読み取り専用かどうかの判定関数

以下は引数のファイルが読み取り専用かどうかを判定する関数です。

読み取り専用の場合はTrueを返し、そうでない場合はFalseを返します。

引数ファイルが存在しているかどうかは判定していないため、ファイル存在チェックは事前に行う必要があります。

ファイル存在チェックについては「VBAでファイルの存在をチェックする」をご参照ください。

Function IsReadOnly(a_sFilePath) As Boolean
    Dim ret As VbFileAttribute
    
    '// ファイルの属性を取得
    ret = GetAttr(a_sFilePath)
    
    '// 読み取り専用が設定されている場合
    If (ret And vbReadOnly) = vbReadOnly Then
        IsReadOnly = True
    Else
        IsReadOnly = False
    End If
End Function

 

使い方

上のIsReadOnly関数の利用例です。

15行目でファイルが読み取り専用かどうかを判定しています。

一応ファイルの存在チェックも入れています。コードは上のリンクにあるファイル存在チェックのソースを使ってます。

Sub IsReadOnlyTest()
    Dim sFilePath
    Dim ret         As Boolean
    
    sFilePath = "D:\test\a.txt"
    
    '// ファイル存在チェック
    ret = IsExistFileDir(sFilePath)
    If (ret = False) Then
        Debug.Print "ファイルが存在しない"
        Exit Sub
    End If
    
    '// 読み取り専用チェック
    ret = IsReadOnly(sFilePath)
    If (ret = False) Then
        Debug.Print "読み取り専用ではない"
        Exit Sub
    End If
    
    Debug.Print "読み取り専用です"
End Sub

ファイル存在チェックのコードは以下を使ってます。

'// ファイル存在チェック
Public Function IsExistFileDir(a_sFilePath) As Boolean
    Dim a
    
    a = Dir(a_sFilePath)
    If (a <> "") Then
        IsExistFileDir = True
    Else
        IsExistFileDir = False
    End If
End Function

]]>
VBAでテキストファイルの行数を取得する https://vbabeginner.net/get-the-number-of-lines-in-a-text-file/ Thu, 04 Jan 2018 01:29:07 +0000 http://vbabeginner.net/?p=2365 テキストファイルの行数が必要な理由

VBAでテキストファイルやCSVファイルなどを扱う際に、そのファイルの行数を知りたいことがあります。

その理由には大きく3つあります。

  1. 単純に行数を知りたいため。
  2. Excelの最大行数(Range(“A:A”).Rows.Count)と比較するため。
  3. 高速化のため。

1番目はいいとして、2番目はファイルの内容をシートに貼り付けたい場合にExcelシートの最大行数(Range(“A:A”).Rows.Count)を超えていないかのチェックのためで、3番目は配列の領域確保を事前にファイル行数分行っている方が高速になるためです。

テキストファイルの行数取得方法にはいくつかありますが、ここでは3つ紹介します。

事前設定

以下のサンプルコードではFileSystemObjectとADODBを利用しています。

1つ目と2つ目のサンプルを利用する場合はFileSystemObjectを利用するため、VBA画面のツールメニュー→参照設定 で「Microsoft Scripting Runtime」にチェックを付けてください。

3つ目のサンプルを利用する場合はADODBを利用するため、VBA画面のツールメニュー→参照設定 で「Microsoft ActiveX Data Objects x.x Library」にチェックを付けてください。x.xのバージョンは最大のものを選択してください。

1. 高速にテキストファイル行数を取得する方法

FileSystemObjectのOpenTextFileを追加モードでオープンして最終行にシーク(書き込み位置を移動)させる方法です。その状態のLineプロパティはファイル行数+1の状態になっているため、Lineプロパティから1を引いた値がファイル行数になります。

10万行程度のファイルであれば数ミリ秒程度で取得できます。

Function GetLineCount(a_sFilePath) As Long
    Dim oFS As New FileSystemObject
    Dim oTS As TextStream
    Dim iLine
    
    '// 引数のファイルが存在しない場合は処理を終了する
    If (oFS.FileExists(a_sFilePath) = False) Then
        GetLineCount = -1
        Exit Function
    End If
    
    '// 追加モードで開く
    Set oTS = oFS.OpenTextFile(a_sFilePath, ForAppending)
    
    GetLineCount = oTS.Line - 1
End Function

使い方

Dim iLine
iLine = GetLineCount("C:\web\test\a.txt")

コード説明

2行目 FileSystemObjectクラスのインスタンス変数を作成するためNewしています。
3行目 FileSystemObjectのメソッドから生成されるTextStreamクラスの変数を宣言しています。ここではまだ使える状態にはなっていません。
4行目 ファイル行数取得用の変数です。
6~10行目 ファイル存在チェックを行い、存在しない場合は-1を返しています。
13行目 OpenTextFileメソッドを追加モードで開き、書き込み位置をファイル終端に移動させています。
15行目 書き込み位置がファイル終端に移動している状態でのLineプロパティはファイル行数+1を返すため、-1してファイル行数に変換しています。ファイルの最終行が改行のみであっても正しくファイル行数を取得できます。

 

2. テキストファイルを1行ずつ読み込んで行数を取得する方法

FileSystemObjectを使うのは1つ目の方法と同じですが、こちらはファイル行を1行ずつ読み飛ばしてループカウンタからファイル行数を取得する分かりやすい方法です。

処理速度は1つ目よりは遅いですが、10万行程度のファイルであれば10ミリ秒も掛からないでしょう。

Function GetLineCount(a_sFilePath) As Long
    Dim oFS As New FileSystemObject
    Dim oTS As TextStream
    Dim iLine
    
    iLine = 0
    
    '// 引数のファイルが存在しない場合は処理を終了する
    If (oFS.FileExists(a_sFilePath) = False) Then
        GetLineCount = -1
        Exit Function
    End If
    
    '// TextStreamオブジェクト作成
    Set oTS = oFS.OpenTextFile(a_sFilePath)
    
    '// ファイル終端までループ
    Do While oTS.AtEndOfStream <> True
        oTS.SkipLine
        iLine = iLine + 1
    Loop
    
    GetLineCount = iLine
End Function

使い方

Dim iLine
iLine = GetLineCount("C:\web\test\a.txt")

コード説明

2行目 FileSystemObjectクラスのインスタンス変数を作成するためNewしています。
3行目 FileSystemObjectのメソッドから生成されるTextStreamクラスの変数を宣言しています。ここではまだ使える状態にはなっていません。
4行目 ファイル行数取得用の変数です。
6行目 ファイル行数変数を0に初期化しています。
8~12行目 ファイル存在チェックを行い、存在しない場合は-1を返しています。
15行目 OpenTextFileメソッドでファイルを開きます。
17~21行目 ファイルの先頭から終端までループし、1行ごとにスキップして行数をカウントします。
23行目 取得したファイル行数を返します。

 

3. ADODBを使ってテキストファイル行数を取得する方法

ちょっと変わった方法です。ADODBを使ってデータベースのようにファイルの行数を取得します。

処理速度は10万行程度で10数ミリ秒と1つ目のと比べると遅いです。

分かりやすくするためにあえてファイル存在チェックは入れていません。必要な場合は上のコードからFileSystemObjectの変数定義と存在チェック部分をコピペしてください。

Function GetLineCount(a_sPath, a_sFileName) As Long
    Dim cn As New ADODB.Connection
    Dim rs As New ADODB.Recordset
    
    cn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & a_sPath & ";Extended Properties='Text;HDR=NO'"

    Call rs.Open("SELECT * FROM " & a_sFileName, cn, adOpenStatic, adLockOptimistic, adCmdText)
    
    GetLineCount = rs.RecordCount
End Function

使い方

Dim iLine
iLine = GetLineCount("C:\web\test\", "a.txt")

コード説明

2~3行目 ADODBの接続とレコードセットのインスタンス変数を作成しています。
5行目 接続先の設定を行っています。
7行目 ファイルの読み込みを行っています。
9行目 RecordCountプロパティでファイル行数を取得しています。
]]>
VBAでShift-JISのファイルをEUCに変換する https://vbabeginner.net/convert-shift-jis-files-to-euc/ Tue, 26 Dec 2017 18:01:10 +0000 http://vbabeginner.net/?p=2324 他の変換は以下のリンクを参照

このページではShift-JISからBOM付きのEUCへのファイル変換について紹介しています。

その他の変換は以下を参照ください。

 

文字コード変換にはActiveX Data Objectsを利用する

VBAの標準機能では文字コードはUnicode(UTF16-LE)とASCIIの2種類が扱えます。

しかし実際に目にするファイルの文字コードはShift-JIS、UTF-8のBOM付き、UTF-8のBOM無し、EUCの4種類になってきます。

そして問題になるのが文字コードの相互変換の方法で、変換が必要な場合にどうやって変換したらいいのか、という点です。

Linuxでは文字コードを変換するnkfコマンドやiconvコマンドがありますが、Windowsの場合は簡単に使えるものがなく、.NetのSystem.Text.Encodingクラス、PerlのEncodeモジュール、PHPのmb_convert_encoding関数、など、どうやって実装しようかなあ、と悩まされる問題があります。ただ、これら.NetもPerlもPHPもプログラミング言語や開発環境のインストールが必要になってくるため、どうしてもハードルが高くなります。

そこでVBAでActiveX Data Objectsを利用しましょう。Excelさえ入っていればインストール不要で文字コード変換が可能です。

なお、上に挙げた.Netは標準でインストール済みのPowerShellでも使えますので、分かる方はそちらでもいいと思います。

事前設定

以下のコードではADODB.Streamを利用するための参照設定が必要です。

VBAの画面で、ツールメニュー→参照設定→Microsoft ActiveX Data Objects x.x Library にチェックを付けます。ここではバージョン6.1ですが、最新バージョンを選択すれば問題ありません。

参照設定を行わない場合はCreateObject関数を利用し、以下のコードの「ADODB.Stream」のオブジェクト変数の初期化の部分を「CreateObject(“ADODB.Stream”)」としてください。

なお、参照設定をした方がわずかではありますが処理速度は速くなります。また、.を入力するとプロパティやメソッドが表示される利点もあります。

Shift-JISからBOM付きEUCに変換

文字コードがShift-JISのテキストファイルをBOM付きのEUCに変換するマクロです。

通常、EUCのファイルはUnixのファイルが多いため、改行コードもLFに変換するようにしています。変換が不要な場合は後述の説明を参照してください。

関数の引数にファイルのフルパスを渡して使います。

Sub SjisToEuc(a_sFrom, a_sTo)
    Dim streamRead  As New ADODB.Stream '// 読み込みデータ
    Dim streamWrite As New ADODB.Stream '// 書き込みデータ
    Dim sText                           '// ファイルデータ
    
    '// ファイル読み込み
    streamRead.Type = adTypeText
    streamRead.Charset = "Shift-JIS"
    streamRead.Open
    Call streamRead.LoadFromFile(a_sFrom)
    
    '// 改行コードCRLFをLFに変換
    sText = streamRead.ReadText
    sText = Replace(sText, vbCrLf, vbLf)
    
    '// ファイル書き込み
    streamWrite.Type = adTypeText
    streamWrite.Charset = "EUC-JP"
    streamWrite.Open
    
    '// データ書き込み
    Call streamWrite.WriteText(sText)
    
    '// 保存
    Call streamWrite.SaveToFile(a_sTo, adSaveCreateOverWrite)
    
    '// クローズ
    streamRead.Close
    streamWrite.Close
End Sub

 

コードの説明

1行目 第一引数に変換元のShift-JISファイルのフルパスを指定し、第二引数に書き込み先のEUCファイルのフルパスを指定します。第一引数のファイルをそのまま変換したい場合は第二引数に第一引数と同じファイルパスを指定しても構いません。
2、3行目 ActiveX Data Objectsのクラス変数のインスタンス変数を作成しています。Shift-JISファイルを読み込む変数と、EUCに変換する先のファイルへの書き込み変数用です。なお、参照設定をしている場合はADODB.Streamの部分はStreamと書いても問題なく動作します。
4行目 書き込み用のデータを格納する変数です。途中の処理で改行コードの変換を行っています。
6行目 7行目から10行目は読み込むShift-JISファイルの設定を行います。
7行目 Typeプロパティにはバイナリモード(adTypeBinary)とテキストモード(adTypeText)の2種類があり、ここではテキストモードを指定しています。
8行目 Charsetプロパティには文字セットを指定します。読み込むファイルがShift-JISのためそのように指定しています。
9行目 データストリームを開きます。ここではまだ引数のShift-JISファイルは参照していません。
10行目 ファイルの内容をストリームに読み込みます。
13行目 読み込んだShift-JISファイルデータを取得します。
14行目 取得したデータの改行コードのCRLFをLFに置換します。LFへの変換が不要な場合は、この行をコメントアウトしてください。
16行目 17行目から25行目は書き込むEUCファイルの設定を行います。
17行目 Typeプロパティにはテキストモードを指定しています。
18行目 書き込むファイルの文字コードであるEUCを指定しています。
19行目 データストリームを開きます。ここではまだ引数のEUCファイルは指定していません。
22行目 WriteTextメソッドでデータの書き込みを行います。
25行目 コピー後のEUCのデータを引数のファイルパスに保存します。
28、29行目 読み込みデータ、書き込みデータをクローズします。

 

CreateObject関数を利用する場合

CreateObject関数を利用する場合は2行目と3行目を以下のように書き換えます。

Dim streamRead  As Object
    Dim streamWrite As Object
    Set streamRead = CreateObject("ADODB.Stream")
    Set streamWrite = CreateObject("ADODB.Stream")

以降のコードは変更不要です。

サンプルコード

Sub SjisToEucTest()
    '// 別ファイルにEUCファイルを保存する場合。
    Call SjisToEuc("C:\web\test\sjis.txt", "C:\web\test\euc.txt")
    
    '// Shift-JISファイルをそのままEUCファイルに変換して保存する場合、
    '// 第一引数と同じファイルパスを第二引数にも設定する。
    Call SjisToEuc("C:\web\test\sjis.txt", "C:\web\test\sjis.txt")
End Sub

]]>
VBAでEUCのファイルをShift-JISに変換する https://vbabeginner.net/convert-euc-files-to-shift-jis/ Tue, 26 Dec 2017 17:47:19 +0000 http://vbabeginner.net/?p=2317 他の変換は以下のリンクを参照

このページではEUCのファイルをShift-JISに変換する方法を紹介しています。

その他の変換は以下を参照ください。

 

文字コード変換にはActiveX Data Objectsを利用する

VBAの標準機能では文字コードはUnicode(UTF16-LE)とASCIIの2種類が扱えます。

しかし実際に目にするファイルの文字コードは主にShift-JIS、UTF-8のBOM付き、UTF-8のBOM無し、EUCの4種類になってきます。

そして問題になるのが文字コードの相互変換の方法で、変換が必要な場合にどうやって変換したらいいのか、という点です。

Linuxでは文字コードを変換するnkfコマンドやiconvコマンドがありますが、Windowsの場合は簡単に使えるものがなく、.NetのSystem.Text.Encodingクラス、PerlのEncodeモジュール、PHPのmb_convert_encoding関数、など、どうやって実装しようかなあ、と悩まされる問題があります。ただ、これら.NetもPerlもPHPもプログラミング言語や開発環境のインストールが必要になってくるため、どうしてもハードルが高くなります。

そこでVBAでActiveX Data Objectsを利用しましょう。Excelさえ入っていればインストール不要で文字コード変換が可能です。

なお、上に挙げた.Netは標準でインストール済みのPowerShellでも使えますので、分かる方はそちらでもいいと思います。

事前設定

以下のコードではADODB.Streamを利用するための参照設定が必要です。

VBAの画面で、ツールメニュー→参照設定→Microsoft ActiveX Data Objects x.x Library にチェックを付けます。ここではバージョン6.1ですが、最新バージョンを選択すれば問題ありません。

参照設定を行わない場合はCreateObject関数を利用し、以下のコードの「ADODB.Stream」のオブジェクト変数の初期化の部分を「CreateObject(“ADODB.Stream”)」としてください。

なお、参照設定をした方がわずかではありますが処理速度は速くなります。また、.を入力するとプロパティやメソッドが表示される利点もあります。

EUCからShift-JISに変換

文字コードがEUCのテキストファイルをShift-JISに変換するマクロです。

通常、EUCのファイルはUnixのファイルが多く、その場合の改行コードはLFのため、改行コードもWindows用のCRLFに変換するようにしています。変換が不要な場合は後述の説明を参照してください。

関数の引数にファイルのフルパスを渡して使います。

Sub EucToSjis(a_sFrom, a_sTo)
    Dim streamRead  As New ADODB.Stream '// 読み込みデータ
    Dim streamWrite As New ADODB.Stream '// 書き込みデータ
    Dim sText                           '// ファイルデータ
    
    '// ファイル読み込み
    streamRead.Type = adTypeText
    streamRead.Charset = "EUC-JP"
    streamRead.Open
    Call streamRead.LoadFromFile(a_sFrom)
    
    '// 改行コードLFをCRLFに変換
    sText = streamRead.ReadText
    sText = Replace(sText, vbLf, vbCrLf)
    sText = Replace(sText, vbCr & vbCr, vbCr)
    
    '// ファイル書き込み
    streamWrite.Type = adTypeText
    streamWrite.Charset = "Shift-JIS"
    streamWrite.Open
    
    '// データ書き込み
    Call streamWrite.WriteText(sText)
    
    '// 保存
    Call streamWrite.SaveToFile(a_sTo, adSaveCreateOverWrite)
    
    '// クローズ
    streamRead.Close
    streamWrite.Close
End Sub

 

コードの説明

1行目 第一引数に変換元のEUCファイルのフルパスを指定し、第二引数に書き込み先のShift-JISファイルのフルパスを指定します。第一引数のファイルをそのまま変換したい場合は第二引数に第一引数と同じファイルパスを指定しても構いません。
2、3行目 ActiveX Data Objectsのクラス変数のインスタンス変数を作成しています。EUCファイルを読み込む変数と、Shift-JISに変換する先のファイルへの書き込み変数用です。なお、参照設定をしている場合はADODB.Streamの部分はStreamと書いても問題なく動作します。
4行目 書き込み用のデータを格納する変数です。途中の処理で改行コードの変換を行っています。
6行目 7行目から10行目は読み込むEUCファイルの設定を行います。
7行目 Typeプロパティにはバイナリモード(adTypeBinary)とテキストモード(adTypeText)の2種類があり、ここではテキストモードを指定しています。
8行目 Charsetプロパティには文字セットを指定します。一般的に日本で使うEUCはJIS X 0208のEUC-JPのため、読み込むファイルもEUC-JPに指定しています。
9行目 データストリームを開きます。ここではまだ引数のEUCファイルは参照していません。
10行目 ファイルの内容をストリームに読み込みます。
13行目 読み込んだEUCファイルデータを取得します。
14行目 取得したデータの改行コードのLFをCRLFに置換します。CRLFへの変換が不要な場合は、この行をコメントアウトしてください。
15行目 変換元のEUCファイルの改行コードがLFではなくCRLFだった場合、14行目での改行コード変換を行うとCR+CR+LFになってしまいます。そのCR+CRを、CRに置換します。CRLFへの変換が不要な場合は、この行をコメントアウトしてください。
17行目 18行目から26行目は書き込むShift-JISファイルの設定を行います。
18行目 Typeプロパティにはテキストモードを指定しています。
19行目 書き込むファイルの文字コードであるShift-JISを指定しています。
20行目 データストリームを開きます。ここではまだ引数のShift-JISファイルは指定していません。
23行目 WriteTextメソッドでデータの書き込みを行います。
26行目 コピー後のShift-JISのデータを引数のファイルパスに保存します。
29、30行目 読み込みデータ、書き込みデータをクローズします。

 

CreateObject関数を利用する場合

CreateObject関数を利用する場合は2行目と3行目を以下のように書き換えます。

Dim streamRead  As Object
    Dim streamWrite As Object
    Set streamRead = CreateObject("ADODB.Stream")
    Set streamWrite = CreateObject("ADODB.Stream")

以降のコードは変更不要です。

サンプルコード

Sub EucToSjisTest()
    '// 別ファイルにShift-JISファイルを保存する場合。
    Call EucToSjis("C:\web\test\Euc.txt", "C:\web\test\sjis1.txt")
    
    '// EUCファイルをそのままShift-JISファイルに変換して保存する場合、
    '// 第一引数と同じファイルパスを第二引数にも設定する。
    Call EucToSjis("C:\web\test\Euc.txt", "C:\web\test\Euc.txt")
End Sub

]]>