OneDriveの同期フォルダに置いたブックで、ブックと同じ場所にCSVを書き出すマクロを確かめたところ、書き出しの行でエラーになりました。この環境では、ThisWorkbook.Pathがhttps://で始まるURLを返していたためです。
書き出すCSVは、Excelで開いても文字化けしないBOM付きUTF-8にしたいところです。読み込む側の設定は、【Power Query】CSVの文字化けをEncoding指定で防ぐ方法に書きました。この記事は書き出す側です。BOM付きUTF-8の書き出し方と、URLパスのときに書き出す前に止める検知を、実機で確かめた結果と一緒にまとめます。
BOM付きUTF-8で書き出すコード
Option Explicit
Private Const adTypeText As Long = 2
Private Const adSaveCreateOverWrite As Long = 2
' パスがURL(https:// など)かどうか
Public Function IsUrlPath(ByVal p As String) As Boolean
IsUrlPath = (InStr(p, "://") > 0)
End Function
' BOM付きUTF-8でCSVを書き出す
Public Sub WriteCsvUtf8Bom(ByVal filePath As String, ByVal csvText As String)
Dim st As Object
Set st = CreateObject("ADODB.Stream")
st.Type = adTypeText
st.Charset = "UTF-8" ' 最初のWriteTextより前に指定する
st.Open
st.WriteText csvText
st.SaveToFile filePath, adSaveCreateOverWrite
st.Close
End Sub
Public Sub ExportCsv()
If IsUrlPath(ThisWorkbook.Path) Then
MsgBox "ブックの場所がURLのため、書き出せません。", vbExclamation
Exit Sub
End If
WriteCsvUtf8Bom ThisWorkbook.Path & "\sample.csv", "A,B" & vbCrLf & "1,2"
End Sub
ポイントは2つです。Charsetを"UTF-8"にして最初のWriteTextより前に指定すること。SaveToFileにadSaveCreateOverWriteを付けること。以下で、それぞれ実測した結果を示します。
BOMが付く条件
Charset = "UTF-8"を最初のWriteTextより前に指定すると、先頭にEF BB BFの3バイトが付きます。指定する位置はOpenの前でも後でも同じでした。WriteTextを2回呼んでも、BOMは先頭に1回だけです。Charsetを指定しないと、既定はUnicodeで、UTF-16LEのBOM(FF FE)が付きます。WriteTextのあとでCharsetを変えようとすると、実行時エラー3219「このコンテキストで操作は許可されていません。」になります。そのまま保存すると、UTF-16LEのファイルになりました。- 空文字を
WriteTextして保存すると、BOMの3バイトだけのファイルになります。WriteTextを一度も呼ばずに保存すると、0バイトのファイルです。 Typeを省略しても、この環境ではテキストとして書けました(Typeの既定値は2、adTypeTextでした)。
Charsetの位置については、Microsoft LearnのCharsetプロパティにも、開いているStreamではPositionが先頭(0)のときに設定する、既定値はUnicode、という説明があります。実測の結果と一致していました。

日本語の「あ」は、UTF-8ではE3 81 82の3バイト、Shift_JISでは82 A0の2バイトで、Shift_JISにBOMは付きません。U+20BB7の文字(サロゲートペア)は、UTF-8で4バイト(F0 A0 AE B7)になりました。Shift_JISでバイト数を数えるときの注意は、【VBA】全角氏名のバイト数チェックはLenBとStrConvで行うにまとめています。
このファイルをExcelで開くと、BOMありのファイルは「あい」のまま開きました。BOMなしのファイルは、同じバイト列が別の3文字に化けました。
SaveToFileのオプションは省略しない
SaveToFileのオプションを省略して、すでにあるファイルに保存すると、実行時エラー3004「ファイルへ書き込めませんでした。」になります。マクロを2回目に実行したときに止まるのは、これが原因です。adSaveCreateOverWrite(2)を指定すると、上書きできました。
BOMなしにしたいとき
BOMなしのUTF-8が必要なときは、いったんBOM付きで書き出してから、バイナリとして先頭の3バイトを除いて保存します。実測では、E3 81 82 2C 42になりました。BOMなしのファイルを読む側で判定する方法は、【Power Query】CSVの文字コードを置換文字で判定するに書きました。
Private Const adTypeBinary As Long = 1
Private Sub StripBom(ByVal src As String, ByVal dst As String)
Dim a As Object, b As Object
Set a = CreateObject("ADODB.Stream")
a.Type = adTypeBinary
a.Open
a.LoadFromFile src
a.Position = 3 ' 先頭の3バイト(BOM)を飛ばす
Set b = CreateObject("ADODB.Stream")
b.Type = adTypeBinary
b.Open
a.CopyTo b
b.SaveToFile dst, adSaveCreateOverWrite
a.Close
b.Close
End Sub
URLのときは、書き出す前に止める
実務のマクロには、パスがURLのときに処理を止める検知を入れています。今回、手元の環境で確かめた結果は次のとおりです。
- 個人のOneDriveの同期フォルダに置いたブックでは、
ThisWorkbook.Pathがhttps://で始まるURL(ホストはlive.net)でした。Pathに"\"とファイル名をつないでSaveToFileすると、エラー3004になりました。 - 同期していないローカルフォルダのブックでは、
Pathは通常のパスで、書き出しは成功しました。 - 同じ環境で、環境変数
OneDriveが指す同期フォルダ(ローカルパス)には、書き出せました。 SaveToFileにダミーのURL(https://example.invalid/…)を渡しても、同じエラー3004になりました。- このExcelにはAutoSaveのスイッチが表示されず、
AutoSaveOnはFalseのままでしたが、それでもPathはURLを返しました。
エラー3004は、すでにあるファイルに上書きできないときと同じ番号・同じ文言です。エラーだけでは、URLだから書けないのか、別の原因なのかが分かりません。フォルダの確認でも区別できませんでした。FileSystemObject.FolderExistsはFalse、Dirは実行時エラー52になりました。そのため、書き出す前に、パスの文字列で判定して止めます。
判定は、Left(path, 4) = "http"でも動きますが、httpdata\xのような相対パスもURLと判定してしまいます。InStr(path, "://") > 0なら、この例を除けました。
| パスの文字列 | Left(path, 4) = “http” | InStr(path, “://”) > 0 |
|---|---|---|
| https://example.invalid/a | True | True |
| HTTPS://EXAMPLE.INVALID/a(大文字) | True | True |
| httpdata\x(相対パス) | True | False |
なお、InStr(path, "://")は、C:\work・\\srv\share\x・D:\http_logs・空文字でもFalseでした。Leftを使う場合は、LCaseで小文字にそろえて比べました。
今回確認した環境と、確認できていないこと
確認したのは、Excel 16.0.19127(Windows版)、日本語環境(コードページ932)、個人のOneDriveの同期フォルダに置いたブックです。次は確認できていません。
- ブラウザやTeams経由で開いた、SharePoint上のブックの
Path(ホストがsharepoint.comになる場合) - AutoSaveのスイッチが使えるExcelでの
Pathの値 - Mac、他のバージョンのExcel
- EUC-JPやUTF-16での書き出し
まとめ
- BOM付きUTF-8は、
Charset = "UTF-8"を最初のWriteTextより前に指定する。Charsetを指定しないとUTF-16LEになる SaveToFileにはadSaveCreateOverWriteを付ける。省略すると、2回目の実行でエラー3004になる- OneDriveの同期フォルダのブックは、
ThisWorkbook.PathがURLになることがある。書き出す前にInStr(path, "://") > 0で検知して止める


コメント