名前

WML Macros - 強力なWMLマクロを書く


説明

このチュートリアルはWMLマクロを書くための手引である。巧妙なマクロを書く ためには、初心者が初めの雛型を書くための、役立つヒントが必要である。 このドキュメントを最高に活用するためには、WMLが私用する個々の段階のド キュメントを読んでおく事が望ましい。

以下の例文は次のようにコンパイルするものとする

  wml -q -p123 test.wml

ほとんどの場合、wml_p2_mp4h パスだけを通せるが、しかし下のラインはより 一般的である。


導入


定義

これらの定義はこの文章で使われるものである。私は詳細にはこだわらないので、 W3Cの定めるものと多少違うかもしれない。


ファーストコンタクト

基本的に全てのマクロ定義は <define-tag> で行なわれる。 これは普通の例である:

Input:

  1| <define-tag foo>
  2| bar
  3| </define-tag>
  4| <FOO>

Output:

  1|
  2|
  3| bar
  4|

この普通の例にはいくつかの興味深いポイントがある:


単純タグについて

HTMLにおける単純タグは以下のような終了タグを持たない要素である。

    <br>

しかし、XMLでは単純タグは次のどちらかの方法で書かれなければならない:

    <br></br>
    <br/>

すなわちボディ無しの複合タグのようにするか、または開始タグの後にスラッ シュを加えるかのどちらかになる。はじめのものはWMLで動かず、その上HTML ブラウザを混乱させるかもしれないので、避けなければならない。そこで、 あなたはこの後置スラッシュを加えるかどうかを選ばなければならない。 WMLはスラッシュがあっても無くてもどちらでも動作する。

この文章では、新しいXHTML規格に従うために、常に後置スラッシュを使用す る。これは私には好ましい書き方だが、他人はこの後置スラッシュ無しでまた 続けるかもしれない。あなたは、どの文法に従いたいかを決めよう。

一方、HTMLブラウザはXHTML構文により混乱するかもしれないので、出力した テキストにはこの後置スラッシュを含まない。これは一見矛盾している。 しかし、このアプローチにより我々の入力ファイルは将来のXHTMLツールで処 理できるように準備ができている。そして、我々がXHTMLのためのページを生 産するためには、適切なフラグでWMLを実行するだけである。


新しいタグの定義

既知の要素が入力テキストから見つかる度に、取り除かれてテキストで置き 換えられる。その後、その中に他のマクロを含む場合にそなえ、この置き換 えたテキストはスキャンされる。

全てのユーザマクロは define-tag 要素で定義される。その最初の属性は 定義されるマクロの名前である。そして、そのボディはこのマクロの代りに 挿入されるテキストである。

単純な例を見てみよう:

Input:

  1| <define-tag homepage>http://www.engelschall.com/sw/wml/<;/define-tag>
  2| <homepage/>

Output:

  1|
  2| http://www.engelschall.com/sw/wml/

複合タグを定義する事は難しくない、そのためには endtag=required 要素 を追加する。

Input:

  1| <define-tag foo endtag=required>bar</define-tag>
  2| <foo>baz</foo>
 
Output:

  1|
  2| bar


特別なテキスト

いくつかの文字列は、マクロのカスタムのために特別な意味を持ち、特定の値と置きかえられる。

%0 %1 ...

属性: %0 は、はじめの属性である。同様に %1、という風に続く。

%name

マクロ名

%attributes

空白で区切られた属性のリスト

%body

マクロのボディ部分(複合タグのみ)

%#

引数の数

%%

パーセント記号

Input:

  1| <define-tag foo endtag=required>
  2| Macro name:          %name
  3| Number of arguments: %#
  4| First argument:      %0
  5| Second argument:     %1
  6| All arguments:       %attributes
  7| Body macro:          %body
  8| </define-tag>
  9| <foo Here are attributes>
 10| And the body
 11| goes here.
 12| </foo>

Output:

  1| 
  2| 
  3| Macro name:          foo
  4| Number of arguments: 3
  5| First argument:      Here
  6| Second argument:     are
  7| All arguments:       Here are attributes
  8| Body macro:          
  9| And the body
 10| goes here.
 11| 
 12| 

これらの特殊文字はまた修飾文字により変更されるかもしれない。それは、 パーセント記号の後に置かれる一文字以上の文字列である。修飾文字には 次のようなものがある:

U (展開しない)

文字は置きかえられるが、展開されない。(expansionについての章を見よ)

A (配列)

リストは空白のかわりに改行で区切られる。この修飾文字は %attributes だけで働く。

Input:

  1| <define-tag foo endtag=required>
  2| First argument:      %A0
  3| All arguments:       %Aattributes
  4| Body macro:          %Abody
  5| </define-tag>
  6| <foo Here are attributes>
  7| And the body
  8| goes here.
  9| </foo>

Output:

  1| 
  2| 
  3| First argument:      Here
  4| All arguments:       Here
  5| are
  6| attributes
  7| Body macro:          
  8| And the body
  9| goes here.
 10| 
 11| 

注意点としては、マクロが読み込まれる時にこれらのシーケンスが置き換えら れ、その後に置き換えられたテキストが再びスキャンされる事である。これは 以下のような構造を書かないためにも、たいへん重要である。

   <if <get-var foo /> %body />

内側の %body は、&lt;if&gt; 要素で置きかえられる。 前にスキャンされる。 それは予想できない結果を引きおこすかもしれない。次のようにするとまだ ましである。

   <if <get-var foo /> "%body" />

しかし、%bodyが二重引用符を含んでしまうと、トラブルの元となる。 そのため、引数のうち一つが特別なシーケンスを含んでいる時、決して &lt;if&gt; (と派生物)をつかってはならない。その代りとしては

   <when <get-var foo />>
   %body
   </when>


空白文字

これまでの例では、多量の使わない改行を表示してきた。これらを消去する 方法がある。一つの方法としては pass 1 で、バックスラッシュを行末に置くと、 行末の改行を捨てる。

Input:

  1| <define-tag foo>\
  2| bar\
  3| </define-tag>\
  4| <FOO/>

Output:

  1| bar

別の手法としては、マクロ定義に whitespace=delete を加える。例えば、

  1| <define-tag foo whitespace=delete>
  2| bar
  3| </define-tag>
  4| <FOO/>

Output:

  1|
  1| bar

1行目の改行は &lt;/define-tag&gt; の後の改行なので捨てられない。

この属性が使われると、全ての頭と終りの空白、角括弧の外側の改行は全て取り 除かれる。


属性つきのマクロ

WMLの素晴しい機能として、任意の属性が扱える事がある。マクロで属性を 扱う方法はいくつもある。ここでは全てのWMLモジュールで使える標準的な 方法について扱う。

HTMLの文法では attribute=value は変数の割当てのみなので、属性は変数で 格納される。ローカル変数を扱うために、プッシュ/ポップのメカニズムが利用 される。次の例を見てみよう。

Input:

  1| <define-tag href whitespace=delete>
  2| <preserve url />
  3| <preserve name />
  4| <set-var %attributes />
  5| <if <get-var name /> ""
  6|   <set-var name="<tt><get-var url /></tt>" /> />
  7| <a href="<get-var url />"><get-var name /></a>
  8| <restore name />
  9| <restore url />
 10| </define-tag>
 11| <href url="http://www.w3.org/"; />

Output:

  1| 
  2| <a href="http://www.w3.org/";><tt>http://www.w3.org/<;/tt></a>

&lt;preserve&gt; タグは、スタックの先頭に引数で指定された変数を プッシュし、そしてその変数をクリアする。この変数は &lt;set-var %attributes&gt;で割りあてられないと空白になる。 &lt;resstore<gtタグは、スタックの先頭の値をポップして、引数で指定さ れた変数にセットする。

HTMLのいくつかの属性では値が無いのが正当なものがある。 そういった属性は検出されるかもしれない

Input:

  1| #use wml::std::info
  2| <define-tag head whitespace=delete>
  3| <preserve title>
  4| <preserve info>
  5| <set-var info=*>
  6| <set-var %attributes>
  7| <head*>
  8| <ifeq "<get-var info>" "" <info style=meta>>
  9| <if "<get-var title>" "<title*><get-var title></title*>">
 10| </head*>
 11| <restore info>
 12| <restore title>
 13| </define-tag>
 14| <head title="Test page 1">
 15| <head info title="Test page 2">

Output: (only non-blank lines are printed)

     <head><title>Test page 1</title></head>
     <head>
     <nostrip><meta name="Author"    content="Denis Barbier, barbier@localhost">
     <meta name="Generator" content="WML 2.0.2 (21-Jun-2000)">
     <meta name="Modified"  content="2000-05-09 23:57:31">
     </nostrip>
     <title>Test page 2</title></head>


クォートとグループ化

で、引用符または二重引用符で囲むことにより、いくつかの言葉を含んでいる 属性を指定する事ができる。WMLは二重引用符だけを理解する。

  1| <define-tag foo>\
  2| Number of arguments: %#
  3| First argument:      %0
  4| </define-tag>\
  5| <foo Here are attributes />\
  6| <foo "Here are" attributes />\

Output:

  1| Number of arguments: 3
  2| First argument:      Here
  3| Number of arguments: 2
  4| First argument:      Here are


拡張

この章では、全ての例は以下のコマンドラインで処理される

   wml -W2,-dat -q -p123

そして、trace ではじまる全ての出力行は、このデバッグフラグにより生成 される。

この章は理解が難しいだろう。しかしこれらの概念がたまに(大部分はWMLチュー トリアルのためにマクロを書くとき)必要なだけなので、わからなくてもWMLを 使う事はできる。

標準では、タグがスキャンされる時、マクロは展開される。

Input:

  1| <define-tag foo>%attributes</define-tag>\
  2| <define-tag bar>baz</define-tag>\
  3| <foo name="<bar/>" />

Output:

  1| trace: -1- <define-tag foo>
  2| trace: -1- <define-tag bar>
  3| trace: -2- <bar>
  4| trace: -1- <foo name=baz>
  5| name=baz

まず、 &lt;bar&gt; マクロが最初に処理される、そして &lt;foo&gt; が処理される (ハイフンの間の行が、処理されるレベルを示す)。 実際にはWMLは foo という名前を見付ける。これはマクロ名なので、属性が探索される。 属性がスキャンされると、それは &lt;bar&gt; を見付ける。このマクロには属性 が無いので、&lt;foo&gt; 属性のスキャンが終了すると、該当するテキストで置 き換えられる。

考察してみよう

Input:

  1| <define-tag foo attributes=verbatim>%attributes</define-tag>\
  2| <define-tag bar>baz</define-tag>\
  3| <foo name="<bar/>" />

Output:

  1| trace: -1- <define-tag foo>
  2| trace: -1- <define-tag bar>
  3| trace: -2- <bar>
  4| trace: -1- <foo name=<bar>>
  5| trace: -1- <bar>
  6| name=baz

attributes=verbatim 属性は、このマクロの属性がスキャンされると、展開 されないという事をWMLに告げる。すると、最初の4行は理解しやすくなる。 しかし、&lt;foo&gt; の後で展開されなければ

   name=<bar>

このテキストは再びスキャンされ、&lt;bar&gt; は展開される。

この展開を禁ずるには、特別なテキストの章で説明される U 修飾文字を使う。

Input:

  1| <define-tag foo attributes=verbatim>%Uattributes</define-tag>\
  2| <define-tag bar>baz</define-tag>\
  3| <foo name="<bar/>" />

Output:

  1| trace: -1- <define-tag foo>
  2| trace: -1- <define-tag bar>
  3| trace: -2- <bar>
  4| trace: -1- <foo name=<bar>>
  5| name=<bar>


MP4H と EPERL を混ぜる

準備をしてから、mp4hePerl を混ぜる方法を見てみよう。以下の章は ちょっとトリッキーである。手っ取りばやく使用するならば直接 How to use these macros を見る事。


入れ子の ePerl マクロは動作しない

このマクロを見てみよう:

   <define-tag show-attr><: print "attrs:%attributes"; :></define-tag>

ぱっと見には、次のようになる。

   <define-tag show-attr-ok>attrs:%attributes</define-tag>

しかし、マクロが入れ子にされると、何が起きるだろうか?

Input:

  1| <show-attr-ok <show-attr-ok 0 /> />

Output:

  1| attrs:attrs:0

きちんと動作する! その一方、

Input:

  1| <show-attr <show-attr 0 /> />

Output:

  1| ePerl:Error: Perl parsing error (interpreter rc=255)
  2| 
  3| ---- Contents of STDERR channel: ---------
  4| Backslash found where operator expected at /tmp/wml.1183.tmp1.wml line
  5| 10, near ""attrs:<: print attrs:0; print "\"
  6|         (Missing operator before \?)
  7| syntax error at /tmp/wml.1183.tmp1.wml line 10, near ""attrs:<: print
  8| attrs:0; print "\"
  9| Execution of /tmp/wml.1151.tmp1.wml aborted due to compilation errors.
 10| ------------------------------------------
 11| ** WML:Break: Error in Pass 3 (rc=74).

ふぅ、なにか間違っているようだ。 pass 2 の出力はこうなる

  1| <: print "attrs:<: print attrs:0; :>"; :>

そして、ePerl コマンドはネストできないので、エラーとなる。 (もしpass 2の出力を見て理解できないなら、以前の章を読みなおしましょう)

この例は単純なものであり、回避方法も単純である (&lt;show-attr-ok&gt; を代りに使えばよい)。しかし、こうやって追跡するのが難しい場合も多い。 例えば、WMLのモジュールで定義されたマクロを入れ子で使う時、ePerl コード があるかどうかはわからない。


まずは問題解決へ挑戦

問題は、この文章によれば ePerl コマンドが入れ子にできないという事 である。そこで、まずは入れ子のレベルを数え、そして外部の時だけ ePerl のデリミタを表示してみよう。

Input:

  1| <set-var __perl:level=0 />\
  2| <define-tag perl endtag=required whitespace=delete>
  3| <increment __perl:level />
  4| <when <eq <get-var __perl:level /> 1 />>
  5| <: %body :>
  6| </when>
  7| <when <neq <get-var __perl:level /> 1 />>
  8| %body
  9| </when>
 10| <decrement __perl:level />
 11| </define-tag>\
 12| <define-tag add1 endtag=required>\
 13| <perl>$a += 1; %body</perl>\
 14| </define-tag>\
 15| <add1><add1><add1></add1></add1></add1>
 16| <:= $a :>

Output:

  1|
  2| 3

別の例を見てみよう(1-11行には変更無し)

Input:

 12| <define-tag remove-letter endtag=required whitespace=delete>
 13| <perl>
 14|   $string = q|%body|; $string =~ s|%0||g; print $string;
 15| </perl>
 16| </define-tag>\
 17| <remove-letter e>Hello this is a test</remove-letter>

Output:

  1| Hllo this is a tst

以前定義したように、&lt;remove-letter&gt; タグを入れ子にすると、この ようになってしまう:

Input:

 17| <remove-letter s><remove-letter e>\
 18| Hello this is a test\
 19| </remove-letter></remove-letter>

Output:

  1| ePerl:Error: Perl parsing error (interpreter rc=255)
  2| 
  3| ---- Contents of STDERR channel: ---------
  4| Bareword found where operator expected at /tmp/wml.1198.tmp1.wml
  5| line 10, near "q|$string = q|Hello"
  6| syntax error at /tmp/wml.1198.tmp1.wml line 10, near "q|$string =
  7| q|Hello this "syntax error at /tmp/wml.1198.tmp1.wml line 10, near ";|"
  8| Execution of /tmp/wml.1198.tmp1.wml aborted due to compilation errors.
  9| ------------------------------------------
 10| ** WML:Break: Error in Pass 3 (rc=74).

わかりやすくするためにはじめの2 passだけを実行して、どのような入力が ePerl に送られるかを見てみよう:

    prompt$ wml -q -p12 qaz.wml
    <: $string = q|$string = q|Hello this is a test|; $string =~ s|e||g;
    print $string;|; $string =~ s|s||g; print $string; :>

期待としては ePerl のデリミタはセンテンスのまわりだけに置かれて、入れ子 にはならない。しかし、%body という指示語が ePerl コードと置き換えら れたので、そうはならない。

これらの特別な言葉(%&lt;digit&gt;, %body, %attributes, ...) が使われると、置き換えテキストが ePerl コマンドを含まない事が保証でき ないため、ePerlのデリミタの範囲に含まれると、トラブルとなる。


wml::std::tags モジュールにより定義されるマクロ

wml::std::tags(3) モジュールは、入れ子にされた ePerl コマンドを使うため の方法を提供する。前の例はこのように書けるだろう

Input:

  1| #use wml::std::tags
  2| 
  3| <define-tag remove-letter endtag=required whitespace=delete>
  4| <perl>
  5| <perl:assign $string>%body</perl:assign>
  6| <perl:assign $letter>%0</perl:assign>
  7| $string =~ s|$letter||g;
  8| <perl:print: $string />
  9| </perl>
 10| </define-tag>\ 
 11| <remove-letter s><remove-letter e>\
 12| Hello this is a test\
 13| </remove-letter></remove-letter>

Output:

      ...61 empty lines...
  62| Hllo thi i a tt
  63|
  64|

これが働く方法を解説しようとすると、この文章で扱う方法を越えてしまう。 ここでは wml::std::tags モジュールが提供するコマンドとその使用法に 集中しよう。下のリストの擬似perlコマンドは、マクロと同等の機能を持つ。

<perl:var />

このマクロは、入れ子のレベルにより全部異なる、Perlの変数に展開される。

    $perl_var<get-var __perl:level />
<perl:print>string</perl:print>

この複合タグは、その内容を表示する。

   print qq(string);
<perl:print: string />

この単純タグは、その属性を表示する。

   print string;
<perl:print:var />

&lt;perl:var&gt;変数を表示する。

  print $perl_var<get-var __perl:level />;
<perl:assign $variable>value</perl:assign>

Perlの変数を割当てる。もし属性が無ければ、値は &lt;perl:var&gt; に割当てられる。

   $variable = qq(value);
<perl:assign:sq $variable>value</perl:assign>

Perlの変数を割当てる。もし属性が無ければ、値は &lt;perl:var&gt; に割当てられる。

   $variable = q(value);


マクロの使用法

問題に解決法があるという事を知った今、その確実な方法を学びたいだろう。 それには二つの鉄則がある:

  1. Perl分の中で特殊文字 (%&lt;digit&gt;, %body, %attributes, ...) を決して使わない事。

  2. Perlの print 文とその派生物も決して使わない事。

まず、これを

  $var1 = qq|%body|;
  $var2 = q|%body|;

こう置き換える

  <perl:assign $var1>%body</perl:assign>
  <perl:assign:sq $var2>%body</perl:assign:sq>

そして次に

  print $string;
  print "<img src=\"$src\" alt=\"$alt\">";

こうする

  <perl:print: $string>
  <perl:print><img src="$src" alt="$alt"></perl:print>


Example 1: wml::des::lowsrc の単純なバージョン

入れ子でないバージョン:

  <define-tag lowsrc>
  <:
  {
      my $src = '%0';
      my $lowsrc = $src;
      $lowsrc =~ s|\.([^.]+)$|.lowsrc.$1|;
      system("convert -monochrome $src $lowsrc");
      print "lowsrc=\"$lowsrc\"";
  }
  :>
  </define-tag>

入れ子バージョン:

  <define-tag lowsrc>
  <perl>
  {
      my $src;
      <perl:assign:sq $src>%0</perl:assign:sq>
      my $lowsrc = $src;
      $lowsrc =~ s|\.([^.]+)$|.lowsrc.$1|;
      system("convert -monochrome $src $lowsrc");
      <perl:print> lowsrc="$lowsrc"</perl:print>
  }
  </perl>
  </define-tag>

最初の変更($srcへの割当て)は、属性にePerlコマンドを許可する。そして、 第二の変更(出力結果)は、このマクロがePerlコマンドに現れるのを許可する。 見ての通り、これはかなり素直な方法である。WMLモジュールが書かれた方法 がわかるかもしれない。

これまでの例や定義では、出力は全て標準出力に対しておこなわれた。 しかし、時々ファイルハンドルに対して出力したい。wml::fmt::xtable を例にしてみよう

入れ子でないバージョン:

  <define-tag xtable endtag=required>
  <:
  {
      my $options = qq|%attributes|;
      my $tmpfile = "<get-var WML_TMPDIR>/wml.table.$$.tmp";
      local (*FP);
      open(FP, ">$tmpfile");
      print FP "<" . "wwwtable $options>\n";
      print FP <<'__XTABLE__EOT'
  %body
  __XTABLE__EOT
  ;
      print FP "<" . "/wwwtable>\n";
      close(FP);
      open(FP, "$WML_LOC_LIBDIR/exec/wml_aux_freetable -w $tmpfile|");
      local ($/) = undef;
      print <FP>;
      close(FP);
      unlink("$tmpfile");
  }
  :>
  </define-tag>

入れ子バージョン:

  <set-var __xtable:level=0 />
  <define-tag xtable endtag=required>
  <increment __xtable:level />
  <perl filehandle="FH_XTABLE">
  {
      my $tmpfile = "<get-var WML_TMPDIR />/wml.table.$$.tmp";
      my $options;
      <perl:assign $options>%attributes</perl:assign>;
      <when <eq <get-var __xtable:level /> 1 />>
      local *FH_XTABLE;
      open(FH_XTABLE, ">$tmpfile");
      </when>
      <perl:assign>
      <wwwtable $options>
          %body
      </wwwtable>
      </perl:assign>
  </perl>
  #   we cut here to change filehandle
  <perl>
      <when <eq <get-var __xtable:level /> 1 />>
      print FH_XTABLE <perl:var/>;
      close(FH_XTABLE);
      open(FH_XTABLE_IN,
         "<get-var WML_LOC_LIBDIR />/exec/wml_aux_freetable -w $tmpfile |");
      local ($/) = undef;
      #  The asterisk below prevents expansion during pass 2 and is
      #  removed after this pass.
      <perl:var/> = <*FH_XTABLE_IN>;
      close(FH_XTABLE_IN);
      <perl:print:var/>
      unlink("$tmpfile");
      </when>
  }
  </perl>
  <decrement __xtable:level />
  </define-tag>

ファイルハンドルは、perlタグの属性により定義される。以降の &lt;perl:print&gt; への呼出しは、全てファイルハンドルに対し出力される。


AUTHOR

 Denis Barbier
 barbier@imacs.polytechnique.org