[発掘:Access]外部テキストファイルとの接続方法

#これを発掘する理由
これは検索しているうちに偶然発見した。
Access97から2000時代の記述である。

https://megalodon.jp/2007-1011-1800-17/x7net.com/~access/AcTipsGnrlHowToAttachExternalText.html
 更新日 	2005/01/28
##概要
外部のテキスト形式ファイル（CSV、TXT、TAB、ASC など）に接続/操作する手段は用途に応じて複数用意されています。これらを熟知していれば、テキスト形式ファイルとの連携に関しての障害はほとんどありません。しかし、接続方法個々に特徴や制限があり、その多様性自体が初心者を混乱させかねないのも、また事実です。

ここでは外部テキスト形式ファイルに接続/操作する手段を整理して、用途に応じて適切な手段を選択できるような指針を提供します。

※ 以下で紹介する各種メソッドについては、ヘルプに詳しい説明や使用例が載っています。本トピックはそれら各種接続手段の位置づけを明確にするものであり、個々の使用法詳細をヘルプに代わって解説するものではありません。
オプション値などについては省略している場合がありますので、各メソッドの詳細については、必ずヘルプを当たってください。
また筆者自身の不明により、ADO での操作は一部を除き取り扱っていません。DAO 中心です。 

###接続手法&機能一覧



|メソッド	|可能な操作|
|:--------------|:------|
|Shell	|起動のみ|
|DoCmd.OutputTo	|オブジェクトのエクスポート|
|DoCmd.TransferText	|オブジェクトのインポート/リンク/エクスポート|
|SQL 文	|データの取得/追加/テーブル作成|
|OpenRecordset	|DAO.Recordset の操作|
|Open	|ADODB.Recordset の操作|
|Open, Line Input #, Print #	|テキストファイルの操作|
|FileSystemObject	|FileSystemObject オブジェクトの操作|
|API	|番外編：API によるファイル入出力|

###Shell 関数
####機能

外部テキストファイルを開くだけで何も操作をしなくてよい場合は、もっとも簡単なのがこの Shell 関数です。

`Call Shell("NOTEPAD C:\Temp.txt", 1)`

Shell 関数自体は別にテキストファイルを操作するための関数ではなく、単にアプリケーションを呼び出すだけで、呼び出し先はメモ帳でも秀〇でも Excel でも何でもかまいません。上記例ではコマンドライン オプションでテキストファイルのフルパスを渡すことによって、メモ帳に起動と同時にテキストファイルを開かせています。

なおテキストファイルのフルパス中に半角スペース文字が混じる場合は、下記のようにパスをさらに二重のダブル クォーテーションで括る必要があります。

`Call Shell("NOTEPAD ""C:\Documents and Settings\YU-TANG\デスクトップ\Temp.txt""", 1) `

###補足

アプリケーションを指定せずに拡張子の関連付けに応じて適切なアプリケーションで実行させたい場合は、DOS の START コマンドや Windows API の ShellExecute 関数を使う方法もあります。

テキストファイルはもっとも基本的なファイル形式であるため、テキストファイルの操作に関しては DOS コマンド (COPY など) で事足りてしまう場合も少なくありません。

DOS コマンドについては、論を外れるので（詳しくないだけ、という話も^ ^;）踏み込みません。興味があれば書籍や Web サイトを当たってください。
###参考情報(すべてリンク切れ)

417089 - [ACC2002]Shell 関数の引数に半角スペースが含まれているとエラーが発生[WebArchive](https://web.archive.org/web/20150223112130/http://support.microsoft.com/kb/417089/JA/)
http://support.microsoft.com/kb/417089/ja/
404917 - [AC95] Shell 関数を用いて DOS のコマンドを実行する方法[Webarchive](https://web.archive.org/web/20150223115506/http://support.microsoft.com/kb/404917/JA/)
http://support.microsoft.com/kb/404917/ja/
408441 - [VB4] Shell 関数の補足説明と制限事項[WebArchive](https://web.archive.org/web/20150223112129/http://support.microsoft.com/kb/408441/JA/)
http://support.microsoft.com/kb/408441/ja/
170918 - [VB] 関連付けられたファイルを ShellExecute で起動する方法 (32 ビット)[WebArchive](https://web.archive.org/web/20150223115338/http://support.microsoft.com/kb/170918/JA/)
http://support.microsoft.com/kb/170918/ja/
#### 現行の参考情報
#####MSのShell関数の注意事項はバグフィックスされたものを除きここに集約されている
[Shell関数](https://support.office.com/ja-jp/article/shell-%E9%96%A2%E6%95%B0-ff2e4b1b-712d-4e34-aea6-6832eadd3c63)
[拡張子関連づけでファイルを開く MOUG](https://www.moug.net/tech/exvba/0060072.html)
[Tipsfound › Excel VBA › Shell 関数](https://www.tipsfound.com/vba/05shell)
[VBAでコマンドプロンプトの起動とコマンドの実行を行う Excel作業をVBAで効率化](https://vbabeginner.net/vba%E3%81%A7%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%97%E3%83%AD%E3%83%B3%E3%83%97%E3%83%88%E3%81%AE%E8%B5%B7%E5%8B%95%E3%81%A8%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AE%E5%AE%9F%E8%A1%8C/)
[VBAでバッチファイル（bat）を実行する Excel作業をVBAで効率化](https://vbabeginner.net/vba%E3%81%A7%E3%83%90%E3%83%83%E3%83%81%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%EF%BC%88bat%EF%BC%89%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B/)
[OfficeTanaka    Excel VBA Excel VBA Tips MS-DOSコマンドの標準出力を取得する](http://officetanaka.net/excel/vba/tips/tips27.htm)

Shell 関数の引数に半角スペースが含まれているとエラーが発生の趣旨は以下のようなもの
https://soudan1.biglobe.ne.jp/qa7849922.html

```vb
rc = Shell("PsQREdit.exe", vbNormalFocus)
'　　↓
rc = Shell("""C:\Program Files (x86)\Psytec\QR Code Editor\PsQREdit.exe""", vbNormalFocus)
```

で、動くのではないかと思います。

notepad.exeの場合は、path環境変数に、実行ファイルの置かれているパスが入っているため、フルパスを省略できますが、そうでない場合は、フルパス指定が必要。
その際に、フルパスに半角スペースが含まれる場合は、上記の通り、ダブルクォートで囲む必要がありま

###DoCmd.OutputTo メソッド
####機能

オブジェクトをエクスポートします。

DoCmd.OutputTo acOutputTable, "tblData", _
  acFormatTXT, "C:\Exported.txt", True

第 4 引数の出力ファイル名はフルパスで記述します。何か特殊な理由が無い限り、マッピングされたドライブ名（例 >> Q:\SheredFolder\Temp.txt）ではなく、UNC パス（例 >> \\ServerName\SheredFolder\Temp.txt）を使用した方がよいでしょう。

DoCmd.OutputTo メソッドは、書式を視覚的に維持した形式で出力するため、出力されたテキストファイルは罫線記号によって整形された独自の形式になります。

構造化されたデータとしての再利用はほぼできなくなりますので、***出力後のファイルを Excel 等の他アプリで読み込んで再使用する可能性がある場合は、後述する DoCmd.TransferText メソッドを使用した方がよいでしょう。***

ただし、DoCmd.OutputTo メソッドには引数を省略するとプロンプトが上がって、たとえば出力先のパスやファイル名のユーザーによる任意指定が可能になるという利点があります（DoCmd.TransferText メソッドで引数を省略するとエラーになります）。用途によっては、かなり便利なメソッドです。

###DoCmd.TransferText メソッド
####機能

オブジェクトをインポート/リンク/エクスポートします。

`DoCmd.TransferText acImportDelim, "インポート定義1", _
  "tblImported", "C:\Temp.csv", True`

第 4 引数の出力ファイル名はフルパスで記述します。何か特殊な理由が無い限り、マッピングされたドライブ名（例 >> Q:\SheredFolder\Temp.txt）ではなく、UNC パス（例 >> \\ServerName\SheredFolder\Temp.txt）を使用した方がよいでしょう。

ポイントになるのは、第 2 引数に指定するインポート/エクスポート定義名です。

DoCmd.TransferText メソッドでエラーになったり、意図どおりの処理ができないケースの多くが、この第 2 引数を省略することに起因しています。

第 2 引数は、固定長テキストファイル以外の場合は省略できることになっていますが、TransferText メソッドの動作に精通しているのでない限り、インポート/エクスポート定義名は極力指定することをお奨めします。

インポート/エクスポート定義は、前もって作成しておく必要があります。

作成方法が分からないという方は、下記の手順にのっとって作成してください。

<details><summary>インポート/エクスポート定義の作成方法</summary><div>

http://x7net.com/~access/javaScript:void(0)
<OL>
<li>まず [ファイル]-[外部データの取り込み]-[インポート] でテキストファイルを選んで <インポート> からテキスト インポート ウィザードに入るか、あるいは [ファイル]-[エクスポート] で <保存> からテキスト エクスポート ウィザードに入ってください。</Li>
以下はテキスト エクスポート ウィザードの画面を例にとって説明しますが、テキスト インポート ウィザードも基本的には同じです。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126919/00d9f90c-5507-8f63-1dff-b1c4259c7a8b.png)
ダイアログの左下隅にある <設定> をクリックし、インポート/エクスポート定義ダイアログを表示させます。 
<li>ダイアログに表示されている各種設定項目に、適切な値を指定していきます。 </li>
通常は [ファイル形式] と [フィールド区切り記号] を指定するくらいでしょう。

[言語] と [コードページ] 欄は、Access 97 以前にはありません。

余談ですが、Access 2000 以降でインポートしたテキストデータが文字化けするときは、この [言語] と [コードページ] に適切な設定がされていない場合がありますので、一度チェックしてみるとよいでしょう。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126919/c867ea76-295e-ef6b-69e0-f2b0d19c8879.png)
さて、[フィールドの情報] 欄には既定で「フィールド名」しか表示されていませんが、実は既定では表示されていない設定項目が他にもあります。

ときには、その非表示項目を設定したい場合もあるでしょう。

その場合は、まず列見出し「フィールド名」の右端にカーソルを合わせてください。

下図のように、カーソルの形が左右の矢印に変化するのが確認できます。

この状態で、ダブルクリックするか、あるいはマウスで右にドラッグしてみてください。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126919/4235b030-48c1-b1a4-1fe6-951bb4a37b35.png)

すると、今まで非表示だった項目が、新たに出現します。

どんどんダブルクリックして、全部表示させてみましょう。

下図が、すべて表示させた状態です。

全部で各フィールドにつき 6 項目の設定列が存在することが分かります。

詳細な設定が必要なときは、ここで行いましょう。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126919/df4d01b3-26c1-af62-8640-8dfbcd81be7a.png)
必要な設定を終えたら、<保存> をクリックします

下図のようなダイアログが表示されるので、分かりやすい名前を付けて <OK> をクリックして保存します。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126919/ea64eb96-32ab-8631-8967-d60c8df0784a.png)
ウィザードの画面に戻ると思いますが、インポート/エクスポート定義を作成してしまえば用済みですので、<キャンセル> をクリックして逃げてしまって構いません。

後はマクロや VBA のコード内で、必要な処理を行う際に、この定義を指定するだけです。 
</OL>

</div></details>

#####編注：現在参照できるサイト
[Docs>Access VBA..>DoCmd.OutputTo メソッド (Access)](https://docs.microsoft.com/ja-jp/office/vba/api/access.docmd.outputto)
######注意:Access2007はPDFファイルやXPSファイル出力にはアドインが必要
アドインをインストールした後にのみ、2007 Microsoft Office システム プログラムから PDF または XPS ファイル形式で保存することができます。 詳細については、「2007 Microsoft Office プログラム用アドイン: Microsoft PDF/XPS 保存アドイン」を参照してください。
[AcOutputObjectType 列挙 (Access)](https://docs.microsoft.com/ja-jp/office/vba/api/access.acexportquality)
[AcExportQuality 列挙 (アクセス)](https://docs.microsoft.com/ja-jp/office/vba/api/access.acexportquality)
###SQL 文
####機能
外部テキストファイルのデータを取得/追加/作成します。

これは Jet Database Engine の機能で、SQL 文中から Text-ISAM 経由で実行されます。したがって、VBA から動的クエリーとして実行させる場合は Execute メソッドあるいは DoCmd.RunSQL メソッドを、静的クエリーとして実行させる場合はあらかじめクエリーを作成しておいて、DoCmd.OpenQuery メソッドを使用することになります。

また、選択クエリーとして、OpenRecordset のソースにも使用できます。

SQL 文の記述には、IN 句を使用する 2 パターンと、使用しない 1 パターンの合計 3 パターンの記述法があり、どれを使っても結果は同じです。以下に、選択クエリーの SQL ビューに記述する前提で、3 パターン提示します。

`SELECT * FROM [Temp#CSV] IN "" "Text;DATABASE=C:\FolderName;HDR=NO;";`

`SELECT * FROM [Temp#CSV] IN "C:\FolderName" "Text;HDR=NO;";`

`SELECT * FROM [Text;DATABASE=C:\FolderName;HDR=NO;].[Temp.CSV];`

上記例は、フィールド見出し行を含まない CSV 形式ファイル C:\FolderName\Temp.csv を表示する選択クエリーの例です。

これは解説が必要でしょう。

テキストファイルの場合、親フォルダをデータベースに見立て、ファイルをテーブルとして取り扱います。その際、IN 句では拡張子の前のドット( .)をシャープナンバー記号(#)に置き換えて指定します。

※「♯」（シャープ）は音楽記号で、「＃」（ナンバー、ハッシュ、パウンド、いげた）が番号記号でした。

先頭行をフィールド見出し行としない場合は、HDR パラメータに "NO" を指定します。これにより、先頭から既定のフィールド名として F1, F2..., Fn が割り当てられます。

ただし、この構文はヘルプ通りではありますが、少々古いのかもしれません。

Access 2000 のクエリーに上記例のような SQL 文を記述すると、別にエラーにはなりませんが、保存時に下記のような構文に自動修正されてしまいます。

`SELECT * FROM [Temp#CSV] IN '' [Text;DATABASE=C:\FolderName;HDR=NO;];`

`SELECT * FROM [Temp#CSV] IN 'C:\FolderName'[Text;HDR=NO;];`

`SELECT * FROM [Text;DATABASE=C:\FolderName;HDR=NO;].Temp.CSV;`

微妙な差ですが...。

ダブルクォーテーションが無くなっているところを見ると、クエリーの SQL ビューをコピーしてそのまま VBA のコード内に貼りつけての使用を考慮した修正ということでしょうか？

別に前出の構文も違反ではないので、どちらでも動作には支障ありません。


さて、Text-IISAM を使用して実行できる SQL 文には制限があります。

<Details><Summary>編注:Access2013でText-IISAMは削除</Summary><Div>
[Access 2013 で廃止、変更された機能](https://support.office.com/ja-jp/article/access-2013-%E3%81%A7%E5%BB%83%E6%AD%A2%E3%80%81%E5%A4%89%E6%9B%B4%E3%81%95%E3%82%8C%E3%81%9F%E6%A9%9F%E8%83%BD-bc006fc3-5b48-499e-8c7d-9a2dfef68e2f)
Access による Jet 3.x IISAM のサポート
Access 97 データベースは、Access 2013 では開くことができなくなりました。 ファイルをアップグレードする必要があります。 これを行うには、Access 2010 または Access 2007 でファイルを開き、.accdb ファイル形式で保存した後、Access 2013 で開きます。

この変更は、Access 97 データベースにリンクされているデータベースにも影響します。

Access 97 データベースは、Access 2013 では開くことができなくなりました。 ファイルをアップグレードする必要があります。 これを行うには、Access 2010 または Access 2007 でファイルを開き、.accdb ファイル形式で保存した後、Access 2013 で開きます。

この変更は、Access 97 データベースにリンクされているデータベースにも影響します。
Jet 3.x IISAM ドライバーのサポートが削除されたのは、このドライバーが提供されなくなったためです。
</Div>
</Details>

以下に表にしてみました。

|クエリー種別	|ステートメント	|操作の許可|
|:-----:|:-----:|:-----:|
|選択クエリー	|SELECT	|〇|
|削除クエリー	|DELETE	|×|
|更新クエリー	|UPDATE	|×|
|追加クエリー	|INSERT INTO	|〇|
|テーブル作成クエリー	|SELECT...INTO	|〇|

<details><summary>編注:クエリーは現在はクエリ</summary><div>で表記が統一されているが、この当時はクエリーだった。当時の表記にも価値があるので、原文の通りとしている。</div></details>

これはあくまで外部テキストファイルを操作対象とした場合の話です。

たとえば更新クエリーでも、外部テキストファイルを更新することはできませんが、外部テキストファイルのデータでローカルテーブルを更新する更新クエリーであれば、ふつうに実行できます。

この辺りは理屈を並べるよりも実例を見たほうが早いので、幾つか例を挙げます。

選択クエリーに関しては前出の例を参照してください。

まず、外部 CSV ファイル C:\FolderName\Temp.csv のレコードを、ローカルテーブル tblCustomer に追加する SQL 文です。

#####CSV ファイル⇒（インポート）⇒既存ローカルテーブル

```sql
INSERT INTO tblCustomer ( CstmrCode, CstmrName, CstmrAddress )
SELECT [Temp#CSV].F1, [Temp#CSV].F2, [Temp#CSV].F3
FROM Temp#CSV IN "C:\FolderName" "Text;HDR=NO;";
```

先頭行を見出し行として使用しないために、HDR=NO を指定しています。また、フィールド指定の際は自動的に割り当てられるフィールド名 F1、F2、F3 を使用し、ファイル名に含まれる # を日付リテラルの接頭辞/接尾辞と混同されないよう、[ ] で括って識別子であることを明示しています。

次は外部 CSV ファイル C:\FolderName\Temp.csv のレコードを、新規ローカルテーブル tblTempNew として新たに作成する SQL 文です。
CSV ファイル⇒（テーブル作成）⇒新規ローカルテーブル

```sql
SELECT [Temp#csv].*
INTO [tblTempNew]
FROM [Temp#csv] IN "C:\FolderName" "Text;HDR=NO;";
```

これで作成されるテーブルには、インデックスの設定がありません。注意してください。

最後に、ローカルテーブルを外部 CSV ファイル C:\FolderName\Exported.csv として出力する SQL 文を、気分で 2 パターンどうぞ。

#####ローカルテーブル⇒（エクスポート）⇒CSV ファイル

```sql
SELECT tblCustomer.*
INTO [Exported#csv] IN "C:\FolderName" "Text;HDR=NO;"
FROM tblCustomer;
```
```sql
SELECT CstmCode, CstmName, CstmAddress
INTO [Text;DATABASE=C:\FolderName;HDR=NO;].[Exported.csv]
FROM tblCustomer;`
```


この方法で外部 CSV ファイルにエクスポートすると、同じフォルダに Schema.ini が作成されます。中には定義情報が記録されており、次回同名のファイルをエクスポートする際には、この定義情報が優先されて使用されます。

具体的に言えば、たとえば tblCustomer を最初にエクスポートした際に、HDR=NO で見出し行を付けずに出力したとします。この時点で Schema.ini が作成されます。

2 回目に、今度は HDR=YES で出力したとします。しかし既存の Schema.ini の定義が優先されるため、出力された CSV ファイルには、見出し行が付きません。

もっとも、MS-Access にはテキストインポート/エクスポート用の DoCmd.TransferText メソッドが用意されているので、このような方法でテキスト出力を行って Schema.ini を実際に目にする機会というのは、あまり多くは無いでしょう。

ただし、固定長テキストファイルの入出力に関しては、あらかじめインポート/エクスポート定義を作成しておいて DSN パラメータで指定するか、あるいは Schema.ini を用意して Text-ISAM 情報を定義しておく必要があります。

フィールド構成が動的に変化するようなクエリーを臨機応変に固定長テキストファイルとして出力したい場合などは、あらかじめインポート/エクスポート定義を作成しておくのは困難でしょうから、Schema.ini が重宝します。

Schema.ini の詳細については、ヘルプあるいは後述する参考情報を当たってください。

また "Text;" 以下に記述される接続文字列の設定項目の実際を確認したい場合は、目的のテキストファイルのリンクテーブルをいったん手動で作成し、そのリンクテーブルをデザインビューで開いてプロパティを表示させると、説明欄にパラメータが埋め込まれていることが確認できると思います。それをコピーすれば、確実でしょう。


余談になりますが、削除できないのを承知で削除クエリーを作成して実行すると、確認ダイアログが上がりますが、そのダイアログに表示されるレコード件数が間違っている場合があります。
RecordCount のバグ
####補足

Access 97 においてテキストファイルからリンクテーブルを作成し、そのリンクテ－ブルを対象にした Domain 系定義域集計関数（DLookup 関数など）を使用すると、リンク直後は正常動作しますが、2 回目の起動時以後では実行時エラーになります（マクロ中で使用したりすると、無応答になります）。

これを回避するには、IN 句を使用して外部ファイルを直接参照する選択クエリーを作成し、そのクエリーをリンクテーブル代わりにするしかありません。

Domain 系定義域集計関数と同等のユーザー定義関数を作成すると問題ないので、どうやら Domain 系定義域集計関数は、機能から推測されるような単純な SQL 文を実行しているだけではなく、他にも何かやっているようです。

#####参考情報
242478 - [PRB: DAO Recordset RecordCount Incorrect When Based on Text File](https://jeffpar.github.io/kbarchive/kb/242/Q242478/)
187670 - [HOWTO: Use RDO and ODBC Text Driver to Open a Delimited Text](https://jeffpar.github.io/kbarchive/kb/187/Q187670/)
178717 - [INF: Excel ODBC Driver and Text ODBC Driver Notes](https://www.betaarchive.com/wiki/index.php?title=Microsoft_KB_Archive/178717)
191253 - [VB] DAO でユーザー定義カウンターを実装する方法
410871 - [[VB5] 他形式のファイルから Jet データベースへのインポート方法](https://web.archive.org/web/20141128130932/http://support.microsoft.com/kb/410871/ja/)
262537 - HOWTO: Open Delimited Text Files Using the Jet Provider's Text IIsam
149090 - ACC: How to Use Schema.ini for Accessing Text Data
210073 - ACC2000: How to Use Schema.ini to Access Text Data [mskb.pkisolutions](https://mskb.pkisolutions.com/kb/210073)
234201 - <Details><Summary>ACC2000: Using SQL to Export to Unicode by Means of the Jet Provider and Text ISAM</Summary><Div>
http://vbcsql.blogspot.com/2007/10/how-to-create-schemaini-file.html
おそらくこの記事のコードだと考えられる

```vb
Private Sub Command1_Click()
   Dim con As New ADODB.Connection
   Dim RS As ADODB.Recordset
   con.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
   "Data Source=" & App.Path & _
   ";Extended Properties=text;"
   con.Execute & _
   "CREATE TABLE [TEST1.CSV] " & _
   "( [Name] TEXT NULL, [MaxVolt] DOUBLE NULL)"
End Sub
```

</div>
</details>
155512 - [[AC97]VBAから Schema.ini ファイルを作成する方法](http://support.microsoft.com/kb/155512/ja/)
210001 - ACC2000: How to Programmatically Create a Schema.ini File
    Setting Connection String Parameters in DAO
    Schema.ini File (Text File Driver) [mskb.pkisolutions](https://mskb.pkisolutions.com/kb/210001)

OpenRecordset
機能

DAO.Recordset オブジェクトを取得します。

これにより、レコードごとの個別判定を含む複雑なデータ処理を行うことが可能になります。ただし、可能な操作は前章の許可一覧に準じます。レコードの削除や更新はできません。

現実問題として、テキストファイルをテキストファイルのまま接続して Recordset に使うことは少ないと思われます。信頼性の面からも操作性の面からも、いったんインポートしてローカルテーブルにしてからの方が、はるかに扱いやすいでしょう。

ただ、技術的にはリンクテーブルあるいは前章のような SQL 文をそのままソースにするのも、可能ではあります。

```vb

Dim strSQL As String
Dim rs As DAO.Recordset
strSQL = "SELECT * FROM [Temp#CSV]" _
	& " IN '' 'Text;DATABASE=C:\FolderName;HDR=NO;';"
Set rs = CurrentDb.OpenRecordset(strSQL, dbOpenSnapshot)
```

#### 参考情報

    242478 - PRB: DAO Recordset RecordCount Incorrect When Based on Text File
     https://jeffpar.github.io/kbarchive/kb/242/Q242478/
    187670 - HOWTO: Use RDO and ODBC Text Driver to Open a Delimited Text
      https://jeffpar.github.io/kbarchive/kb/187/Q187670/
    178717 - INF: Excel ODBC Driver and Text ODBC Driver Notes
     https://www.betaarchive.com/wiki/index.php?title=Microsoft_KB_Archive/178717
    191253 - [VB] DAO でユーザー定義カウンターを実装する方法[WebArchive](https://web.archive.org/web/20141128093355/http://support.microsoft.com/kb/191253/ja/)
    249682 - HOWTO: Change the Datatype of a Field using Data Access Objects (DAO) [WebAArchive JP](https://web.archive.org/web/20090428072631/http://support.microsoft.com/kb/249682/ja)
    262537 - HOWTO: Open Delimited Text Files Using the Jet Provider's Text IIsam
    Setting Connection String Parameters in DAO [WebArchive JP](https://web.archive.org/web/20110224164258/http://support.microsoft.com/kb/262537/ja)
#####編注 参考
[How To Query and Update Excel Data Using ADO From ASP](https://support.microsoft.com/ja-jp/help/195951/how-to-query-and-update-excel-data-using-ado-from-asp)
##Open
#機能

ADODB.Recordset オブジェクトを取得します。

これにより、レコードごとの個別判定を含む複雑なデータ処理を行うことが可能になります。ただし、可能な操作は前々章の許可一覧に準じます。レコードの削除や更新はできません。

DAO.Recordset と同様、現実問題として、テキストファイルをテキストファイルのまま接続して Recordset に使うことは少ないと思われます。

```vb
Dim strSQL As String
Dim rst    As New ADODB.Recordset
strSQL = "SELECT * FROM " & _
         "[Text;DATABASE=C:\FolderName;HDR=NO;].[Temp.CSV];"
rst.Open strSQL, CurrentProject.Connection
```

上記は読み取り専用になります。レコードを追加したい場合は、Open メソッドの第 4 引数 LockType（上では省略）に適切なロックタイプを設定する必要が有ります。

ここで、Open メソッドの第 2 引数 ActiveConnection に注目してください。

SQL 文の中にパスを書き込むのは Jet プロバイダ独特の指定方法であるため、ここには Jet プロバイダの Connection を設定する必要が有ります。

MS-Access から使用する場合は、上記のように CurrentProject.Connection を指定すれば通常は問題ありません。

何らかの理由で Jet プロバイダではなく ODBC プロバイダを使用する場合は、SQL 文中にパスを書き込めません。

この場合は、以下のように接続文字列パラメータで親フォルダのパスを指定しておく必要が有ります。

```vb
Dim strSQL As String
Dim strCnn As String
Dim rst    As New ADODB.Recordset
strSQL = "SELECT * FROM Temp#CSV;"
strCnn = "Provider=MSDASQL;" & _
         "Driver={Microsoft Text Driver (*.txt; *.csv)};" & _
         "DefaultDir=C:\FolderName;"
rst.Open strSQL, strCnn
```

SQL 文中のテーブル名は <ファイル名>#<拡張子> の形式で指定します。

パラメータは「DefaultDir」ではなく「DBQ」でも有効です。

また、MSDASQL プロバイダは ADO の既定プロバイダで省略可能なため、接続文字列は下記のように簡略化できます。

```vb
strCnn = "Driver={Microsoft Text Driver (*.txt; *.csv)};" & _
         "DefaultDir=C:\FolderName;"
```

なお前者の Jet プロバイダ版はヘッダーの有無を指定できましたが、後者の ODBC プロバイダ版ではヘッダーの指定方法が確認できませんでした。既定ではヘッダー有りとして取り扱われるようです。
####参考情報

    187670 - HOWTO: Use RDO and ODBC Text Driver to Open a Delimited Text [WebArchive JP](https://web.archive.org/web/20121110011528/http://support.microsoft.com/kb/187670/ja)
    178717 - INF: Excel ODBC Driver and Text ODBC Driver Notes [WebArchive JP](https://web.archive.org/web/20110807113411/http://support.microsoft.com/kb/178717/ja)
    262537 - HOWTO: Open Delimited Text Files Using the Jet Provider's Text IIsam [WebArchipve JP](https://web.archive.org/web/20110224164258/http://support.microsoft.com/kb/262537/ja)

##Open, Line Input #, Print #
##機能

いよいよ VBA の本領発揮です。

Open, Line Input #, Print # はいずれも VBA のステートメントです。

実際には関連ステートメント、関数として、これ以外にも Close、Input #、Input、Input$、Write、Put、Get などがあります。

これらを駆使すれば、テキストファイルに対してできない操作は何もありません。

裏を返せば、MS-Access あるいは DAO、Jet が用意してくれている組み込み機能に頼るわけでは有りませんので、どこまでできるかは腕次第、というシビアな領域でもあります(^ ^;)。

典型的なコード例を以下に提示します。

MS-Access 付属のサンプルデータベース NorthWind.mdb 内の運送会社テーブルをいったん CSV 形式で出力したものが存在するという前提で、それを行単位で読み込むものとします。

```vb
' CSV ファイルを 1 行ずつ読み込んで、イミディエイト
' ウィンドウに書き出します。
Const CSV_NAME As String = "C:\Temp\運送会社.csv"
Dim intFileNum As Integer
Dim strBuff    As String
intFileNum = FreeFile   ' 未使用のファイル番号を取得します。
' シーケンシャル入力モードでファイルを開きます。
Open CSV_NAME For Input Access Read As #intFileNum
' ファイルの終端までループを繰り返します。
Do Until EOF(intFileNum)
    ' 行を変数に読み込みます。
    Line Input #intFileNum, strBuff
  　' イミディエイト ウィンドウに表示します。
    Debug.Print strBuff
Loop
Close #intFileNum　' ファイルを閉じます。
```

↓実行結果

```text
1,"アカネコ","(03) 3955-98xx"
2,"トマト","(03) 3681-31xx"
3,"ペンギン","(03) 3566-99xx"
```

ヘルプの使用例そのまんまですな(^ ^;)。

実行結果で分かる通り、これはファイルをテキストエディタで開いたときに見える行をそのまま読み込んだ形になります。

カンマやテキスト区切り文字のダブルクォーテーションがそのままの形で読み込まれます。これをフィールド単位で区別して読み込みたい場合は、Line Input # ではなく、次の例のように Input # を使います。

```vb
' CSV ファイルをフィールドごとの変数に読み込んで、イミディエイト
' ウィンドウに書き出します。
Const CSV_NAME As String = "C:\Temp\運送会社.csv"
Dim intFileNum As Integer
Dim lngNum     As Long
Dim strName    As String, strPhone As String
intFileNum = FreeFile   ' 未使用のファイル番号を取得します。
' シーケンシャル入力モードでファイルを開きます。
Open CSV_NAME For Input Access Read As #intFileNum
' ファイルの終端までループを繰り返します。
Do Until EOF(intFileNum)
　　' 行を変数に読み込みます。
　　Input #intFileNum, lngNum, strName, strPhone
　　' イミディエイト ウィンドウに表示します。
　　Debug.Print lngNum, strName, strPhone
Loop
Close #intFileNum　' ファイルを閉じます。
```

↓実行結果

```text
 1            アカネコ      (03) 3955-98xx
 2            トマト        (03) 3681-31xx
 3            ペンギン      (03) 3566-99xx
```

CSV のために用意されたようなステートメントですね。

しかし、これだけ見ている分には、わざわざ Open しなくても、DoCmd.TransferText メソッドを使えば済むような気がするかもしれません。

実際その通りで、ほとんどの操作はローカルテーブルに取り込んで必要な処理を施してから出力すれば、済んでしまいます。

しかし、たとえば既存の HTML ファイルの途中のセクションに、データを個別に加工して出力したい場合など、細かいカスタマイズが要求される局面では必須のテクニックになってきます。

シーケンシャル アクセスについて、もう少し見ていきましょう。

ここで取り込み元として想定している CSV は、次のような内容でした。

```text
1,"アカネコ","(03) 3955-98xx"
2,"トマト","(03) 3681-31xx"
3,"ペンギン","(03) 3566-99xx"
```

テキスト区切り記号として、ダブルクォーテーション（"）が使用されています。

仮にテキスト区切り記号が無くても、先ほどの Input # を使用したコードで問題なく処理できます。

```text
1,アカネコ,(03) 3955-98xx
2,トマト,(03) 3681-31xx
3,ペンギン,(03) 3566-99xx
```

Input # ステートメントはダブルクォーテーション（"）を無視するため、有っても無くても結果に差は有りません。

では、データ自体が無い場合はどうでしょう。

```text
1,アカネコ,(03) 3955-98xx
2,トマト,  ' ← 3 列目のデータが無い
3,ペンギン,(03) 3566-99xx
```

これも、問題なく読み込まれます。

欠落データの読み込み用変数が文字列型であれば、空文字列（vbNullString）に、Variant 型であれば Empty 値（vbEmpty）になります。

ではでは、データはむろんのこと、カラム区切り記号のカンマ（,）までが無い場合はどうでしょう。

```text
1,"アカネコ","(03) 3955-98xx"
2,"トマト"  ' ← 3 列目のカラム区切り記号が無い
3,"ペンギン","(03) 3566-99xx"
```

この場合はイミディエイト ウィンドウに下記の出力がされた時点で、実行時エラーが発生します。
```text
1             アカネコ      (03) 3955-98xx
2             トマト        3
```
実行時エラー '62':

ファイルにこれ以上データがありません。

本来 空であるべき 2 行目 3 列目の出力位置に、3 行目 1 列目のデータがずれ込んできています。そのため、以降のデータが順次一つずつずれてしまい、最終データにいたって読み込むべきデータが見つからないというエラーになっています。

Input # ステートメントはデータの区切りをカンマ記号によって認識するため、カンマ記号自体が無いと、データの区切りを正しく認識することができなくなります。

そんな CSV ファイルは使わなければ一番いいのですが、Excel が CSV 形式でファイルを保存する際に最大列数を 16 行単位で認識する仕様になっているため、困ったことに、行の途中から右端のデータがカンマ記号ごと消え失せる CSV ファイルが、実際には世の中に氾濫しています。

このような CSV ファイルは、Input # ステートメントで処理することは出来ません。

一番簡単なのは、DoCmd.TransferText メソッドを使ってリンクなりインポートなりをしてしまう方法でしょう。

DoCmd.TransferText メソッドは、右端のデータがカンマ記号ごと消え失せている CSV ファイルも、正常に処理してくれます。

Open ステートメントにこだわって処理をするのであれば、Input # ステートメントではなく Line Input # ステートメントで行単位に読み込み、Split 関数で分割して処理をする、というロジックにするとよいでしょう。

Open ステートメントの入出力モードには、上記で使用したシーケンシャル アクセス モード以外にも、ランダム アクセス モードやバイナリ アクセス モードがあります。

ランダム アクセス モードは主に固定長データの入出力に使用されます。

実例を挙げてみます。

たとえば、NorthWind.mdb の運送会社テーブルを、次のような設定で固定長のテキストファイルに出力したとしましょう。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126919/16b7c72c-752c-bcb1-2ec1-550c6603b2b0.png)

このファイルを読み込む場合は、ユーザー定義のデータ型を宣言してから、Get ステートメントを使います。

```vb
Private Type 運送会社　　' ユーザー定義型を定義します。
　　運送コード As String * 8
　　運送会社   As String * 20
　　電話番号   As String * 12
　　改行       As String * 2
End Type

Public Sub GetFixedLenText()
　　' 固定長テキストファイルをユーザー定義型変数に読み込んで、
　　' イミディエイト ウィンドウに書き出します。
　　Const TXT_NAME As String = "C:\Temp\運送会社.txt"
　　Dim intFileNum As Integer
　　Dim my運送会社 As 運送会社
    ' 未使用のファイル番号を取得します。
　　intFileNum = FreeFile
　　' シーケンシャル入力モードでファイルを開きます。
　　Open TXT_NAME For Random Access Read As #intFileNum _
        Len = Len(my運送会社)
    ' ファイルの終端までループを繰り返します。
　　Do Until EOF(intFileNum)
　　    ' レコードを読み込みます。
　　　　Get #intFileNum, , my運送会社
　　　　Debug.Print my運送会社.運送コード, _
                   my運送会社.運送会社, _
                   my運送会社.電話番号
　　Loop
　　Close #intFileNum  ' ファイルを閉じます。
End Sub
```

↓実行結果

```text
 1            アカネコ      (03) 3955-98xx
 2            トマト        (03) 3681-31xx
 3            ペンギン      (03) 3566-99xx
```

ここでポイントになるのは、ユーザー定義型に改行コードの領域も含めておくことでしょう。

これが無いと、1 レコード読み込むごとに 2 バイトずつずれていってしまいます。

もしホストから出力したようなファイルで、レコード間に改行コードが含まれない形式であれば、この改行用のメンバは不要です。

また Windows 系 OS では改行コードは 2 バイトですが、Unix や Macintosh では 1 バイトになるので、他の処理系から出力されたテキストを読み込む場合は改行用のメンバのサイズが変わってきます。

ただ、プログラミングというのは何の処理でもそうですが、固定長だからといって必ずしもランダム アクセスで読まないとダメというものではありません。

別に Line Input や Input 関数で行ごと読んでから Mid 関数で切り出しても、DoCmd.TransferText メソッドでいったんテーブルに取り込んでも、同じ結果を手にすることは出来ます。

もうこの辺りは、好みの世界ですね。

ただ、いろいろ知っていると選択肢の幅が広がりますし、いわゆる適材適所というのも、どんな「材」達が有るのか知らないと使い分けようが無いので、知識として入れておくのは決して無駄ではないでしょう。


さて、最後に挙げたバイナリ アクセス モードですが、これはバイナリ ファイルの入出力はもちろん、通常のテキストファイルを高速に読み書きする場合にも使われます。

この辺りは初心者がもっとも混乱しがちなポイントでもあり、また逆に言えばもっとも応用し甲斐のある処理なのですが、いかんせんまともに解説しようとすると紙幅がいくらあっても足りません。

悲しいことに Office VBA のヘルプはこの点に関して充実しているとはとても言えず、詳しくはヘルプを参照のこと、と言って片付けるにしてはあまりにも貧弱な情報しか収録されていません。

私は本家 VB ではなく Office VBA から入った人間なので、はっきり言ってこの点に関してはかなり苦労させられました。

いずれ単独で取り上げる機会を設けるかもしれませんが、ひとまず有益と思われるリンクを一通り紹介しておきます。
参考情報

ファイル入出力に関する Microsoft 社の資料です。
<OL>
<li>msdn online Library - シーケンシャル アクセス
<li>msdn online Library - ランダム アクセス
<li>msdn online Library - バイナリ アクセス
<li>406366 - [XL95] ファイル入出力に関するサンプル（シーケンシャルファイル編)
<li>408058 - [XL97] バイナリ ファイルの入出力を制御する方法
<li>Controlling File Access with Visual Basic for Applications
<li>257794 - HOWTO: Use Binary File Access with Visual Basic
<li>230265 - ImportText.exe Impoting Text into Access with ADO/RDO/DAO/Filesys/Automation
<li>150700 - How to Work with Random Access Files
<li>151262 - Working with Sequential Access Files
<li>151335 - Working with Binary Access Files [WebArchive](https://web.archive.org/web/20140306074322/http://support.microsoft.com/kb/151335)
</OL>
上記の中でもっとも詳細な解説が加えられているのは 6. なのですが、残念ながら邦訳が見当たらないようです（よくある話ですが）。

続いて、Binary モードでテキストファイルを高速に読み書きするサンプルです。これはよく知られたテクニックのようですが、オフィシャルな解説は無いようです。それでも情報が得られる辺り、インターネット時代のありがたさですね。

    テキストファイルをバイナリデータとして読み書きする
    ファイルを高速に読み込む(Internet Archive)
    ファイルの扱い方（配列の応用）
    http://ecell.hoops.livedoor.com/freeml/xl_picwrite.txt 

最後に、Open ステートメントでいかにテキスト出力を自在に操れるかを示す実例として、HTML ファイルへの出力例を挙げます。このトピックは、ファイル I/O のサンプルとして以外の意味でもいろいろ勉強になる、実に強力なトピックです。

AccessデータベースのWebコンテンツ化(下記に発掘)

##FileSystemObject
###FilesystemObjectの機能

FileSystemObject は Access 2000 (VBA) から導入されたオブジェクトで、ファイルの管理を行います。

このFileSystemObject オブジェクトの OpenTextFile メソッドや OpenAsTextStream メソッド、CreateTextFile メソッドを使用すると、テキストファイルに対してシーケンシャルアクセスを行う TextStream オブジェクトを作成することが出来ます。

TextStream オブジェクトは、バイナリアクセスとランダムアクセスをサポートしません。ファイル汎用のアクセス手段ではなく、テキストファイルの扱いに特化したオブジェクトです。

※ ただし MS のアナウンスによると、将来的にはバイナリアクセスをサポートするための拡張が予定されているようです。

この予定は立ち消えになるかもしれません。VBScript は対 JavaScript 戦争において敗退したため、事実上メンテナンスモードに突入しています。そのため VBScript 支援ライブラリの性格が濃かった Scripting Runtime Library をこれ以上充実させる意味が MS 的にはすでに失せています。おそらくアナウンスも特に無いまま放置プレイの公算大ではないでしょうか。VBScript が気に入っている YU-TANG 的には、ある日バイナリアクセスがサポートされて、YU-TANG の読み違いであったことが証明される日が来て欲しいと思うのですが。（YU-TANG@2003/8/15）

元々が Windows Scripting Host 用のオブジェクトだったため、VBA ライブラリの Open ステートメントなどと機能的には重複しています。さすがにそこまで融通は利きませんが、逆にテキストファイルの読み書きで主に使用されるメソッドを統合してあるため、コーディングがかなりすっきりと収まります。

テキストファイルに対する主要な操作のほとんどは、これで賄えるでしょう。

以下は使用例です。

```vb
' テキストファイルを一気に読み込み、イミディエイト
' ウィンドウに表示します。
Dim fs As Object, ts As Object
Set fs = CreateObject("Scripting.FileSystemObject")
Set ts = fs.OpenTextFile("C:\Temp.txt")
' テキストファイルが空でなければ読み込みます。
If ts.AtEndOfStream = False Then Debug.Print ts.ReadAll
ts.Close: Set ts = Nothing
Set fs = Nothing
```

前出の Open ステートメントの使用例と比較すると、そのスマートさが際立ちますね。

Access 97 以前でも、Microsoft Scripting Runtime に参照設定することによって、FileSystemObject オブジェクトを使用することができます。また、Access 2000 以降でも、FileSystemObject オブジェクト用の定数を使いたい場合やアーリーバインディングを行う場合には、やはり Microsoft Scripting Runtime への参照設定が必要になります。
##API
###機能

Win32 API は、本来 Access VBA の範疇ではありません。また私自身、別に API に詳しいわけでもありません。したがって、ここで詳しい説明はしませんし、また出来る知識も持ちません。

では、なぜここであえて取り上げるのかというと、ちょっとショッキングなほど処理速度に差があることが判明したからです。

ここでは、私がそれを知ったサイトを勝手にリンクするに留めます。

そちらをご覧になれば、後は説明不要でしょう。

猫の知ったかぶり > ファイル・フォルダ関連 > ファイルの読み込み方法各種

※ 2005/01/28 時点ではサイトが閉鎖されていましたが、2006/7/7 に七夕プレゼントとして（？）オフライン版が公開されています（Thanks for notification, nil）。
VB5 当時の内容ですが、今なお代えの効かない極上ネタがひしめいていますので、ぜひこの機会にダウンロードを！

関連主要 API について調べる場合は、ReadFile、ReadFileEx、WriteFile、WriteFileEx をキーワードに検索するとよいでしょう。
###参考情報
ReadFile/WriteFile API については、日経ソフトウェアの連載「APIから知るWindowsの仕組み」第 5 回（2002/11 月号）と第 19 回（2004/1 月号）で詳しく解説されています。

ソースコードは下記からダウンロードできます（C ですが ^^;）。

    日経ソフトウェア 2002 年ダウンロード
    日経ソフトウェア 2004 年ダウンロード 

また、以下の VB 用 MSKB も参考になります。

165942 - [VB] WriteFile API を使ってデータをファイルに書き込む方法 
<Details><Summary>Github</Summary>このデッドリンク165952はGitHubにある。 https://jeffpar.github.io/kbarchive/kb/165/Q165942/</Details>

---

---

#Schema.ini File (Text File Driver)
MSDN Home >  MSDN Library >  Data Access >  Microsoft Open Database Connectivity (ODBC) >  ODBC Drivers >  Microsoft ODBC Desktop Database Drivers >  Text File Driver Programming Considerations >  Other Text File Driver Programming Details
http://msdn.microsoft.com/library/en-us/odbc/htm/odbcjetsdk_98.asp
##Schema.ini File (Text File Driver)

When the Text driver is used, the format of the text file is determined by using a schema information file. The schema information file, which is always named Schema.ini and always kept in the same directory as the text data source, provides the IISAM with information about the general format of the file, the column name and data type information, and a number of other data characteristics. A Schema.ini file is always required for accessing fixed-length data; you should use a Schema.ini file when your text table contains DateTime, Currency, or Decimal data or any time you want more control over the handling of the data in the table.

###Note   
The Text ISAM will obtain initial values from the registry, not from Schema.ini. The same default file format applies to all new text data tables. All files created by the CREATE TABLE statement inherit those same default format values, which are set by selecting file format values in the Define Text Format dialog box with <default> chosen in the Tables list. If the values in the registry are different from the values in Schema.ini, the values in the registry will be overwritten by the values from Schema.ini.

##Understanding Schema.ini Files

Schema.ini files provide schema information about the records in a text file. Each Schema.ini entry specifies one of five characteristics of the table:

    The text file name
    The file format
    The field names, widths, and types
    The character set
    Special data type conversions

The following sections discuss these characteristics.
##Specifying the File Name

The first entry in Schema.ini is always the name of the text source file enclosed in square brackets. The following example illustrates the entry for the file Sample.txt:

[Sample.txt]

##Specifying the File Format

The Format option in Schema.ini specifies the format of the text file. The Text IISAM can read the format automatically from most character-delimited files. You can use any single character as a delimiter in the file except the double quotation mark ("). The Format setting in Schema.ini overrides the setting in the Windows Registry on a file-by-file basis. The following table lists the valid values for the Format option.

|Format specifier 	|Table format 	|Schema.ini Format statement|
|:-------------------|:-------------|:----------------------------|
|Tab Delimited|Fields in the file are delimited by tabs.|Format=TabDelimited|
|CSV Delimited| Fields in the file are delimited by commas (comma-separated values).|Format=CSVDelimited|
|Custom Delimited|Fields in the file are delimited by any character you choose to input into the dialog box. All except the double quote (") are allowed, including blank.| <Div>Format=Delimited(custom character) </Div><div>-or- </Div> <Div>With no delimiter specified: </Div> <Div>Format=Delimited( ) </Div>|
|

##Specifying the Fields
You can specify field names in a character-delimited text file in two ways:
Include the field names in the first row of the table and set ColNameHeader to True.
Specify each column by number and designate the column name and data type.

You must specify each column by number and designate the column name, data type, and width for fixed-length files.
###Note   
The ColNameHeader setting in Schema.ini overrides the FirstRowHasNames setting in the Windows Registry on a file-by-file basis.

The data types of the fields can also be determined. Use the MaxScanRows option to indicate how many rows should be scanned when determining the column types. If you set MaxScanRows to 0, the entire file is scanned. The MaxScanRows setting in Schema.ini overrides the setting in the Windows Registry on a file-by-file basis.

The following entry indicates that Microsoft Jet should use the data in the first row of the table to determine field names and should examine the entire file to determine the data types used:

ColNameHeader=True
MaxScanRows=0

The next entry designates fields in a table by using the column number (Coln) option, which is optional for character-delimited files and required for fixed-length files. The example shows the Schema.ini entries for two fields, a 10-character CustomerNumber text field and a 30-character CustomerName text field:

Col1=CustomerNumber Text Width 10
Col2=CustomerName Text Width 30

The syntax of Coln is:

```sql
Coln=ColumnName type [Width #]
```

The following table describes each part of the ***Col****n* entry.

Parameter 	Description
ColumnName 	The text name of the column. If the column name contains embedded spaces, you must enclose it in double quotation marks.
type 	Data types are: 

<編注:以降は現在と同じなので省略>
###Note   
If you omit an entry, the default value in the Windows Control Panel is used.
---
#AccessデータベースのWebコンテンツ化
CGIやISAPIを使わなくてもVisual BasicはWWWで活用できる
MDB形式データベースHTMLコンバータ

 相変わらずのインターネットブームだが、IISやIE 3.0はリリースされたものの、Visual Basic自体の出番はまだない。しかし、インターネットはWWWだけではないという視点で前回はICPを使ったチャットプログラムを紹介した。しかし、何といっても人気のWWWでもVisual Basicを活用したい。そこで、今回はWindows上で使えるデータベースをHTMLにコンバートしてWWWに活用してみよう。

酒井 法雄 <編注:メールアドレスは削除>

###WWWとデータベース

 本誌でもすでに何度も述べているように、インターネットとWWWはイコールではない。しかし、インターネットと言えば何といってもWWWを連想し、Netscape NavigatorとInternet Explorerの新バージョン合戦、新機能合戦、パフォーマンス合戦、相手のバグ指摘合戦、バグフィックス合戦を連想してしまう今日このごろである。そういう舞台でのMicrosoftの主戦力はIEであり、IEに搭載されているActiveXなんとかといういろいろな仕組みである。Visual Basicも、現在のところはVB Scriptという形でその一部がかろうじてあるだけである。
  噂では、時期Visual Basic 5.0ではコンパイラが装備され、ActiveXコントロールやActiveXドキュメントを作成できるらしい。そうなってくれば、Visual Basicも大手を振ってインターネット対応、WWW対応と言えるばかりか、ActiveX構想を一般化するための重要なツールとなるわけだが、現状ではWWWとの距離は遠い。

 ところで、WWWひとつ取ってみても、非常にたくさんのアーキテクチャーが導入され、元々のシンプルなHTTPを侮辱しているのではないかというほど、めちゃくちゃな状態になりつつある。私なども個人的にはJavaもActiveXもいいかげんにしてくれと言いたいクチだ。むしろ、シンプルだったHTTPの機能を由緒正しい形で拡張し、やりたいことをうまく実現するのが本当のプロなのではないかと思ったりもする。 では、由緒正しい形とは何だろうか。私は次の二つだと思っている。

###HTML
 ご存じの通り、Hyper Text Markup Languageの略。ハイパーテキストでのインターネット上の世界規模データベースを実現する基本的なエレメントである。最近では、やたらにピクチャーやShockWaveだのを使ったコンテンツも見かけるが、これは邪道だ。インフラが整備されていない現在のインターネットで、不要に多くのトラフィックを発生させないためにも、このようなものは最低限にすべきである。つまり、シンプルなHTMLを上手く活かすことが大切だ。

### CGI
 WWWのプロトコルHTTPでは、あらかじめ用意されたHTML形式のファイルを、リクエストに合わせて転送するのが基本だ。しかし、実行時に動的にHTML形式データを作成するCGI(Common Gateway Interface)を利用することができる。これは、HTML形式のファイルの代わりに、プログラムを起動して実現する。このプログラムがなんらかの処理をして動的にHTML形式データを作成してクライアントに送りつけるのだ。
 もちろん、Javaをはじめとする新しいテクノロジーを完全に否定することはできないが、そんなに話を複雑化させなくても、スマートに実現できる方法があればそれにこしたことはない。 とすると、必然的にCGIを使おうということになるのだが、インターネットやイントラネットを仕事で使おうという向きに重要なのはデータベースとのリンクである。SQL ServerやOracle ServerのデータをWWWでも活用できれば、これは小手先のActiveXコントロールだのアニメーションばかりに使われているJavaなどより、よっぽど役に立つのは自明だ。
 つまり、従来はクライアント/サーバーシステムなどと言って騒いでやってきたことを、WWWで実現しようというわけだ。MicrosoftのIIS(Internet Information Server)ではODBCデータソースを活用するためのIDC(Internet Database Connection)という仕組みが用意されているし、OracleのWeb ServerでもOracle Serverに特化したインターフェイスが用意されている。

    ・ WWWサーバーの動作
     client		HTTP		Server
     Web Browser  ----------  WWW Server  -------  HTML File
                     
    ・ CGIの動作
     client		HTTP		Server
     Web Browser  ----------  WWW Server  +------  HTML File
                                          +-----  CGI  ----  RDBMS
    ・ IISでのIDCの動作
     client		HTTP		Server
     Web Browser  ----------  WWW Server  +------  HTML File
                                          +-----  CGI
                                          +-----  ISAPI --- IDC ---- ODBC --- RDBMS

しかし、誰もがC/S環境を持っているわけでもないし、自社内に自由にいじれるWWW Serverがあるわけでもないし、WWW ServerがWindows NTみたいなインターネット向きとは言い難いOSを使っているわけではない。このへんは、レベルによってまちまちなのだ。現在のインターネットのような実際には金にならないような、見栄情報発信などにしっかりと金がかけられるのは、それなりの規模の企業か、立場上やらざるをえないか、趣味である(私の会社の場合は、もちろん趣味で専用線を引いている)。
    すると、インターネットで何かをやりたいが、現状ではそこまで金はかけられないという、しっかりとした経営センスがあるような会社では、せいぜいCGIの使えないレンタルサーバーを使うしかない(もっとも、私の会社ではRDBMSとWWWをリンクしたレンタルサーバーも用意している。しかし、DBの設計やツール作成などはどうしても個別になってしまうために、それなりに初期費用がかかるため、なかなか利用者は増えない(T_T))。 しかし、このようなHTMLを置くだけ、またはCGIが使えるといってもRDBMSサーバーが自由に使えないようなレンタルサーバーでは、できることも限られてくる。たとえばWWWでモノを売ろうなどというとき、いちいち用意した商品リストをHTML化したりするのは大変だ。売るモノや在庫状況の変化の仕方にもよるだろうが、HTMLのメンテで毎日の大半が過ぎてしまいかねない(だから、うちのシステムを利用すればいいのにと思うのだが)。 このようなレンタルサーバーでも、たとえばいつも使う自分のマシンのAccessに入っている在庫データをうまく活かすことができるといった、そんなうまい話はないのだろうか。

###データベースの静的HTML化
 幸いにして、このようなうまい話があるのだ。もっとも制限付きだ。まずは、その仕組みを述べよう。 たとえば、AccessやVisual Basicで使えるMDB形式のデータベースは、スタンドアローンやファイル共有によって、ある程度の業務に使うことはできる。あえて言わせてもらえば、Accessでマトモなアプリケーションを作るのは困難だ。Visual Basicで作る方が自由度が高いし、コストも安く早く作ることができる。しかし、それもある程度の規模までの話であり、そこから先を考えるならSQL ServerやOracleといったRDBMS Serverを用意すべきである。まあ、それでもいい。とにかくデータベースはあり、そこにはWWWで公開するとメリットがあるような内容が含まれているとしよう。 話はカンタンで、このデータベースを元にしてWWWで使えるHTML形式のファイルをあらかじめ作っておこうというわけだ。つまり、CGIなどを使うとRDBMSのデータを動的にHTML形式データを作成するのに対して、あらかじめHTMLファイルとして用意する、データベースの静的HTML化である。

\・ HTMLコンバータを使ったときの動作
     
     client		HTTP		Server
    Web Browser -------- WWW Server ------ HTML File
                                        ^
    				      +-- Visual Basic <-+-- MDB
                                                             +-- ODBC - RDBMS


したがって、次のようなことはできない。

###自由な検索
キーワードを指定して検索といった自由度はない。あらかじめ用意してあるファイルの組み合わせで行うしかない。

### WWWからのデータベース更新
 在庫データの変更や、WWWから申し込みがあったデータをデータベースに保存するといったことはできない(電子メールにすることはカンタンだが)。

というわけで、この方法は何でもできる魔法の技ではないが、用途を限って考えれば、それなりに有効な手段となりうる。たとえば、たくさんの商品があったとしても、商品の一覧をカテゴリー別に、あるいはアルファベット順にといったように一覧したファイルを複数用意しておく。そこで商品を指定すると、商品の詳細を記述したファイルに飛ぶことができる。さらに、そこから関連商品情報に飛んだりすることもできるだろう。また場合によっては、この間の階層を何段階かにして絞り込みがしやすいようにしておくこともできるだろう。
　この方法は、元々の自由度は低いものの、設計次第ではかなりたくさんのファイルをうまくリンクして、ユーザーにとっては使いやすいシステムとすることも可能なのだ。

###HTMLコンバータの作成
　ここまで述べてしまえば、あとは何も書くことはない。さあみなさんやってみましょうでおしまいにしたいところだ。なにせ、Visual Basicを使う意味はMDB形式のファイルやODBCデータソースにアクセスできる仕組みがあるというだけの話であり、ぜんぜんWindowsらしいプログラムではないからだ。
 はっきり言って、こんなことはヒントにすぎない。本誌の読者諸氏は、もっと高度な話題を望んでいるに違いない。ここでしょうもないプログラムを作って公開したとあっては、品格を疑われかねないような気がするのだ。しかし、それではページも埋まらないし、せっかくCD-ROMというメディアが付いているのだから、改造する元にしていただければ良いだろうと自分を納得させて、続きを書くことにしよう。 ここでは、Visual Basic 4.0付属のAccess形式のデータベースファイルBIBLIO.MDBを題材としよう。このファイルを選んだ理由はカンタンで、新たにサンプルのデータを作る必要がないという消極的な理由からだったのだ。しかし、実際に設計をはじめ、さらに作り始める、この小さな規模のデータベースからもRDBMS設計について数々の教訓を学ぶことができた。もちろん、少々コードを書き直せば、MDB形式以外のファイル形式やODBCデータソースに対応することもできるから、応用は広いだろう。
 プログラムは、テーブル構造を元にしてデータベースにアクセスし、適切なHTML形式のファイルをシーケンシャルファイルとして吐き出すというシンプルなものになる。しかし、どのようなファイルを出力し、WWWからアクセスできるようにすべきか考えなくてはならない。

###データベースの内容
まずは、各テーブルを見てみよう。このデータベースファイルは16bit用DAO 2.5に準拠しているので、ここではあらかじめ32bit用DAO 3.0にコンバートしてあるが、内容はそのままだ。

```text

        Title
        Title	テキスト型
        Year Published	数値型
        ISBN	テキスト型	Key
        PubID	数値型
        Description	テキスト型
        Notes	テキスト型
        Subject	テキスト型
        Comments	メモ型
        Title Author ISBN
        テキスト型	Key
        Au_ID	数値型	Key
        Authers Year Born
        Au_ID	オートナンバー型	Key
        Author	テキスト型
        Publishers
        PubID	数値型	Key
        Name	テキスト型
        Company Name	テキスト型
        Address	テキスト型
        City	テキスト型
        State	テキスト型
        Zip	テキスト型
        Telephone	テキスト型
        fax	テキスト型
        Comments	メモ型
```
各テーブルは、次のような内容になっている。

|フィールド名|詳細|
|:---:|:----:|
|Titles |本についての基本情報。キーはISBN番号になっている。ここから、筆者や出版社についての情報を別テーブルから引いてくることができる。|
|Title Author|TitlesのISBNに対応する筆者のIDが格納されている。|
|Authors|筆者の情報。内容は名前と生年だけというシンプルなものだ。|
|Title| Autorテーブルを間にはさんで、Titlesから引くことができる。|
|Publishers|出版社についての基本情報。Titleテーブルから引くことができる。また、このテーブルを元にして、出版社ごとの本の一覧や筆者なども拾うことができる。 |

###WWWページ構成の決定
 テーブルを解析して、どのようなアクセスが可能かは分かった。これをデータベースとして利用するアプリケーションを作ろうと思えば、いろいろなことができるだろう。そして、WWWで利用するとしても、ほぼ同様のことができるはずだ。しかし、すでに述べたように、ここではあらかじめこのデータベースの内容を元にしたファイルを作成しておくわけだから、できることは限られてくる。言うなれば、最初から印刷したレポートを次々と表示するようなことしかできないのだ。とはいえ、たくさんの可能性はある。次に、ざっと考えられることを並べてみよう。

```text
            本のタイトル一覧 -> 本の情報
            本の情報 -> 出版社情報
            本の情報 -> 筆者の情報
            出版社一覧 -> 本のタイトル一覧
            出版社一覧 -> 筆者一覧
            筆者の情報 -> 本のタイトル一覧
            筆者一覧 -> 本のタイトル一覧
            筆者一覧 -> 出版社一覧 
```
もちろん、それぞれのデータは単体である必要はなく、ひとつのページに複数テーブルのデータを表示させてもいいし、一覧の順番もいろいろ考えられる。。また、これらの組み合わせ次第では、非常に自由度の高い検索ができるような複雑なシステムにすることもできる。どうせなら全部やってみたいところだが、いくつか作ってしまえばあとは似たようなパターンになってしまう。そこで、ここでは次のような検索ができるようにしよう。

```text

            本のタイトル一覧 -> 本の情報
            本の情報 -> 出版社情報
            本の情報 -> 筆者の情報 
```

 これだけではつまらないので、タイトルの一覧方法に、出版社順、アルファベット順、発売順の三つの検索ができるようにしよう。つまり、トップとなるページは、検索方法を指定するだけのページである。
 複数の検索方法を指定できるようにすると、もちろん事前に作っておかなくてはいけないファイルも増えることになる。ここには200件以上の本のデータが入っているから、ヘタに検索方法を増やすと大変な数のファイルを作らなくてはならないことになる。しかし、実際には検索一覧画面が3種類になるにすぎない。各本の情報については、一意のファイルが用意できるからである。ただ、やりかたによってはそうはならないので注意してほしい。

###HTMLの定数化

 ここまでくれば、いよいよプログラムを作り始めることができる。しかし、最初からデータアクセスのプログラムに入ってはいけない。まずは、HTMLのおおまかな表示方法を考えなくてはならない。というのも、データベースにアクセスさせできれば、あとはファイルに出力するだけだからだ。
 ここではいくつかの表示方法が考えられるが、いずれも表組みとして実現してみよう。そこで、あらかじめ使いそうなHTMLのタグを定数として用意しておくことにする。もちろん、直接記述することもできるのだが、Visual Basicで文字列を出力するときには必ず"(ダブルクオーツ)でくくらなくてはならない。意外にこれがうっとうしいのである。 特に、HTML自体の中に"が使われるときには、Visual Basicの文字列中に"を入れなくてはならないから、いくつ入れたらいいのかだんだん分からなくなってくる。たとえば、

```html
< TD ALIGN = "right" >
```

ならば、Visual Basicのリテラル文字列とするには、次のように記述しなくてはならない。

```vb

 "<TD; ALIGN = ""right"">"
```

というわけで、ここではあらかじめ使いそうなタグを定数として用意した。すべてGlobal定数としてあるが、プログラムでは実際にはPrivateでもかまわない構造になっている。後々拡張していったときのことを考えて、あらかじめGlobalにしてあるだけである。また、このプログラムでは実際には使っていないタグもあるが、あらかじめ用意しておいたというわけだ。実際には、リスト1のように定数を定義した。 HTMLのタグは、開始と終了がセットになっているモノが多い。リスト1の最初の二つで分かるように、"<HTML;>"に対応するものは"</HTML>"である。したがっても、Sで始まる定数はスラッシュが前につく終了のタグという規則にした。

```vb
        Global Const HTML = "<HTML;>"
        Global Const SHTML = "</HTML>"
        Global Const HEAD = "<HEAD;>"
        Global Const SHEAD = "</HEAD>"
        Global Const TITLE = "<TITLE;>"
        Global Const STITLE = "</TITLE>"
        Global Const BODY = "<BODY;>"
        Global Const SBODY = "</BODY>"
        Global Const HR = "<HR;>"
        Global Const BR = "<BR;>"
        Global Const UL = "<UL;>"
        Global Const SUL = "</UL>"
        Global Const TH = "<TH;>"
        Global Const STH = "</TH>"
        Global Const TD = "<TD;>"
        Global Const TDR = "<TD; ALIGN = ""right"">"
        Global Const TDC = "<TD; ALIGN = ""center"">"
        Global Const STD = "</TD>"
        Global Const TR = "<TR;>"
        Global Const CENTER = "<CENTER;>"
        Global Const SCENTER = "</CENTER>"
        Global Const TABLEBORDER = "<TABLE; BORDER>"
        Global Const STABLE = "</TABLE>"
        Global Const PL = "<P; ALIGN =""left"">"
        Global Const PR = "<P; ALIGN =""right"">"
        Global Const P = "<P;>"
        Global Const ADDRESS = "<ADDRESS;>"
        Global Const SADDRESS = "</ADDRESS>"
```

###データアクセスに関連する変数と定数
 次に、データアクセスを効率的にするための変数や定数を用意しておこう。 データベース自体はこのプログラムの主要な部分でアクセスされることになるから、ローカル変数でなく、最初に開いて最後に閉じることにする。したがってWorkspace, Databaseの各オブジェクト変数はPrivateで使えるようにする。

```vb
        Dim ws As Workspace
        Dim db As Database
```

アクセスするファイル名についても考えなくてはならない。各ファイルはプログラムと同じディレクトリにある、また作られることにしよう。これは、AppPathにあらかじめ最後がパスの区切り記号が入るようにして用意しておく。

```vb
       Global AppPath As String
       Global Const DBFILE = "BIBLIO7.MDB"
``` 

出力するファイルは、定数でプレフィックスとサフィックスに分けて用意しておく。また、各本の情報などは頭3文字が同じになるようにして、これも定数として用意しておく。

```vb
        Global Const FILEPUBIDX = "IDXPUB"
        Global Const FILEALPIDX = "IDXALP"
        Global Const FILEYERIDX = "IDXYER"
        Global Const FILEDETAIL = "DET"
        Global Const FILEAUTHOR = "AUT"
        Global Const FILEPUBLISHER = "PUB"
        Global Const EXT = ".HTML"
        Global Const NONE = "-"
```

\ここでは、3つの検索方法を用意するので、それに合わせて一覧のファイルも3つ必要だ。もちろん、ファイルのタイトルなども変更する必要がある。また、このときのクエリーで使われるOrderBy句の内容なども効率よく渡せるようにしたい。そこで、ここではあらかじめ配列としてこれらの変化する内容を格納する変数を用意しておこう。もちろん、それらを区別するための定数も用意したほうがいい。

```vb
        Global MainPageFile As String
        Global TitlePageFiles(0 To 2) As String
        Global TitleIDXNames(0 To 2) As String
        Global TitleOrderBy(0 To 2) As String

        Global Const PUB = 0
        Global Const ALP = 1
        Global Const YER = 2

        Global CR As String * 1
        Global LF As String * 1
        Global CRLF As String * 2
```

###初期化

        まずは、こうして用意しておいたファイル関係の初期化をしておこう。コードを見れば自明だが、CRやLFなどのバイナリ値は定数にできないので、変数の内容として設定する。
        また、App.Pathもルートディレクトリのときとそうでないときに最後に"\"が付くかつかないかが変わるので、あらかじめ同じように最後に"\"が付くようにしておこう。 3つの検索に対応するためのファイル名、タイトル名、OrderBy句の内容も設定しておく。ここで、注意が必要なのは、"Year Published"のようにテーブル名の中にスペースが入るものがある。これはSQL文で正しく解析されないので、"`Year Published`"のようにバッククォートでくくっおく。 ちなみに、こういうテーブル名にするとバグの原因になりがちなので、日本語のテーブル名やフィールド名と同様にあまり使わない方がいいだろう。どうも、Microsoftの提供するサンプルには分かりやすくしたつもりかもしれないが、日本語が使われていて調子が悪い。いちいちかな漢字変換しないとプログラムが組めないのは面倒くさいと思わないのだろうか。

```vb
Sub Initialize()
            CR = Chr$(&HD;)
            LF = Chr$(&HA;)
            CRLF = CR & LF
            If Right(App.Path, 1) = "\" Then
                AppPath = App.Path
            Else
                AppPath = App.Path & "\"
            End If
            MainPageFile = "DBMAIN" & EXT
            TitlePageFiles(PUB) = FILEPUBIDX & "IDX" & EXT
            TitlePageFiles(ALP) = FILEALPIDX & "IDX" & EXT
            TitlePageFiles(YER) = FILEYERIDX & "IDX" & EXT
            TitleIDXNames(PUB) = "出版社順"
            TitleIDXNames(ALP) = "アルファベット順"
            TitleIDXNames(YER) = "発売年順"
            TitleOrderBy(PUB) = "PubID"
            TitleOrderBy(ALP) = "Title"
            TitleOrderBy(YER) = "`Year Published`"
End Sub
```

###メインメニューページとアドレス出力
いよいよ、本格的にプログラムの開始だ。まずは、メニューを作ろう。 これはカンタンである。あらかじめ用意した変数にファイル名は格納されているから、シーケンシャルファイルとしてオープンし、適切なHTMLタグを表す定数と組み合わせて出力すればよい。

        Sub MkMenu()
            Dim FileNum As Integer
            
            FileNum = FreeFile()
            Open AppPath & MainPageFile For Output As #FileNum
            ' Header
            Print #FileNum, HTML
            Print #FileNum, HEAD
            Print #FileNum, TITLE & "Visual Basic 書籍データベース メニュー" & STITLE
            Print #FileNum, SHEAD
            
            ' Body
            Print #FileNum, BODY
            Print #FileNum, CENTER & "<H1;>Visual; Basic 書籍データベース メニュー</H1>" & SCENTER
            Print #FileNum, HR
            Print #FileNum, BR
            Print #FileNum, "<H4;>一覧方法を選択してください。</H4>"
            Print #FileNum, UL
            Print #FileNum, "<LI;><A; HREF=""" & TitlePageFiles(PUB) & """>出版社順</A>"
            Print #FileNum, "<LI;><A; HREF=""" & TitlePageFiles(ALP) & """>アルファベット順</A>"
            Print #FileNum, "<LI;><A; HREF=""" & TitlePageFiles(YER) & """>発売年順</A>"
            Print #FileNum, SUL
            Print #FileNum, HR
            DispAddress FileNum
            Print #FileNum, SBODY
            Print #FileNum, SHTML
            Close #FileNum
        End Sub

        WWWページの最後には著作権やメンテナンスに関わる連絡先ことなどを記述するのが通例である。これはどのページでも共通であるから、ファイル番号を引数として渡せば、このあたりを一通り出力するプロシージャを呼び出せるようにしておこう。


        Sub DispAddress(FileNum As Integer)
            Print #FileNum, BR
            Print #FileNum, CENTER
            Print #FileNum, ADDRESS
            Print #FileNum, "Last Modified " & Format(Now, "Long Date") & BR
            Print #FileNum, "Created by ""MDB to HTML Converter""" & BR
            Print #FileNum, "This Converter was developed by <A; HREF=""http://www.int21.co.jp/"">int21; Corporation</A>" & BR
            Print #FileNum, SADDRESS
            Print #FileNum, SCENTER
End Sub
```

###一覧ページの作成

ページは、あらかじめアクセス順も分かっているから、ふつうにコーディングしていけばよい。 まずは、一覧を作ろう。ここでWorkspaceやDatabaseオブジェクトを設定する。ここからそれぞれのページファイルも作っていくことにする。 Databaseオブジェクトが作成できたら、3つの並べる順序に合わせて3つのファイルを順に作成していく。これらは0から2の配列に必要な情報はあらかじめ格納されてあるから、For~Next文で記述できる。
また、このとき、ファイルはたくさんつくられることをもあるから、処理中の表示をフォームにしておくことにしよう。
 ここでは、出版社順、アルファベット順、出版年順の3つがあるから、出版社と出版年も同時に表形式で表示できるようにしておこう。本当は筆者名も入れておきたいところだが、これにはさらに二つのテーブルにアクセスしなくてはならないので、一覧を作るだけでもかなりの時間がかかってしまう。最近の高性能なマシンを使ってこんなに遅いのはどうかしているのではないかと思ってしまうが、これがGUIとかユーザーフレンドリーという悪魔に魂を売った結果なのである。デバッグもやってられなかったのでコードはコメントアウトしてあるから、筆者も表示したいときには、コメントを外して欲しい。 この処理では、各本の詳細が記述してあるファイル名も同時に決定する必要がある。そうしないとリンクできないからだ。ファイル名は、例のプリフィックスに続き、本のユニークなIDであるISBNを指定してある。これはかなり長いものであるから、Windows 95から採用されている拡張FATあるいはNTFSである必要がある。 こうして決定されたファイル名はHTMLタグのアンカー"<A;>"と</A>の間に入れて、飛ぶことができるようにしておく。
        ちなみに、ISBNには改行コードを含んでやたらに長いデータが入っていた。実際にはデータがおかしいのだと思うが、こういうことにも対処できるように、ファイルのユニーク部は通常の最大桁数の13桁までを使うようにしてある。 データがおかしいのは他にもあって、対応するISBNがTitle Authorテーブルにないものもあった。これはクエリーのエラーになってしまうので、エラートラップを設けて対処してある。 また、例によってスペースが間に入っているようなフィールド名のクエリーには、バッククオートを使っている。

ループは3回繰り返されるわけだが、このうち一回目だけに各詳細情報ファイルを作るプロシージャを呼び出すようにしてある。

```vb
Sub MkTitlePages()
            Dim rsTitles As Recordset
            Dim rsAuthors As Recordset
            Dim rsPublishers As Recordset
            Dim rsTitleAuthor As Recordset
            Dim sufix As String
            Dim FileNum As Integer
            Dim TitleName As String
            Dim i As Integer
            Dim AuID As Integer
            Dim c As Integer
            
            Set ws = DBEngine.Workspaces(0)
            Set db = ws.OpenDatabase(AppPath & DBFILE, False, True)
            
            For i = 0 To 2
                frmMain.lblState.Caption = TitleIDXNames(i) & "を処理中..."
                frmMain.lblState.Refresh
                ' Make Index
                FileNum = FreeFile()
                Open AppPath & TitlePageFiles(i) For Output As #FileNum
                ' Header
                Print #FileNum, HTML
                Print #FileNum, HEAD
                Print #FileNum, TITLE & "タイトル一覧 (" & TitleIDXNames(i) & ")" & STITLE
                Print #FileNum, SHEAD
                
                ' Body
                Print #FileNum, BODY
                Print #FileNum, CENTER & "<H1;>タイトル一覧 (" & TitleIDXNames(i) & ")</H1>" & SCENTER
                
                Set rsTitles = db.OpenRecordset("SELECT * FROM Titles ORDER BY " & TitleOrderBy(i))
                rsTitles.MoveFirst
                Print #FileNum, "<H3;>" & PL & "Visual Basic 書籍データベース" & P & "</H3>" & BR
                
                Print #FileNum, HR
                Print #FileNum, BR
               
                Print #FileNum, "<H4;>タイトルを選択してください。</H4>"
        '        Print #FileNum, UL
                sufix = 0
                
                ' Table Header
                Print #FileNum, TABLEBORDER
                Print #FileNum, TH & "タイトル" & STH
        '        Print #FileNum, TH & "著者" & STH
                Print #FileNum, TH & "出版社" & STH
                Print #FileNum, TH & "発売年" & STH
                Print #FileNum, TR
                Do Until rsTitles.EOF
                    sufix = Left(rsTitles("ISBN"), 13)
                    TitleName = rsTitles("Title")
                    Print #FileNum, TH & "<A; HREF=""" & FILEDETAIL & CStr(sufix) & EXT & """>" & TitleName & "</A>" & STH
                    ' Author
        '            Set rsTitleAuthor = db.OpenRecordset("SELECT Au_ID FROM `Title Author` WHERE ISBN = '" & rsTitles("ISBN") & "'")
        '            On Error Resume Next
        '            rsTitleAuthor.MoveFirst
        '            If Err Then
        '                Print #FileNum, TD & "no data" & STD
        '            Else
        '                AuID = rsTitleAuthor("Au_ID")
        '                Set rsAuthors = db.OpenRecordset("SELECT Author FROM Authors WHERE Au_ID = " & rsTitleAuthor("Au_ID"))
        '                rsAuthors.MoveFirst
        '                If Err Then
        '                    Print #FileNum, TD & "no data" & STD
        '                Else
        '                    Print #FileNum, TD & rsAuthors("Author") & STD
        '                    rsAuthors.Close
        '                End If
        '                rsTitleAuthor.Close
        '            End If
        '            On Error GoTo 0
                    ' Publishers
                    Set rsPublishers = db.OpenRecordset("SELECT Name FROM Publishers WHERE PubID = " & rsTitles("PubID"))
                    On Error Resume Next
                    rsPublishers.MoveFirst
                    If Err Then
                        Print #FileNum, TD & "no data" & STD
                    Else
                        Print #FileNum, TD & rsPublishers("Name") & STD
                    End If
                    On Error GoTo 0
                    rsPublishers.Close
                    ' Year
                    If rsTitles("Year Published") > 1900 Then
                        Print #FileNum, TD & rsTitles("Year Published") & STD
                    Else
                        Print #FileNum, TD & "no data" & STD
                    End If
                    Print #FileNum, TR
                    If i = 0 Then
                        c = c + 1
                        frmMain.lblState.Caption = "詳細ファイルを処理中..." & CStr(c) & "件"
                        frmMain.lblState.Refresh
                        MkTitleDetail rsTitles, FILEDETAIL & CStr(sufix) & EXT
                    End If
                    rsTitles.MoveNext
                Loop
                Print #FileNum, STABLE
                rsTitles.Close
        '        Print #FileNum, SUL
                Print #FileNum, HR
                Print #FileNum, "<A; HREF=""" & MainPageFile & """>書籍データベース メニュー</A>" & BR
                Print #FileNum, BR
                DispAddress FileNum
                Print #FileNum, SBODY
                Print #FileNum, SHTML
                Close #FileNum
            Next i
            MkMenu
            MkAuthors
            MkPublishers
            db.Close
            ws.Close
        End Sub
```

###詳細データファイルの作成
 次に、本の詳細データを作ることにしよう。ここでは、元となるタイトルのRecordsetオブジェクトと、詳細データのファイル名を引数として渡してある。ここから、さらにクエリーをして詳細データを表形式でHTML化する。 また、ここには筆者と出版社の名前も表示し、アンカーとしてそれぞれのマスターの内容を表示できるようにしておく。ここで、それぞれのファイル名を決定しなくてはならないのだが、例によってプリフィックスに加えてユニークにするためのAu_IDとPubIDを使ったものにする。

```vb
Sub MkTitleDetail(rsTitles As Recordset, FileName As String)
Dim rsAuthors As Recordset
            Dim rsPublishers As Recordset
            Dim rsTitleAuthor As Recordset
            Dim FileNum As Integer
            Dim AuID As Integer
            
            FileNum = FreeFile()
            Open AppPath & FileName For Output As #FileNum
            
            ' Header
            Print #FileNum, HTML
            Print #FileNum, HEAD
            Print #FileNum, TITLE & rsTitles("Title") & STITLE
            Print #FileNum, SHEAD
            
            ' Body
            Print #FileNum, BODY
            Print #FileNum, CENTER & "<H1;>" & rsTitles("Title") & "</H1>" & SCENTER
            
            Print #FileNum, HR
            Print #FileNum, BR
            
            Print #FileNum, TABLEBORDER
            Print #FileNum, TH & "項目" & STH
            Print #FileNum, TH & "内容" & STH
            Print #FileNum, TR
            
            ' Author
            Print #FileNum, TH & "著者" & STH
            Set rsTitleAuthor = db.OpenRecordset("SELECT Au_ID FROM `Title Author` WHERE ISBN = '" & rsTitles("ISBN") & "'")
            On Error Resume Next
            rsTitleAuthor.MoveFirst
            If Err Then
                Print #FileNum, TD & "no data" & STD
            Else
                AuID = rsTitleAuthor("Au_ID")
                Set rsAuthors = db.OpenRecordset("SELECT Author FROM Authors WHERE Au_ID = " & rsTitleAuthor("Au_ID"))
                rsAuthors.MoveFirst
                If Err Then
                    Print #FileNum, TD & "no data" & STD
                Else
                    Print #FileNum, TD & "<A; HREF=""" & FILEAUTHOR & AuID & EXT & """>" & rsAuthors("Author") & "</A>" & STD
                    rsAuthors.Close
                End If
                rsTitleAuthor.Close
            End If
            On Error GoTo 0
            Print #FileNum, TR
            
            ' Publishers
            Print #FileNum, TH & "出版社" & STH
            Set rsPublishers = db.OpenRecordset("SELECT Name FROM Publishers WHERE PubID = " & rsTitles("PubID"))
            On Error Resume Next
            rsPublishers.MoveFirst
            If Err Then
                Print #FileNum, TD & "no data" & STD
            Else
                Print #FileNum, TD & "<A; HREF=""" & FILEPUBLISHER & rsTitles("PubID") & EXT & """>" & rsPublishers("Name") & "</A>" & STD
            End If
            On Error GoTo 0
            rsPublishers.Close
            Print #FileNum, TR
           
            Print #FileNum, TH & "発売年" & STH
            Print #FileNum, TD & rsTitles("Year Published") & STD
            Print #FileNum, TR
            
            Print #FileNum, TH & "ISBN" & STH
            Print #FileNum, TD & rsTitles("ISBN") & STD
            Print #FileNum, TR
            
            Print #FileNum, TH & "説明" & STH
            Print #FileNum, TD & rsTitles("Description") & STD
            Print #FileNum, TR
            
            Print #FileNum, TH & "注記" & STH
            Print #FileNum, TD & rsTitles("Notes") & STD
            Print #FileNum, TR
            
            Print #FileNum, TH & "キーワード" & STH
            Print #FileNum, TD & rsTitles("Subject") & STD
            Print #FileNum, TR
            
            Print #FileNum, TH & "コメント" & STH
            Print #FileNum, TD & rsTitles("Comments") & STD
            Print #FileNum, TR
            
            Print #FileNum, STABLE
            
            Print #FileNum, BR
            Print #FileNum, HR
            Print #FileNum, BR
            Print #FileNum, "<A; HREF=""" & MainPageFile & """>書籍データベース メニュー</A>" & BR
        '    Print #FileNum, "<A; HREF=""" & KanriPageFile & """>戻る</A>" & BR
            DispAddress FileNum
            Print #FileNum, SBODY
            Print #FileNum, SHTML
            Close #FileNum
End Sub
```

###筆者ファイルと出版社ファイルの作成
 筆者ファイルと出版社ファイルについては、タイトル一覧から引っ張る必要はない。そこで、タイトル一覧を作成するループから抜けてから、これらのプロシージャを呼び出すことになる。 ここでは、各テーブルのレコードを最初から最後までアクセスして、例の法則で決まったファイル名としてHTMLファイルを作成すればよい。実はデータアクセスの方法としては、ここが一番カンタンである。

```vb
Sub MkAuthors()
Dim rsAuthors As Recordset
Dim FileNum As Integer
Dim FileName As String
Dim c As Integer

Set rsAuthors = db.OpenRecordset("Authors")
rsAuthors.MoveFirst
Do Until rsAuthors.EOF
c = c + 1
frmMain.lblState.Caption = "著者ファイルを処理中..." & CStr(c) & "件..." & rsAuthors("Author")
frmMain.lblState.Refresh
FileName = FILEAUTHOR & rsAuthors("Au_ID") & EXT
FileNum = FreeFile()
Open AppPath & FileName For Output As #FileNum
' Header
Print #FileNum, HTML
Print #FileNum, HEAD
Print #FileNum, TITLE & rsAuthors("Author") & STITLE
Print #FileNum, SHEAD

' Body
Print #FileNum, BODY
Print #FileNum, CENTER & "<H1;>" & rsAuthors("Author") & "</H1>" & SCENTER

Print #FileNum, HR
Print #FileNum, BR

Print #FileNum, TABLEBORDER
Print #FileNum, TH & "項目" & STH
Print #FileNum, TH & "内容" & STH
Print #FileNum, TR

Print #FileNum, TH & "名前" & STH
Print #FileNum, TD & rsAuthors("Author") & STD
Print #FileNum, TR

Print #FileNum, TH & "生年" & STH
Print #FileNum, TD & rsAuthors("Year Born") & STD
Print #FileNum, TR

Print #FileNum, STABLE

Print #FileNum, BR
Print #FileNum, HR
Print #FileNum, BR
Print #FileNum, "<A; HREF=""" & MainPageFile & """>書籍データベース メニュー</A>" & BR
'    Print #FileNum, "<A; HREF=""" & KanriPageFile & """>戻る</A>" & BR
DispAddress FileNum
Print #FileNum, SBODY
Print #FileNum, SHTML
Close #FileNum
rsAuthors.MoveNext
Loop
rsAuthors.Close
End Sub


Sub MkPublishers()
Dim rsPublishers As Recordset
Dim FileNum As Integer
Dim FileName As String
Dim c As Integer

Set rsPublishers = db.OpenRecordset("Publishers")
rsPublishers.MoveFirst
Do Until rsPublishers.EOF
c = c + 1
frmMain.lblState.Caption = "出版社ファイルを処理中..." & CStr(c) & "件..." & rsPublishers("Name")
frmMain.lblState.Refresh
FileName = FILEPUBLISHER & rsPublishers("PubID") & EXT
FileNum = FreeFile()
Open AppPath & FileName For Output As #FileNum

' Header
Print #FileNum, HTML
Print #FileNum, HEAD
Print #FileNum, TITLE & rsPublishers("Name") & STITLE
Print #FileNum, SHEAD

' Body
Print #FileNum, BODY
Print #FileNum, CENTER & "<H1;>" & rsPublishers("Company Name") & "</H1>" & SCENTER
                
                Print #FileNum, HR
                Print #FileNum, BR
                
                Print #FileNum, TABLEBORDER
                Print #FileNum, TH & "項目" & STH
                Print #FileNum, TH & "内容" & STH
                Print #FileNum, TR
                
                Print #FileNum, TH & "略称" & STH
                Print #FileNum, TD & rsPublishers("Company Name") & STD
                Print #FileNum, TR
                
                Print #FileNum, TH & "正式名" & STH
                Print #FileNum, TD & rsPublishers("Company Name") & STD
                Print #FileNum, TR
                
                Print #FileNum, TH & "町名" & STH
                Print #FileNum, TD & rsPublishers("Address") & STD
                Print #FileNum, TR
                Print #FileNum, TH & "市/区" & STH
                Print #FileNum, TD & rsPublishers("City") & STD
                Print #FileNum, TR
                Print #FileNum, TH & "州/県" & STH
                Print #FileNum, TD & rsPublishers("State") & STD
                Print #FileNum, TR
                Print #FileNum, TH & "〒" & STH
                Print #FileNum, TD & rsPublishers("Zip") & STD
                Print #FileNum, TR
                Print #FileNum, TH & "電話" & STH
                Print #FileNum, TD & rsPublishers("Telephone") & STD
                Print #FileNum, TR
                Print #FileNum, TH & "FAX" & STH
                Print #FileNum, TD & rsPublishers("Fax") & STD
                Print #FileNum, TR
                Print #FileNum, TH & "コメント" & STH
                Print #FileNum, TD & rsPublishers("Comments") & STD
                Print #FileNum, TR
                
                Print #FileNum, STABLE
                
                Print #FileNum, BR
                Print #FileNum, HR
                Print #FileNum, BR
                Print #FileNum, "<A; HREF=""" & MainPageFile & """>書籍データベース メニュー</A>" & BR
            '    Print #FileNum, "<A; HREF=""" & KanriPageFile & """>戻る</A>" & BR
                DispAddress FileNum
                Print #FileNum, SBODY
                Print #FileNum, SHTML
                Close #FileNum
                rsPublishers.MoveNext
            Loop
            rsPublishers.Close
        End Sub
```

###フォームの作成
 さて、こうして作成されたルーチンを、フォームから呼び出すようにすれば、プログラムは完成である。ここには、作成するためのボタン、処理状況を表示するためのラベル、終了ボタンなどを配置する。また、適当なメニューもつけてみた。 フォームの画面およびコードを次に示す。

```vb
        Private Sub cmdExec_Click()
            Me.Enabled = False
            Screen.MousePointer = vbHourglass
            MkTitlePages
            MkMenu
            Screen.MousePointer = vbArrow
            Me.Enabled = True
            lblState.Caption = "ファイルが作成されました。"
            Beep
        End Sub

        Private Sub cmdExit_Click()
            End
        End Sub

        Private Sub Form_Load()
            Caption = App.ProductName
            Initialize
        End Sub

        Private Sub mnuAbount_Click()
            MsgBox App.ProductName & " Version " & CStr(App.Major) & "." & CStr(App.Minor) & "." & CStr(App.Revision) & CR & _
                "Copyright (c) 1996 " & App.CompanyName & ", " & App.LegalCopyright & CR & _
                "All Rights Reserverd.", , _
                App.ProductName & " について"
        End Sub

        Private Sub mnuCreate_Click()
            cmdExec.Value = True
        End Sub

        Private Sub mnuDesc_Click()
            MsgBox "VB 4.0付属のBIBLIO.MDBの内容をHTML化します。", _
                    vbInformation, _
                    Caption & " の使い方"
        End Sub

        Private Sub mnuExit_Click()
            cmdExit.Value = True
        End Sub
```

###HTMLコンバータの実用性
こうしてできたHTMLファイルは、Webブラウザでローカルなファイルとしてもアクセスすることができる。そういう意味では、何もWWWサーバーが必須なわけでもないから、共有ディレクトリに配置するといった限られた使い方もできるかもしれない。 実際のメニュー、一覧、詳細、筆者、出版社の画面を示す。
このように、HTMLファイルを作成すること自体は、インターネットプログラミングなどと呼べるようなものではないのだが、インターネットやイントラネットに活用できるデータが比較的手軽に作成できるという点で興味深い。
実際、このやり方だと自由度は落ちるものの、あらかじめファイルを作っておくわけだから、IISがいくらDLLを使って超高速にアクセスできると言ったところで、こっちの方が速いに決まっている。そういう意味では、大変に優れた方式と言えなくもない。 CGIなどに比較してもプログラミングは容易であるし、WWWサーバーを使わなくてもデバッグができる点も優れている。ましてや、インプロセスで高速ながらIDCやDLLを独自に作らなくてはならないIISなどでは、ヘタなプログラムを作るとサーバーごと落ちてしまう。

また、最近ではVisual Basicで作ったプログラムを呼び出すことができるWWWサーバーもあるのだが、これはオーバーヘッドが大きいし、Visual Basic自体の動作速度にも難がある。この方式ならば、作成には多少は時間がかかるものの、実際に呼び出されるときには単なるファイルなのだからずっとずっと高速なのだ。 こう考えてくると、用途によってはこのような方法もかなり優れているのではないかという気はしてくる。
 ただし、これだけの規模のものでも500ものファイルが作成されてしまった。ちょっとプログラムを作るとHTMLファイルが500も作れるというのはなんだか愉快ではあるのだが、ファイルのトータルサイズは1Mバイトにもなってしまう。しかも、これはテキストだけのデータである。実際には商品のイメージデータなどを入れたいこともあるだろうから、かなりのデータがWWWサーバーのディレクトリに必要になるだろう。 いずれにしても、規模や用途に合わせてHTMLを事前に用意する方法は、かなりの効果があると言えるだろう。
###インターネットの可能性

　今回のテーマはインターネットではなく、データベース活用のひとつの方法としてインターネットがあるということなのだが、やはりインターネットというテーマになってしまったような気がする。
　すでに前回に述べたように、インターネットはWWWだけではないということで、カンタンなチャットプログラムを作った。また、DDJにはネットワーク対戦の五目並べも発表した。この他にも、最近だとCoolTalkやNetMeetingといったインターネットやイントラネットでのチャット、ボイスチャット、ホワイトボード、アプリケーションの共有などができるものも出てきている。実際にNetMeegingなどをしてみると、どうもテレクラみたいなノリになっているところが気にかかるが...。 インターネットでのチャットといえば、IRCが以前からあるが、私はここのところIRCをよく利用している。もっぱら会社など専用線がある人が利用していることもあって、ひとつのチャネルに30人以上人がいるはずなのに、たまにちょっと誰かがなにか書いたと思ったら、あと数時間は誰も何も書かないなんてこともあって、だいぶ他のチャットとはノリが違う。

　また、インターネットを利用した対戦ゲームもいくつか出ており、先日も4人で対戦できる麻雀を試してみた。IRCでチャットしながら麻雀を楽しんだものの、実は私はあまり麻雀が得意ではなく、負けまくってしまった。しかし、東京、名古屋、大阪といった離れた人間がインターネットを使っていろいろなコミュニケーションができたというのは、難とも感動であった。
　インターネットはアイデア次第でまだまだいろいろな可能性がある。そのためのツールとしてVisual Basicにも多くの可能性があるだろう。 

---

#テキストファイルをバイナリデータとして読み書きする　　　（１２０）
http://www.bcap.co.jp/hanafusa/VBHLP/Binary.htm

```vb
Option Explicit

Private Sub Command1_Click()
'テキストファイルをバイナリデータとして読込表示する（その１）
　　Dim bytArray()　As Byte
　　Dim intFileNo　 As Integer
　　Dim lngFileLenB As Long
　　lngFileLenB = FileLen("sample1.txt")
　　ReDim bytArray(lngFileLenB - 1)
　　'使用可能なファイル番号を取得する
　　intFileNo = FreeFile
　　'ファイルをバイナリモードで開く
　　Open "sample1.txt" For Binary As #intFileNo
　　'バイナリデータとして読込んで
　　Get #intFileNo, , bytArray
　　Close #intFileNo
　　'Unicodeに変換して表示
　　Text1.Text = StrConv(bytArray, vbUnicode)
End Sub

Private Sub Command2_Click()
'テキストファイルをバイナリデータとして読込表示する（その２）
　　Dim intFileNo As Integer
　　'使用可能なファイル番号を取得する
　　intFileNo = FreeFile
　　'ファイルをシーケンシャル入力モードで開く
　　Open "sample1.txt" For Input As #intFileNo
　　'バイナリデータとして読込んでUnicodeに変換して表示
　　Text1.Text = StrConv(InputB$(LOF(intFileNo), intFileNo), vbUnicode)　
　　Close #intFileNo
End Sub


Private Sub Command3_Click()
'テキストファイルをバイナリモードで書込み
　　Dim intFileNo As Integer
　　intFileNo = FreeFile
　　'ファイルをバイナリモードで開く
　　Open "sample1.txt" For Binary Access Write As #intFileNo
　　　　'テキストファイルを丸ごと書込み
　　　　Put #intFileNo, , Text1.Text
　　Close #intFileNo
End Sub
```

テキストボックスの内容を丸ごと保存・丸ごと読み込みする。ではシーケンシャルモードで丸ごと読み書きしていましたが、この方法では　””　で区切られてしまいますので、文中には　””　を使用出来ませんでしたが、バイナリモード読み書きする事によって表示された内容そのままを保存・読込でき、尚高速に読み書きができます。
02/03/21

##複数行テキストボックスの指定行の文字列を取得する。　　　（０７７）

```vb
'フォームの General Declarations セクションに記入
Option Explicit　　　　 'SampleNo=077　WindowsXP VB6.0(SP5) 2002.05.16
'指定のウインドウにメッセージを送る（P750)
Private Declare Function SendMessage Lib "user32" _
　　　　Alias "SendMessageA" (ByVal hwnd As Long, _
　　　　ByVal wMsg As Long, ByVal wParam As Long, _
　　　　lParam As Any) As Long
'複数行テキストボックスにおいてテキストの行数を取得する（P799)
Private Const EM_GETLINECOUNT = &HBA
'　　〃　　　　指定行の先頭の文字インデックスを取得する（P803)
Private Const EM_LINEINDEX = &HBB
'指定の行を取得する
Private Const EM_GETLINE = &HC4
'　　〃　　　　指定の文字インデックスを含む行インデックスを取得（P802)
Private Const EM_LINEFROMCHAR = &HC9
'総行数を格納する変数
Dim lngMaxRow As Long
'指定行を格納する変数
Dim lngAppointRow As Long


'指定行の文字列を取得するプロシージャ
Private Sub AppointRowStr()
　　Dim strBuffer As String * 200　 '取得する文字列の文字数を指定
　　Dim lngLen As Long
　　'バッファをクリア
　　strBuffer = ""
　　'指定行の文字列・バイト数（ANSI）を取得
　　lngLen = SendMessage(Text1.hwnd, EM_GETLINE, lngAppointRow, ByVal strBuffer)
　　'ANSI の分後ろに空白がはいるので空白を削除してラベルに表示
　　Label1.Caption = RTrim$(Left$(strBuffer, lngLen))
End Sub


'複数行表示するテキストボックス
Private Sub Text1_Change()
　　'テキストボックスの内容が変更されたら総行数を再取得
　　lngMaxRow = SendMessage(Text1.hwnd, EM_GETLINECOUNT, 0&, 0&)
End Sub


Private Sub Text1_Click()
'現在行を取得してその内容を表示
　　lngAppointRow = SendMessage(Text1.hwnd, EM_LINEFROMCHAR, -1&, 0&)
　　If lngAppointRow >= 0 And lngAppointRow <= lngMaxRow Then
　　　　'指定行の文字列取得へ
　　　　Call AppointRowStr
　　End If
End Sub


Private Sub Text1_KeyUp(KeyCode As Integer, Shift As Integer)
'現在行を取得してその行の内容を表示
　　lngAppointRow = SendMessage(Text1.hwnd, EM_LINEFROMCHAR, -1&, 0&)
　　If lngAppointRow >= 0 And lngAppointRow <= lngMaxRow Then
　　　　'指定行の文字列取得へ
　　　　Call AppointRowStr
　　End If
End Sub


'指定行を入力するテキストボックス
Private Sub Text2_GotFocus()
　　Label2.Caption = "指定行を１～" & lngMaxRow & "の範囲で指定して下さい"
End Sub


'指定行を入力するテキストボックス
Private Sub Text2_KeyPress(KeyAscii As Integer)
　　On Error GoTo ErrorHandler
　　If KeyAscii = vbKeyReturn Then
　　　　KeyAscii = 0
　　　　'指定行から１行分引く
　　　　lngAppointRow = CLng(Text2.Text) - 1
　　　　If lngAppointRow >= 0 And lngAppointRow <= lngMaxRow Then
　　　　　　'指定行の文字列取得へ
　　　　　　Call AppointRowStr
　　　　Else
　　　　　　Beep
　　　　　　Text2.Text = ""
　　　　End If
　　End If
　Exit Sub
ErrorHandler:
　　　Beep
　　　Text2.Text = ""
End Sub



'ついでにファイルの入出力関係を　　　（０７７）

'丸ごと保存のコマンドボタンに記入
Private Sub Command1_Click()
　　Dim Ret As Long
　　Ret = InStr(Text1.Text, Chr$(34))
　　If Ret = 0 Then
　　　　Ret = InStr(Text1.Text, Chr$(-32408))
　　End If
　　If Ret Then
　　　　Text1.SelStart = Ret - 1
　　　　Text1.SelLength = 1
　　　　Text1.SetFocus
　　End If
　　If Ret > 0 Then
　　　　Dim strMsg As String
　　　　strMsg = "文中に " & Chr$(34) & " が含まれています 保存しますか"
　　　　Ret = 0
　　　　Ret = MsgBox(strMsg, vbYesNo)
　　　　If Ret = vbNo Then
　　　　　　Exit Sub
　　　　End If
　　End If

　　'テキストボックスの内容を丸ごと保存
　　'　”区切りで保存されるので文書中には　”は使用不可。
　　Dim lngFileNo As Long
　　lngFileNo = FreeFile
　　Open "sample1.txt" For Output As #lngFileNo
　　Write #lngFileNo, Text1.Text
　　Close #lngFileNo
End Sub


'行ごと読み込みのコマンドボタンに記入
Private Sub Command2_Click()
　　'Line Input で行ごとの読み込み
　　Dim Mystring As String
　　Dim lngFileNo As Long
　　lngFileNo = FreeFile
　　Text1.Text = ""
　　DoEvents
　　Open "sample1.txt" For Input As #lngFileNo
　　Do Until EOF(lngFileNo)
　　　　Line Input #lngFileNo, Mystring
　　　　Text1.Text = Text1.Text & Mystring & vbCrLf
　　Loop
　　Close #lngFileNo
　　Text2.SetFocus
End Sub


'丸ごと読み込みのコマンドボタンに記入
Private Sub Command3_Click()
　　'ファイル丸ごと読み込み（高速）
　　Dim Mystring As String
　　Text1.Text = ""
　　DoEvents
　　Dim lngFileNo As Long
　　lngFileNo = FreeFile
　　Open "sample1.txt" For Input As #lngFileNo
　　Input #lngFileNo, Mystring
　　Text1.Text = Mystring
　　Close #lngFileNo
　　Text2.SetFocus
End Sub
```

テキストボックスの総行数・現在行・現在桁の取得　も参考にして下さい。
複数行テキストボックスの総行数・指定行の文字列等が取得でき、テキストファイルを扱う場合便利かと思います。行ごとの文字列が簡単に扱えるので読み込み時変数等に保管する必要もなく、ファイルの保存もテキストファイルを丸ごと保存でき処理が簡単にすみます。
読み込みも丸ごと読み込みが出来３２KB位のファイルで０．２秒位で読み込み表示ができます。
行ごとに読み込みますと １４秒位かかります。但し、丸ごと読み込むため　”　は文書中には使えません。（ ” 区切りで保存のため）

尚、最初にテキストファイルを読み込むには行ごと読み込みを使って下さい。
文書中に ” があれば表示したテキストボックス中で削除し、丸ごと保存で保存して下さい。
丸ごと保存で保存したファイルだけが、丸ごと読み込みで読み込めます。
テキストファイルは適当な Readme.txt 等をコピーして使って下さい。

#リッチ及びテキストボックス内の総行数・現在行・現在桁の取得   （０７６）
http://www.bcap.co.jp/hanafusa/VBHLP/TextEdit.htm
##テキストボックスの場合

```vb
Option Explicit　　 'SampleNo=076　WindowsXP VB6.0(SP5) 2002.05.16
'指定のウインドウにメッセージを送る（P750)
Private Declare Function SendMessage Lib "user32" _
　　　　Alias "SendMessageA" (ByVal hwnd As Long, _
　　　　ByVal wMsg As Long, ByVal wParam As Long, _
　　　　lParam As Any) As Long
'複数行テキストボックスにおいてテキストの行数を取得する（P799)
Private Const EM_GETLINECOUNT = &HBA
'　　〃　　　　指定行の先頭の文字インデックスを取得する（P803)
Private Const EM_LINEINDEX = &HBB
'　　〃　　　　指定の文字インデックスを含む行インデックスを取得（P802)
Private Const EM_LINEFROMCHAR = &HC9
'選択されたテキストの最初と最後の文字インデックスを取得する（P801)
Private Const EM_GETSEL = &HB0


Private Sub Text1_KeyUp(KeyCode As Integer, Shift As Integer)
'マウスカーソル位置の文字数（行の先頭から桁数）を取得
　　Dim MaxRow As Long
　　Dim PosRow As Long
　　Dim PosCol As Long
　　Dim Count　As Long
　　'総行数を取得する
　　MaxRow = SendMessage(Text1.hwnd, EM_GETLINECOUNT, 0&, 0&)
　　'現在行（マウスカーソル位置の）を取得する
　　PosRow = SendMessage(Text1.hwnd, EM_LINEFROMCHAR, -1&, 0&) + 1
　　'現在行の先頭位置までのバイト数（vbFromUnicode）を取得
　　PosCol = SendMessage(Text1.hwnd, EM_LINEINDEX, -1, 0)
　　'カーソル位置から行の先頭までの文字数を求める
　　For Count = 0 To Text1.SelStart
　　'先頭位置と同じバイト数になるまで文字数をカウント
　　　　If PosCol = LenB(StrConv(Left$(Text1.Text, _
　　　　　　　　(Text1.SelStart - Count)), vbFromUnicode)) Then
　　　　　　PosCol = Count + 1　'＋１は Word と同じカウントに合わせる
　　　　　　Exit For
　　　　End If
　　Next Count
　　Label1.Caption = "総行数 : " & MaxRow & "　 現在行 : " & _
　　　　　　PosRow & "　 現在桁 : " & PosCol
End Sub
```

リッチテキスト用と同様にプロシージャ化して Text1_KeyUp  イベントや RichTextBox1_Click
イベントから呼び出した方が便利かも知れません。その辺は環境に合わせてご使用下さい。

##リッチテキストボックス用
注意　VB5.0 用のOCX(V.5.01.4319等)では正しい桁数を返しません。
VB5.0(SP3)　　Microsoft Rich Textbox Control 6.0(SP3) で動作確認
こちらはプロシージャ化しました

```vb
Option Explicit　　 'SampleNo=076　WindowsXP VB6.0(SP5) 2002.05.16
'指定のウインドウにメッセージを送る（P750)
Private Declare Function SendMessage Lib "user32" _
　　　　Alias "SendMessageA" (ByVal hwnd As Long, _
　　　　ByVal wMsg As Long, ByVal wParam As Long, _
　　　　lParam As Any) As Long
'複数行テキストボックスにおいてテキストの行数を取得する（P799)
Private Const EM_GETLINECOUNT = &HBA
'　　〃　　　　指定行の先頭の文字インデックスを取得する（P803)
Private Const EM_LINEINDEX = &HBB
'　　〃　　　　指定の文字インデックスを含む行インデックスを取得（P802)
Private Const EM_LINEFROMCHAR = &HC9
'選択されたテキストの最初と最後の文字インデックスを取得する（P801)
Private Const EM_GETSEL = &HB0

Private Sub sCursorRow()
'マウスカーソル位置の文字数（行の先頭から桁数）を取得
　　Dim MaxRow As Long
　　Dim PosRow As Long
　　Dim PosCol As Long
　　Dim Count　As Long
　　Dim CurPos As Long
　　Dim strLin As String

　　'総行数を取得する
　　MaxRow = SendMessage(RichTextBox1.hwnd, EM_GETLINECOUNT, 0&, 0&)
　　'現在行（マウスカーソル位置の）を取得する
　　PosRow = SendMessage(RichTextBox1.hwnd, EM_LINEFROMCHAR, -1&, 0&) + 1
　　'現在行の先頭位置までのバイト数（vbFromUnicode）を取得
　　CurPos = SendMessage(RichTextBox1.hwnd, EM_LINEINDEX, -1&, 0&)
　　'現在位置までのバイト数を取得
　　PosCol = SendMessage(RichTextBox1.hwnd, EM_GETSEL, 0&, 0&)
　　'行の先頭からのバイト数を求める
　　PosCol = PosCol \ 65536 - CurPos
　　'先頭からの文字列を求める
　　strLin = Left$(RichTextBox1.Text, RichTextBox1.SelStart)

　　'IME入力時はこの処理ができない
　　'バイト数でカウントするならこの処理及びキー制限はいらない
　　For Count = 0 To Len(strLin)
　　'先頭位置と同じバイト数になるまで文字数をカウント
　　　　If CurPos = LenB(StrConv(Left$(strLin, _
　　　　　　　　Len(strLin) - Count), vbFromUnicode)) Then
　　　　　　PosCol = Count + 1
　　　　　　Exit For
　　　　End If
　　Next Count
　　Label2.Caption = "総行数 : " & MaxRow & "　 現在行 : " & _
　　　　　　PosRow & "　 現在桁 : " & PosCol
End Sub


Private Sub RichTextBox1_Click()
　　sCursorRow　'マウスをクリックした場合も桁位置が判るように
End Sub


Private Sub RichTextBox1_KeyUp(KeyCode As Integer, Shift As Integer)
　　'押されたキーを制限（漢字入力時等でうまく処理できない）
　　If KeyCode = vbKeyReturn Or (KeyCode >= 36 And KeyCode <= 40) Then
　　　　sCursorRow
　　End If
End Sub
```

リッチテキストボックス用もテキストボックス用と同様の処理で行けると思ったのですが、IME
が入力出来なくなったり、文字位置がうまく取得出来なかったりで、結構悩みました。
最終的にカーソル移動キーとEnter　キーとマウスクリックした場合に取得するように変更しました。バイト単位での位置取得には、問題は無いようです。ご使用時には、その辺十分注意して下さい。（特にOCXのバージョンの違いには）
上記サンプルはＶＢ専用掲示板の＃３６６に（たくボンさん）が投稿されたものです。
又　No.1208 正確な行と列の確保：解決　　投稿者：A0k1x さん　でも投稿されておられますが、こちらは現在桁は取得できません（現在行の総桁数）

たくボンさんのサンプルでは半角文字と全角文字が混在すると桁位置がずれるのでRichTextBox　用と一部現在桁取得のところは私が変更しました。過去のログも合わせて参照して下さい。
当初のサンプルは事前の改行位置から現在の位置を割り出していましたが、改行でなくワードラップで折り返している場合、位置が違った値になるため、今回は行の先頭位置のバイト数を調べ
それと同じバイト数になる文字数を求めました。

---

#ファイルを高速に読み込む
http://ww2.tiki.ne.jp/~kotb/vb/tips/readfast.html
##編注 Line Inputだがバイナリ形式で読み込む方法
###特徴は高速で読み込まれること

##Code

```vb
Option Explicit

Private Sub cmdReadFast_Click()
　　'素早く読み込む

　　Dim strBuf As String
　　
　　'ファイル名はお好きに変えて下さい
　　Point?参照
　　Open "Test.txt" For Binary As #1
　　　　strBuf = Space$(LOF(1))
　　　　Get #1, , strBuf
　　Close #1
　　
　　txtText = strBuf
End Sub

Private Sub cmdReadNormal_Click()
　　'ふつうに読み込む

　　Dim strBuf As String
　　Dim strTemp As String
　　
　　Open "Test.txt" For Input As #1
　　　　Do Until EOF(1)
　　　　　　Line Input #1, strTemp
　　　　　　strBuf = strBuf & strTemp & vbCrLf
　　　　Loop
　　Close #1

　　txtText = Left$(strBuf, Len(strBuf) - 2)
　　
End Sub

Private Sub cmdClear_Click()
　　'テキストボックスの内容を消去する

　　txtText = ""
　　
End Sub
```

##Point
###Openステートメント

Openステートメントで、For Inputではなく、For Binaryと指定してバイナリモードでファイルを開きます。 その後、ファイルの内容を格納する変数に、ファイルのサイズ分の空きを作ってやります。 こうすると、一瞬でファイルが読み込まれます（サイズが大きいほどLine Input#...との差は大きくなります）。 

<Details><Summary>Access 2010 で廃止、変更される機能</Summary>
<div>

この記事の一覧から Access 2007 で利用できる機能は、Microsoft Access 2010 で変更されている機能です。 次の表では、いくつかの変更が、Access データベースにどのように影響がについて説明し、選択肢その他のオプションが使用可能な機能の変更に関する理由も提供します。

|廃止または変更される機能|変更内容|代替案|
|:---|:---|:---|
|<div>フィールドの作業ウィンドウの機能は、データ型のギャラリーで置き換えられますを追加します|リボンから、追加するフィールドのオプションを使用して、データシート ビューではなく、さまざまな定義済みのデータ型を選択し、保存して再利用する独自に、データ型のギャラリーを使用できるようになります。データ型のギャラリーは、データ型と機能の向上に共有できます。 追加のフィールドの作業ウィンドウのオプションは、キャプチャする複数のフィールド テンプレートを使用するデータ型テンプレートを 1 つのフィールドのみを許可します。 フィールド リストに加えられた変更は無視され、フィールドの追加] 作業ウィンドウに、マクロの呼び出しが失敗します。</div>||
|オート フォーマットグループは、オプションの [テーマ] グループで置き換えられます。|オート フォーマット] グループでは、フォーム レイアウト ビューとレポートのレイアウト ビューのリボンで使用されなくなったです。 オート フォーマットは、テーマに置き換えられます。</div><div>テーマを使用するフォームのオプションまたはレポートのカスタマイズ可能な拡張される良い書式しダウンロードまたは Office Online またはメールで他のユーザーと共有することができます。 サーバーに公開することもできます。 オート フォーマットをへのアクセスに使用できます。テーマは、その他の Office アプリケーションで使用できます。</div>|ユーザー設定のリボンには、オート フォーマット] コマンドを追加できます。|
|予定表コントロール (mscal.ocx) はサポートされていません|予定表コントロールを含むフォームを開く場合は、コントロールは、Access 2010 では表示されないことを示すエラー メッセージが表示されます、Access データベースには、コントロールに見つからないまたは壊れた参照が含まれています。<p>この問題を修正するのには、アプリケーションから予定表コントロールを削除します。</p>|予定表コントロールを追加するのに日付の選択機能を使用することができます。|
|データ アクセス ページ (Dap) は表示されません。|設計し、データ アクセス ページを実行することはできません。 あっても、Dap ナビゲーション ウィンドウで表示しようとすると、それを開くときに、Access では、データ アクセス ページの操作はサポートされていないことを示すエラーが表示されます。データベースに格納する Dap は引き続きし、に使用する前のバージョンの Access を使用することができます。 ホストの Web 上でアクセスする、SharePoint を使用することもできます。||
|エクスポート、インポート、および Lotus 1-2-3 ファイルからのデータへのリンクは表示されません。|Lotus リストに Access 2010 で表示されますが、それらを操作することはできません。
表示を「インストール可能な ISAM 見つかりませんでした」エラー メッセージ。|エクスポート、インポート、または Lotus 1-2-3 ファイルからのデータをリンクするには、以前のバージョンの Access を使用します|

</div></Details>
<Details><Summary>Access でファイルのインポートまたはエクスポートを実行すると、読み取り専用のエラーが発生する場合がある</Summary>
<div>
https://support.microsoft.com/ja-jp/help/436329
###現象
Microsoft Access 2010、Microsoft Access 2000 または Microsoft Access 97 でファイルのインポート、またはファイルのエクスポートを実行した際に、以下のエラー メッセージが表示され、ファイルのインポート、またはエクスポートに失敗する場合があります。

データベースまたはオブジェクトは読み取り専用であるため、更新できません。

このエラーは、Access に Jet データベースエンジンのセキュリティ上の問題に対応したサービス パックが適用された環境で、拡張子が無い、または、以下の拡張子以外のファイルをインポート、またはエクスポートしようとした際に発生します。
<OL>
<li>txt
<li>csv
<li>tab
<li>asc
<li>htm
<li>html
</OL>

###原因
Jet データベースエンジンは Text IISAM を使用することでテキスト ファイルを編集することが可能なため、システム テキストファイルの誤った編集によるセキュリティ上のリスクが発生する可能性がありました。この問題に対応するため、Microsoft ACE データベース エンジン、Microsoft Jet データベース エンジン 4.0 および Microsoft Jet データベースエンジン 3.5 では、Service Pack 3.0 以降、レジストリに指定された拡張子以外のファイルをインポート、またはエクスポートすることができなくなったためにこの現象が発生します。
###回避策
拡張子無しのファイル、または、以下の拡張子以外のファイルを処理するには、レジストリキーの値に、処理したい拡張子を追加してください。
<OL>
<li>txt
<li>csv
<li>tab
<li>asc
<li>htm
<li>html
</OL>
重要 : このセクション、方法、またはタスクには、レジストリの編集方法が記載されています。レジストリを誤って変更すると、深刻な問題が発生することがあります。レジストリを編集する際には十分に注意してください。万一に備えて、編集の前にレジストリをバックアップしておくと、問題が発生した場合にレジストリを復元することができます。バックアップおよび復元方法の詳細を参照するには、以下のサポート技術情報をクリックしてください。
322756 Windows でレジストリをバックアップおよび復元する方法

    起動しているすべての Office プログラムを終了します。
    [スタート] ボタン[スタート] ボタン をクリックし、[プログラムとファイルの検索] ボックスに regedit と入力します。
    検索されたプログラム一覧の [regedit.exe] をクリックします。
    "次のプログラムにこのコンピューターへの変更を許可しますか？" とメッセージが表示されたら、[はい] をクリックします。
    [レジストリ エディター] が起動したら、お使いの Access のバージョンの次のキーを探して移動します。

    Access 2010 (Microsoft ACE データベース エンジン) の場合 :
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\14.0\Access Connectivity Engine\Engines\Text\DisabledExtensions
    Access 2000 (Jet データベース エンジン 4.0) の場合 :
    HKEY_LOCAL_MACHINE\Software\Microsoft\Jet\4.0\Engines\Text\DisabledExtensions
    Access 97 (Jet データベース エンジン 3.5) の場合 :
    HKEY_LOCAL_MACHINE\Software\Microsoft\Jet\3.5\Engines\Text\DisabledExtensions
    [DisabledExtensions] レジストリ キーを右クリックし [修正] をクリックして、[値のデータ] に処理したいファイルの拡張子を追加します。追加が完了したら、[OK] をクリックします。
    [レジストリ エディター] を終了します。


状況
この動作は仕様です。

詳細
Jet データベース エンジンおよび Text IISAM のファイルが以下のバージョン以降の環境では、このセキュリティ問題に対する修正が加えられています。

Access 2000/Jet データベース エンジン 4.0


   ファイル名      バージョン        サイズ
   -----------------------------------------
   msjet40.dll     4.00.2927.4    1,495,312
   mstext40.dll    4.00.2927.6      241,936
 

Access 97/Jet データベース エンジン 3.5


   ファイル名      バージョン        サイズ
   -----------------------------------------
   msjet35.dll     3.51.3203.0    1,050,384
   mstext35.dll    3.51.3203.0      166,672
 

関連情報
Jet データベース エンジン 3.5 のセキュリティ上の問題については、次のサポート技術情報を参照してください。
239105 Jet Text IISAM を使用してシステムファイルが編集される

Jet データベース エンジン 3.5 の最新版のアップデートについては、次のサポート技術情報を参照してください。
172733 アップデート バージョンの Microsoft Jet 3.5


Jet データベース エンジン 4.0 のセキュリティ上の問題については、次のサポート技術情報を参照してください。
414330 Microsoft Jetデータベース エンジンにおけるセキュリティの弱点


Jet データベース エンジン 4.0 の最新版のアップデートについては、次のサポート技術情報を参照してください。
282010 ダウンロードセンターで入手可能な Microsoft Jet 4.0 のアップデート バージョン



最終更新日: 2016/10/04 
</div></Details>
<Details><Summary>Text ISAM Files</Summary>
<div>
https://docs.microsoft.com/ja-jp/previous-versions/windows/desktop/odbc/dn170576(v=vs.85)
ODBC Desktop Database Drivers 4.0 includes two 32-bit ISAM files for text files: Mstext40.dll and Odtext32.dll.
</div></Details>