【HTML プリプロセッサ】

HTML プリプロセッサ

【まえがき】

 このページは、いまだに vim とかで HTML をゴリゴリ書いている人向けの HTML プリプロセッサを紹介します。vim でゴリゴリのみなさん。気が付いたら同じコードを何度も書いていた。 なんてことありませんか?私はあります。さすがに嫌になって HTML で include とかできないか調べたんですけど、 これがなんと HTML の鬼門だったのですね。 スタイルシートや javascript は別ファイルに切り出して共有できるのに、 HTML にはそういう方法が無いんです。で、HTML プリプロセッサについて調べることになるわけですけど、 これがまあ、ejs やら edge.js やら node.js で使うこと前提の高機能なやつしかありません。 いや、高機能でも使い方が簡単なら問題ないんですが、 gulp から使うから設定ファイルを書かなきゃダメとか、 なにやらハードルがやたらと高い。私みたいな昔ながらのローテクのプログラマには気が引けてしまいます。 そこで、無いなら作るか、どうせすごーく簡単だろうし。ということで作ってみました。 このページでは、そのツールの使い方を説明します。同じローテクなみなさん。 興味があったら使ってみてください。 こちら からダウンロードできます。

【準備】

 コマンド名は hpp とします。 まあ、単なる python3 script なので、名前は好きにリネームしてもらって良いです。 このページの説明ではコマンド名は hpp です。 hpp の処理するファイルは便宜上 hpp ファイルと呼ぶことにします。C++ の hpp ファイルとは関係ありません。 hpp ファイルは html ファイルに hpp ディレクティブ(指令)を埋め込んだものです。 hpp ディレクティブは HTML のコメントの形をしています。例えば

<!--$include file.html -->

 のように書くと file.html の内容をそこに埋め込みます。

 ただし、ディレクティブは1行に1つしか書けません。 また、複数行にわたって書くこともできません。 さらに、他のタグと交ぜることもできません。 つまり、以下のように書くとディレクティブとして認識されません。

<span><!--$include file.html --></span>

 最後に使い方ですが、hpp ファイルの名前が hppfile.html として、 bash などのシェルから以下のように hpp コマンドを実行すると、 処理結果の normal.html が得られます。

hpp < hppfile.html > normal.html

 以下では、hpp ファイルで使えるディレクティブについて説明します。

【include ディレクティブ】

 何をおいてもまずこれをやりたかった。 指定したファイルを読み込んでディレクティブの位置に埋め込みます。 以下のように書くと file.html を読み込んでディレクティブの位置に埋め込みます。

<body>
  <div>
    <!--$include file.html -->
  </div>
</body>

 include ディレクティブは、 ディレクティブの書かれたインデントの深さに合わせて内容もインデントします。 インデントしたくない場合は、以下のように |include ディレクティブを用います。

<body>
  <div>
    <!--$|include file.html -->
  </div>
</body>

【ifdef ディレクティブ】

 フラグが立っているかどうかで処理を分岐することができます。

<body>
  <!--$ifdef flag -->
  <p>hello, world</p>
  <!--$endif -->
</body>

 この例では、flag というフラグが立っているときのみ hello, world が出力されます。 flag というフラグを立てて hpp コマンドを実行するには以下のようにします。

hpp flag < hppfile.html > normal.html

 複数のフラグを立てるには以下のようにします。

hpp flag1 flag2 flag3 < hppfile.html > normal.html

 また、ifndef ディレクティブもあり、フラグが立ってない場合に有効になります。 さらに、else ディレクティブもあります。説明は必要ないですよね。

<body>
  <!--$ifndef japanese -->
  <p>hello, world</p>
  <!--$else -->
  <p>こんにちは、世界</p>
  <!--$endif -->
</body>

【def ディレクティブ】

 def ディレクティブを使うと、繰り返し使うような記述をマクロとして定義できます。

 マクロを定義するには以下のように書きます。

<!--$def @macro -->
hello, world
<!--$enddef -->

 マクロを使う場合はマクロ名を書くだけです。つまり、以下のように書きます。

<div>@macro</div>

 展開された結果は以下のようになります。

<div>hello, world</div>

 マクロの内容が HTML の文法に違反している場合は、 以下のように def ディレクティブを書くと、エディタの警告等をだまらせることができます。

<!--$def @macro -->
<!--$$
Taro & Jiro
$$-->
<!--$enddef -->

 hpp コマンドは、 <!--$$$$--> を単に読み飛ばしますが、 HTML としてはこの両者に挟まれた部分はコメント扱いになります。

 しかし、多くの場合これでは不十分でしょう。 たぶん HTML のエスケープをやりたい場合がほとんどだと思います。 HTML エスケープもやる場合は以下のように書きます。

<!--$def @macro -->
<!--$esc -->
<!--$$
Taro & Jiro
$$-->
<!--$enddef -->

 また、マクロの内容が1行に収まるような短いものであるとき、 def= ディレクティブを使えば1行で定義できます。

<!--$def= @a=Taro @b=Jiro -->

 上記のように複数のマクロ @a と @b を同時に定義できます。 もちろん、1つだけ定義してもかまいません。

 マクロの内容に = やスペースを含めたい場合は以下のように書きます。

<!--$def:/@a:T=a=r=o/@b:J i r o/-->

 def の直後の文字が = の代わりに、def の後の2文字目がスペースの代わりに使われます。 必要に応じて適当な記号を割り当ててください。

 上記の例ではマクロ名として先頭に @ を付けたものを使いましたが、 マクロ名には特に制限はありません。macro でも %macro でも %macro% でもかまいません。 しかし、%a と %abc というマクロ名を同時に使うと、%a の内容が 1 の場合、 %abc は 1bc に書き換えられてしまいます。マクロ名の衝突を避けるためには、 %a%, %abc% のような名前の方がいいかもしれません。

 さらに、def= ディレクティブはフラグを立てることもできます。 以下のように書きます。

<!--$def= flag -->

 フラグは中身が空のマクロとは違います。中身が空のマクロは以下のように定義します。

<!--$def= @macro= -->

 関連して、hpp コマンドを実行するときにマクロを定義したい場合は以下のようにします。

hpp @macro=123 < hppfile.html > normal.html

 hpp ファイルで定義しているマクロと同名のマクロをコマンドラインで定義した場合、 コマンドラインのマクロの方が優先されます。

【use ディレクティブ】

 include ディレクティブはファイルをそのまま読み込むだけで、 パラメータを持たせて任意の文字列で置き換えるようなことはできません。 それを実現するのが use ディレクティブです。

 例えば、内容が以下のようなファイル template.html があった場合、

<p>hello, $a</p>
<p>hello, $b</p>

 以下のように書くと、

<body>
  <!--$use template.html -->
  <!--$sub $a -->
  Taro
  <!--$sub $b -->
  Jiro
  <!--$enduse -->
</body>

 以下のように展開されます。

<body>
  <p>hello, Taro</p>
  <p>hello, Jiro</p>
</body>

 ただし、ファイル名は記号で始まってはいけません。 具体的には、python の isalnum() が True を返す文字で始まる必要があります。 記号で始まる場合は、ファイルの代わりにマクロが使われます。 ただし、名前が記号で始まらないマクロは使えません。

 また、引数が1行で書ける場合は、以下のように use= ディレクティブで1行で書けます。

<body>
  <!--$use= template.html $a=Taro $b=Jiro -->
</body>

引数に = やスペースを含めたい場合は以下のように書きます。

<body>
  <!--$use:/template.html/$a:T=a=r=o/$b:J i r o/-->
</body>

 use ディレクティブ, use= ディレクティブは、 ディレクティブの書かれたインデントの深さに合わせて内容もインデントします。 インデントしたくない場合は、|use ディレクティブ, |use= ディレクティブを用います。

<body>
  <!--$|use template.html -->
  <!--$sub $a -->
  Taro
  <!--$sub $b -->
  Jiro
  <!--$enduse -->
</body>

 enduse ディレクティブは |enduse ディレクティブではないので注意してください。

【def/use ディレクティブ】

 use ディレクティブはマクロではなくディレクティブなので、 1行に1つのみで他のタグと混在することはできません。 ほとんどの場合これでも問題ありませんが、 場合によってはマクロのように自由な位置で展開したいことがあるかもしれません。 そのような場合、def/use ディレクティブを使って use の結果をマクロにすることができます。 以下のように書けば use の結果をマクロにできます。

<!--$def @macro use template.html -->
<!--$sub $a -->
Taro
<!--$sub $b -->
Jiro
<!--$enddef -->

 あるいは、def/use= ディレクティブを使って1行で以下のように書けます。

<!--$def @macro use= template.html $a=Taro $b=Jiro -->

 HTML の文法に違反する引数を渡してエスケープする場合は、以下のように書きます。

<!--$def @macro use template.html -->
<!--$esc -->
<!--$sub $a -->
world
<!--$sub $b -->
<!--$$
Taro & Jiro
$$-->
<!--$enddef -->

【import ディレクティブ】

 include ディレクティブは、 指定されたファイルの内容を読んで呼び出したファイルに埋め込みます。 それと同時に、埋め込む内容は hpp ファイルのとして評価されます。 つまり、include するファイルの中で、 ifdef ディレクティブや def ディレクティブを使うことができます。 ようするに、include するファイルの中でマクロを定義したりできます。

 この性質を使えば、よく使うマクロを1つのファイルで定義しておいて、 include ディレクティブで読み込むというようなこともできます。 しかし、include ディレクティブの目的は内容の埋め込みですから、 マクロの定義だけを読み込みたい場合は、埋め込まれる内容が邪魔です。 この問題を解決するのが import ディレクティブです。

 import ディレクティブは、マクロやフラグの定義だけを読み込んで、 ファイルの内容は全部捨てます。つまり、呼び出し側のファイルを一切変更しません。 import ディレクティブは以下のように書きます。

<head>
  <!-- その他の HTML のヘッダー -->
  <!--$import macrofile.html -->
</head>

【undef ディレクティブ】

 以下のように書くと、定義されいるマクロやフラグを未定義にできます。

<!--$undef @macro -->

【ノウハウ】

<コメントアウト>

 hpp ディレクティブは HTML のコメントの形をしているから、 hpp ディレクティブ自身をコメントアウトできないじゃないか。 という人もいるかもしれません。 もし、hpp ディレクティブを1行だけコメントアウトしたければこうします。

<!----><!--$include file.html -->

 これで、hpp ディレクティブではなく HTML のコメントになります。

 1行だけじゃなく複数行コメントアウトしたい場合は、 nodef というフラグが立っていない場合、以下のようにします。

<!--$ifdef nodef -->
コメントアウトしたい部分1
コメントアウトしたい部分2
<!--$endif -->

<コード例>

 hpp のちょっと便利な使い方として以下のようなものがあります。

<div class="layer1">
  <div class="layer2">
    <!--$def @code@ -->
    <!--$esc -->
    <div class="hello">
      hello, world
    </div>
    <!--$enddef -->
    <div class="code-frame">
      <pre>@code@</pre>
    </div>
  </div>
</div>

 上記のコードは、class の内容にもよりますが、 hpp で処理してブラウザで見ると、例えば以下のような結果を表示します。

<div class="hello">
  hello, world
</div>

 一度でも HTML でプログラミング言語や HTML 自身のコードの例を書いた経験のある人なら、 この便利さが理解できるでしょう。そう、インデントを崩さずコードが書けるのです。 hello, world を記述している部分は layer1, layer2 の中に入っています。 つまり、深いインデントの中にあるのですが、hpp で処理すると、 その深いインデントはキャンセルされます。 また、HTML のコード例をそのまま書くことができます。 エスケープは hpp コマンドがやってくれます。

【ソースコードとダウンロード】

 hpp コマンドのソースコードを以下に示します。 Download ボタンからダウンロードしてください。 こんな小さなプログラムに著作権が主張できるのか分かりませんが、 hpp は MIT ライセンスとします。好きにしてやってください。

 実行には python3 が必要です。バージョンは 3.8 以上です。 コピペあるいはダウンロードしたファイルを python3 コマンドで実行するか、 実行属性を立てて実行してください。


 コードを見てもらえば分かると思いますが、エラー処理とかは最低限の処理しかしていません。 ちょっと文法的におかしなソースを食わせると、意味不明な結果を返すでしょう。 きちんとエラー処理もやれという方は、 hpp はあきらめて ejs や edge.js を頑張って使ってください。

【あとがき】

 みなさん、どうでしょう。hpp コマンドは気に入ってもらえましたか? 今回、hpp コマンドを書いてみて、python3 のいい勉強になりました。 確かに python はこういう小規模なツールを書くのに良さそうな言語ですね。

 何?python3 など邪道?俺は python2 と運命を共にするって? うん、まあ hpp は大した規模のコードじゃないんで、 python2 love な人は自力で書き換えてくださいね。

 え?男ならだまって perl で書けって?お断りします。perl が unix 系 OS の標準ツールの地位を確立したことは認めますが、 いまさら新しいツールを perl でなんか書きたくありません。 私はコードを書くのを楽しみたいのであって、 苦行をしたいわけではないですから。

 それにしても、この程度のツールがネットを検索しても見つからないというのも不思議です。 同じような問題にぶち当たる人は大勢いると思うんですけどねぇ。