Vz resources : MACRO FAQ
2007-10-13T11:59:30+09:00

VZ Macro FAQ - ファイル

現ファイル名をバッファに取得するには

変数nmが指すファイル名はカレントディレクトリからの相対パス名ということになっていますので,ドライブレターまで含むフルパス名を取得しようとする場合は少々不安定かもしれません。

一番簡単な方法は,コマンド#82(ファイル名の変更)を使うことです。#82を実行するとリネーム用ヒストリーバッファにファイル名が複写されたうえで,ファイル名変更窓が現われます。これをそのまま利用できます。

1 ^\ "" ?.
        #82 #[          ;ファイル名変更窓を出してすぐにキャンセル
        &m("%s",pr)     ;pr(リネーム用バッファ)に格納されている

少し工夫するだけで,削除バッファにも取得できます。

1 ^\ "" ?.
        #82 #H #[       ;削除してすぐにキャンセル

なお,コマンド#82で複写されるファイル名がどこにあるかというと,テキストワークにあります。このテキストワークとは,オプションTC(オープン可能なテキスト数)で指定した数が確保される228バイトの構造体です。ここには編集テキストに関する多くの情報が含まれていて,ドライブレターを含むフルパスのファイル名が格納される引出も64バイトの大きさで確保されています。逆に言えば,64バイトの大きさを超えるパス名を保存することはできません。直後にあるタイトルサーチ文字列と互いに確執し壊し合います。(笑)

このフルパス名はwa+146から始まり,終端にヌルを持つ文字列です。したがって,バッファを用意しておきさえすれば,単純に文字列関数で複写することができます。

1 ^\ ""
        ?.
        &Sc(ps,wa+146)
        &m(ps)

現ファイル名をカーソル位置に出力するには

前項で紹介したwa+146を使って出力することができます。

1 ^\ ""
        &?("%s",wa+146)

wa+146は,入力窓でもコマンドラインでも参照することができます。したがって,マクロ冒頭のお呪い「?.」は必要ありません。もちろん記述しておけば,編集テキストのカーソル位置にしか出力しなくなります。

現テキストのファイル名の一部分を出力するには

フルパスのファイル名を出力するのが至って簡単であることは前項で述べましたが,ファイル名とか拡張子を取り除いたファイル名部分だけというのは,それなりの工夫が必要になってきます。

手順としては,次のようになります。

ファイル名を解析するには,特殊な文字列関数&Sy()を使用します。&Sy()の引数としてパス名の先頭ポインタを与えると,その文字列をパス名と見なして解析し,解析結果を返します。またファイル名や拡張子の開始位置(メモリ上の番地)をレジスタ変数に格納します。

返り値rの値は複数のビットフラグの和になっています。次の表はビットごとに定義された意味で,フラグが立っているときに真であるということになります。要するに,それらしき文字列があるかどうかということですね。

&Sy()関数の解析結果rとレジスタ変数に格納されたポインタ
rの値ビットフラグが立っているときの意味
+1ワイルドーカード文字
+2拡張子
+4ファイル名
+8ディレクトリ名
+16ドライブ名
+32絶対パス
レジスタ変数
変数変数の指すアドレス
bxファイル名の開始位置
cx拡張子の開始位置
siパス名の末尾

次のマクロを実行してみてください。

1 ^\ ""
        &Sy(wa+146)
        &m("%d",r)

拡張子のあるファイルでは「62」が,拡張子のないファイルでは「60」が返ってくると思います。62=2+4+8+16+32で,ワイルドカード文字以外はすべての条件に合致しています。したがって「62」が返ってくれば,ドライブ名を含むフルパスで拡張子のあるファイルということです。「60」が返ってくれば,フルパスで拡張子のないファイルです。

さて,解析結果とともにファイル名と拡張子のそれぞれの開始位置がレジスタ変数に格納されてきていますので,これも表示させてみましょう。

1 ^\ ""
        &Sc(pa,wa+146)          ;別のバッファに複写する
        &Sy(pa)                 ;複写先で解析する
        cx.0=0,                 ;複写先でヌルを埋め込む
        &m("ファイル名:[%s] / 拡張子:[.%s]",bx,cx+1)

ここで一番重要なことは,ヌルを埋め込むような場合は,別に用意したバッファに複写してから行わなければならないという点です。テキストワークを直接書き換えるようなことは注意して行うべきで,特にファイル名を勝手に変更してはいけません。

ここではファイル名と拡張子を別個に表示しようとしているため,ヌルの埋め込みを行って文字列を中途でちょん切っています。拡張子の開始位置であるピリオドをヌルで上書きしているので,表示するときはピリオドを補完したうえで表示開始位置を1バイトずらしています。ヌルを埋めこんでおきながら位置ずらしを怠ると,開始位置にいきなりヌルが出現するので何も表示できません。

裏ファイル名をバッファに取得するには

現ファイルのテキストワークが変数waの指している構造体であることは前の項に述べました。現ファイルは別の言い方をすると表ファイルになります。これに対して,現ファイルに切り替える前に編集・参照していたファイルを裏ファイルと言います。編集画面が分割されているときに反対画面に表示されている,あるいは#Y(編集画面の分割=#64)コマンドを実行したときに反対画面に表示されるテキストです。これらの表・裏のテキストは複数のファイルをオープンしている状態で特別な扱いとなります。

裏ファイルのテキストワークは変数wbの指している構造体です。「構造」体と言うくらいですから,その構造は裏も表も,またその他のテキストワークもまったく同じです。したがって裏テキストのパス名はwb+146から始まる64バイトの領域にあります。

裏テキストのファイル名をバッファに取得する手順は,表ファイルの場合とまったく同じです。

1 ^\ ""
        &Sc(pa,wb+146)
        &m("%s",pa)

裏ファイル名をカーソル位置に出力するには

前項において,裏テキストのファイル名をバッファに取得しました。この文字列をカーソル位置に出力するには,わざわざバッファに複写する必要はありません。

1 ^\ ""
        &?("%s",wb+146)

なお,パス名を編集して出力する場合は,テキストワークを直接いじらずに,用意したバッファに複写したものをいじってください。

ファイルの属するディレクトリ名を取得するには

例えば,現テキストのあるディレクトリの別のファイルをオープンしたいときはどうしてますか?

VZPATHに設定していない深いディレクトリなんて,なかなか移るのが面倒ですよね。手動でやるなら,まず[ESC]Oでファイル名入力窓を開き,^QNで現テキストのファイル名を出力して,ファイル名部分を\[BS]で削除して入力し,目的のディレクトリでファイラーを起動する,…って感じですよね。

同じことをマクロでやればいいわけです。

1 ^\ ""
        ?.
        (wa==wo)?.              ;コンソールは不可(一応チェックする)
        #O #P                   ;ファイル名入力窓に現ファイル名を出力
        (-1){                   ;無限ループに入る
                #s              ;末尾から1文字戻る
                (cd!='\')?#g    ;デリミタでなければ削除
                ?{}             ;デリミタならループを抜ける
        }
        #m                      ;指定ディレクトリでファイラーを起動

この例は1文字毎に移動・削除していますが,一度カーソルを逆に進めるという動作が許せるのであれば,次のようにも書けます。結果は同じで好みの問題です。ひとつだけ利点があるとすれば,後で#u一発で現ファイル名が出力できるところですが,特別嬉しいというほどのこともありません。速度はちょっとだけ速いかも。

1 ^\ ""
        ?.
        (wn)??.                 ;コンソールは不可(一応チェックする)
        #O #P                   ;ファイル名入力窓に現ファイル名を出力
        (-1){                   ;無限ループに入る
                #s              ;末尾から1文字戻る
                (cd=='\')?{}    ;デリミタならループを抜ける
        }
        #d #l                   ;1文字進めてカーソル以降を削除
        #m                      ;指定ディレクトリでファイラーを起動

このようにして起動したファイラーをキャンセルして編集画面に戻ると,pf(ファイル名ヒストリーバッファ)の最新文字列として格納されています。

以上はエディタらしい機能を使って結果を得ていますが,メモリ上での操作も可能です。先の項にあった,wa+146を複写・加工する方法です。加工した後にpfに格納してファイラーを起動することができます。

1 ^\ "" ?.
        &Sc(pa,wa+146)          ;フルパスを複写する
        &Sy(pa) bx.0-,          ;ファイル名の開始位置でちょん切る
        &Sh(pf,pa)              ;pfに履歴複写する
        #O #e #m                ;最後の履歴として呼び出す

何となくスマートな気がしてきますね。&Sy()解析によるbx格納アドレスの使い方の典型例と言えるでしょう。

長いファイル名を取得するには

Windows 95系のMS-DOSプロンプトでは高橋版を,Windows NT系のコマンドプロンプトでは金野氏修正高橋版を利用することにより,ファイラーで長いファイル名を扱うことができるようになりました(オプションlf有効)。このおかげで,ファイラーにおける新規ファイル作成コマンド#Nやリネームコマンド#Rの入力窓では,長いファイル名を利用することができます。

しかし,テキスト構造体の中のフルパスは短いファイル名となっていますので,拡張ファンクションコールを使用して長いファイル名を取得する必要があります。

1 ^\ ""
        ?.
        si=wa+146,      ;解析するパス(ショート)
        &> di=r,        ;ロング変換後書込み先:261バイト必要
        cx=2, ax=$7160, &i($21)
        (ax==$7100)?{ &m("ファンクションがサポートされていません.") . }
        (r)?di=si,      ;長いファイル名の取得に失敗
        &m("%s",di)
0:      (3+$)."$[261]"

確実に作業用新規ファイルを開くには

通常はランダムなファイル名を付けてオープンしてやれば,大抵は新規ファイルとしてオープンできるでしょう。例えば,日付と時刻を組み合わせたり,加えて乱数をかませたりというファイル名です。

次のマクロでは,カーソル位置に「08063942.416」といった8+3文字列を出力します。ファイル名入力窓で実行してやれば,新規ファイル名とすることができるでしょう。

1 ^\ ""
        ax=$2A00, &i($21) &?("%02d%02d",dx>>8,dx&$ff)   ;月日
        ax=$2C00, &i($21) &?("%02d%02d",cx&$ff,dx>>8)   ;分秒
        &i($21) rn=cx+dx,       ;時刻でランダムシード
        &r &?(".%03d",r%1000)   ;.000〜.999の拡張子

しかし,このファイル名が本当に新規ファイルとなる保証はあるようでないのです。1年後の同じ月日の同じ時刻,同じ乱数が出力される可能性はかなり低いでしょう。しかし,2年後は? 3年後は? 絶対に同じファイル名が作られないと誰が断言できるでしょう。

作業ファイルとして使うファイル名は,絶対にセーブできないファイル名にすることによって,いかなる場合も新規ファイルとしてオープンすることができます。そのセーブできないファイル名が「nul」です。ただし,「nul」というファイル名で既に編集中のファイルがあった場合は,そのファイルがアクティブになり,新規ファイルとしてオープンすることはできません。それを解決するのが#R(リードオンリーオープン)です。このコマンドは参照用にオープンするときのコマンドで,同じファイルをいくつでもオープンすることができます。これらの効用により,作業ファイルを常にからっぽの状態でオープンできるのです。

1 ^\ ""
        ?.
        #R "nul" #m     ;nifty:FGALPK/MES/15/#3628,榊原知氏による
        (s)?'y'         ;(s==4)の省略形:NT系OSでは新規ファイル確認がある

日付・時刻ファイル名に関する蛇足

なお,単純な数字列では年月日時分秒を8文字で表現することはできませんが,次のような書式であれば8文字で表現できます。ただし,元々このフォーマットはファイルのDTA領域に格納されたタイムスタンプを取得して生成するものなので,秒は偶数だけです。このため秒の単位は1桁になります。

8桁日付・時刻の書式
年2桁00〜99 100での剰余でゼロ詰めです。1980〜2079年まで対応。
月1桁1〜9,A〜C16進数形式です。
日1桁1〜9,A〜V16進数形式ですが,Vまで拡張します。
時1桁0〜9,A〜N16進数形式ですが,Nまで拡張します。
分2桁00〜59 原形のままですが,ゼロ詰めです。
秒1桁0〜9,A〜T16進数形式ですが,Tまで拡張します。

実例を示すと,1994年03月15日00時13分08秒は「943F0134」,2001年12月13日08時04分50秒は「01CD804P」という形式です。2秒以内に連続実行されないのであれば,新規ファイルとしてオープンできるでしょう。しかし作業用新規ファイルはセーブする必要がないわけですから,この方法はあくまでもメモやログなどをとる時のものとしたほうがよいでしょう。

強制的に編集を打ち切って保存することなく閉じるには

編集状態のファイルを閉じようとすると,「○○を保存しますか? (Y/N)」という確認があります。これはもちろんセーブされていないファイルを保存しないまま閉じてしまう危険を抑止する機能で,エディタだけでなくすべてのアプリケーションにおける一般的な動作です。

しかし急いで閉じたいという場合に,逆に鬱陶しいのもまた確かで,そんな我が儘をマクロで実現することができます。

ファイルを閉じようとするときに,保存するかどうかを確認してくるのは,編集中のファイルに修正フラグが立っているからです。修正フラグというのは,編集テキスト変数mrのことで,以下の値をとります。

テキスト修正フラグの値と意味
mrテキストの状態
0未修正
1修正あり
128ビューモード
254リードオンリー(修正あり)
255リードオンリー(修正なし)

テキスト修正フラグmrの値が1の修正ありなら,ファイルを閉じようとすると保存の確認がありますので,強制的に1以外の値にすれば問い合わせが発生しません。ただ,ラインバッファが書き戻されて値が変わってしまっては意味がないので,まず#]でラインバッファを書き戻してからmr=0などとしなければなりません。この一連の動作を一発で解決するのがm.gann師の発見による次のコードです。

1 ^\ ""
        ?. mr3, #C

mrは2〜127の値を与えられると,修正ありのリードオンリーとして解釈され,修正フラグが立っていても問い合わせなしにクローズすることがEmmett博士によって報告されています(nifty:FGALDC/MES/15/#5869)。

さて,すべてのファイルの編集をキャンセルしてVZそのものを終了させたい場合には,特にコマンド#[(キャンセル:VZの終了)を使用します。この場合,編集のキャンセルについては問い合わせがありますので,自動応答すればよいということになります。

1 ^\ ""
        #[ (s)?'y'

すべての編集ファイルを閉じてもVZを終了させないようにするには

前項において,全キャンセル終了マクロに #[ (s)?'y' を書きましたが,環境によっては #[ だけでいきなり終了する場合があります。これはオプションsq(Close,Quitで即座に終了)が有効になっている状態です。sqが有効になっていると,最後のファイルを閉じたときにVZも終了しますので,前項のようなマクロは不要ということでもあります。

製品版の初期設定ではsqが無効になっています。意図的に変更した場合でも,このようなオプションは普段は決して意識することがないため忘れてしまいがちです。すべての編集ファイルを閉じたら,ファイル名入力窓やファイラーが表示されている状態にしたいという場合は,sqを無効(sq-)にしてください。

VZを終了させないためにこっそりとコンソールを開いておくには

m.gann師によって開発された技の紹介です。(nifty:FGALPK/MES/13/#506,1992/12/27)

まず,次のマクロをVZ.DEFに記述しておきます。

69 "" ?. ss-, #E        ;マクロ番号は重複していないこと

そして,次の内容をもつバッチファイルで起動します。

VZ -ss -r69 console     ; -r69 で69番マクロを自動実行する

常駐と組み合わせると,以下のようになります。この手法を開発したのはEmmett博士です。(nifty:FGALDC/MES/15/#3881,1997/01/08)

VZ -Z
VZ -ss -r69 console

直前に閉じたファイルを高速に再度開くには

ファイル名ヒストリーバッファには,オープンしたファイル名の履歴が残っていますので,閉じたファイルのファイル名も履歴をたどって再度オープンすることが可能です。しかし,直前に閉じたファイルであれば履歴を利用するよりもすばやくオープンすることができます。

先の項での説明に出てきたwawbなどテキストワークを指す変数と同じ種類の変数にwf(未使用のテキストワークのリンクポインタの先頭)というものがあります。この変数は,本来は「Free windows record pointer」ということで単に未使用のテキストワークを指すものなのですが,この変数が指すテキストワークには直前にクローズしたテキストのワークが残っていることが多いという特性があります。あくまでも絶対ということはなく保証の限りではないのですが,閉じたばかりであればほぼ確実に再度オープンすることができるようです。

1 ^\ ""
        ?. #O &?("%s",wf+146) #m

またこれを利用して,編集のやり直し(ディスク上のファイルの再ロード)を行うこともできますね。(#Pの方がコードは短いですが(^^;

1 ^\ ""
        ?. mr3,
        #L &?("%s",wf+146) #m

同じファイルを二重に開くには

コマンド#R(リードオンリーオープン)は,すでに同名のファイルがオープンされていても別のファイルとしてオープンすることができます。ディスク上に同名パスがあればディスクから読み込みますので,保存前の現ファイルとディスク上のファイルを比較することで,編集箇所をテキストの比較コマンド(#69)を使って確認することなどができます。また,同じテキストの離れた場所を参照しながら書くということもできるようになっています。

1 ^\ ""
        ?.
        (wf)??{ &m(6). };編集可能なテキスト数の空きを確認 (tc-wc)??.
        #R #P #m        ;

なお,製品版サンプルVZ.DEFでも「テキストの二重化」ということで,初期導入マクロに含まれています。これを拡張するマクロでは,編集テキスト変数の値を両者で合わせたり,編集位置を再現したりという工夫が行われています。さらに,金近真一氏のdupe.defや見米快之氏のshadow.defのように,テキストを完全に二重化して互いに編集を反映させるような高度なマクロも開発されました。

2バイトにタイムスタンプをパックするには

ファイルの読込みや書込みを行うとき,FCB(File Control Block)とDTA(Disk Transfer Address)という領域を使用します。この領域に日付と時刻がそれぞれ2バイトのバイナリデータで格納されています。ファンクションコールを使用してファイルの日付・時刻を取得する場合,この2バイトずつのバイナリデータを分解・展開して値を取り出すわけですが,その逆ももちろん可能です。

次のマクロは村上和生氏の制作です。(nifty:FGALDC/MES/15/#4158,1997/01/16)

=== T_pack === 2バイト変数にタイムスタンプをパック
*m
80^\""?.
      ax$2a00,&i($21) d=(cx-1980)<<9+dx>>3&$1e0+dx&$ff,       ;DTA form.
      ax$2c00,&i($21) c=cx<<3&$f800+cx<<5&$7e0+dx>>9,
      &m("%d-%02d-%02d %02d:%02d:%02d",
         d>>9&127+1980,d>>5&15,d&r31,c>>11&r,c>>5&63,c&r*2)   ;from Touch
*

日付・時刻データを2個の変数に格納しておけるので,リソースの軽量化に役立つでしょう。

編集テキストのサイズを取得するには

リアルタイムで編集テキストのサイズを取得するのは易しそうで難しい事例のひとつです。セーブ直後であれば,ディスク上のファイルサイズをファンクションコールで取得するなどすればよいわけですが,リアルタイムで編集中のテキストのサイズを取得するには意外なところから文字列で読み出して,数値に変換しなければなりません。その意外な場所とは,ステータスラインに各種情報を表示する設定にしたときに,表示する文字列を一時的に展開するバッファです。

1 ^\ "" ?.
        ds[+, #] &Sc(pa,pu+bu,8) ds],   ;システム情報を8バイト複写
        &Ss(pa) t=r, u=pa+10, i-,       ;バッファ,カウンタのセット
        &Im(u,0)                        ;ロング整数初期化
 :l     &Ia(u,t.i-$30)                  ;文字を数値に変換
        i++, (t.i)?{ &Im(u,10) >l }     ;次の桁があれば桁繰上
        &m("%lu",u..0,u..1)             ;ロング整数表示

プロファイル情報から履歴をコンバートするには

コマンドラインで「VZ @」と起動することにより,プロファイル情報から各種の履歴を引き継いで元の編集環境を再現することができます。この場合,単に検索文字列の履歴だけを引き継ぐわけにはいかず,カレントディレクトリや編集ファイルの各種編集テキスト変数の値まで引きずってしまうので,牛刀で鶏を割く風情になってしまいます。

次の例は,検索文字列履歴に限ってコンバートするという単機能版ですが,履歴の識別子となるコロン+1文字はそうたくさんあるわけではないので,ちょっと工夫すればどのバッファの履歴でもコンバートできるでしょう。

1 ^\ "" ?.
        #28 a=wm+230+2,         ;[a]:ラインバッファ先頭アドレス
        (a..-1!='S:')?.         ;Stringバッファのみ対象
        l=wa..19-a,             ;履歴全体の長さ
        (l>Hs)?{ &m(11). }      ;「バッファがいっぱいです」
        &Sm(ps,a,l)             ;ヌルごとコピー
        #54                     ;編集中止
        &m("履歴を転送しました")

特定パスの存在を確認するには

与えられた特定のパスが本当に存在しているか確かめる一番簡単な方法は,ファイル名入力窓にそのパスを書き込み,実際にオープンしてみることです。しかしこの方法は,オープンできるファイルの余裕がなければ使えませんし,ファイル名の履歴に残ってしまったりするし,何よりスマートではありません。

また,ファンクションコール4E00hを使って一致するファイルを検索しようとすると,DTA領域を確保することになったりするので,かなり大袈裟なことになってしまいます。

似たようなディスクアクセスをするのであれば,VZの機能をそのまま利用した方が簡潔なコードで済みますね。ここではコマンド#60(タグ情報の取得)が使えそうです。

#60は実行のたびにディスクアクセスしてファイルの有無を確認し,変数pf(ファイル名ヒストリーバッファ)の指す領域に取得した文字列を格納します。架空のファイル名を与えられた場合は何もせず-1を返します。ファイルが存在している場合は0を返します。

本来の目的とは異なるものの,ファイルの実在を確認するのに使えますね。

1 ^\ "" ?.
        pf[=pa,         ;pfが汚れないようにすりかえる
        #60             ;タグ情報の取得を実行
        pf],            ;pfを戻す
        &m("実在%4s","せずする"+(r+1)*4)

ファイル名の記述された行にカーソルを置いて実行すれば,ファイルの有無が確認できます。なお,ファイル名のどこかにカーソルが乗っている必要はなく,その行のどこかにカーソルがあればVZがすべて面倒を見てくれます。ファイル名を自前で取得する必要がないので,とても楽ちんですね。

編集ファイルのパス,ファイル名,拡張子を抽出するには

他の項目と手法が重複しますが,まとめということになるでしょう。

    &Sc(pa,wa+146)      ;フルパス複写
    &Sy(pa)             ;パス名を解析
    bx.-1-,             ;ファイル名直前の'\'をヌル化
    cx.0-,              ;拡張子直前の'.'をヌル化
    &m("ディレクトリ: %s / ファイル名: %s / 拡張子: %s",pa,bx,cx+1)

なお,裏ファイルのファイル情報を取得する場合は,単にwb+146とするだけで済みます。

編集テキストのセグメント:オフセットアドレスを取得するには

次の部品を使えば簡単に取得することができます。

0:      bx=gz, dx=wa+$92,               ; テキスト名のセグメント:オフセット
        &i("$(8E,DB,CD,21,C3)") (r)     ;       mov     ds,bx
                                        ;       int     21h

この手法は上村郁夫氏によるものです。どのように使われているかの具体例を次に示します。nifty:fgalpk/mes/14/13549(1995/09/14T00:09)

= save.def = by I.Kamimura

【ワンタッチセーブと別名セーブ】

 ● VZ Editor Ver 1.5 以降

* M
80 [ESC]S @S "Save"
        ?.
        (mr&$80)? { &m(36). }           ; 「変更できません」
        (mb)? { #S. }                   ; ブロックモードなら基本機能
        (ks&1)? {                       ; シフト実行なら別名でセーブ
                #J #P #m m=mr,          ; ファイル名を pw へ
                &b(3) #S &l (r)?.       ; ビープ音.[CR]/[ESC] を待つ
                #82 #x &?("%s",pw) #m   ; 元のファイル名にリネーム
                mr=m, #].               ; 修正フラグを元に戻す
        }
        cx-, ax=$4300, &>               ; ファイル属性を調べる
        (cx&1)? {                       ; リードオンリか?
                &a("ディスク上ではリードオンリ属性です.更新します") (r-1)?.
                ax=$4301,
                cx[, cx=0, &> cx],      ; リード/ライト可能に変更
        }
        #S #m                           ; セーブ
        (cx&1)? {
                ax=$4301, &>            ; リードオンリに戻す
                &m("更新後リードオンリ属性に戻しました")
        }

;0:     dz[, dz=gz, dx=wa+$92, &i($21) (r) dz],         ;×

0:      bx=gz, dx=wa+$92,               ; テキスト名のセグメント:オフセット
        &i("$(8E,DB,CD,21,C3)") (r)     ;       mov     ds,bx
                                        ;       int     21h
*

VZ Macro FAQ - file
The VZ resources, valid XHTML 1.0 Strict. ©2005-2007 by mezala.