H2H/2.0計画

H2H/2.0計画

[8] 誰も期待していないとは思いますが、元々『冬日』の記述のために 作られた(とされる)マーク付け言語, H2H の第2版 (2.0) の 予定をお知らせします。

H2H 言語のサンプル (そしておそらくは唯一の) 実装は、 parser と HTML などへの転写部分をほぼ完全に分離する ことにしています。このうち parser 部分は昨日一気に 書き上げてしまい、今日はそれに肉付けしていました。

H2H/2.0 に適合する(完全な)文書はこんな感じになります。

  H2H/2.0
  #V TENKI 雪
  NEW {cat=>"時候"} はつまうで
  
  今日は
  LINK* http://www.tsurugi.co.jp/toyama-hiejinjya/ 神社
  におまいりに行きました。
  
  軽く参拝(謎)して帰ろうとしたとき、いきなり
  RUBY 雷鳴 らいめい
  が響きました。なにか
  DEL 不潔
  INS 不吉
  なものを感じました。

段落を意味する P 要素の印は空行で代用できるようになります。 去年からの念願のウェブの掲示板への転用 FN 改行を BR 要素に変換する今の大多数のウェブの掲示板は とっても汚い! /FN がようやく視野に入りました:-)

それはともかくスケジュール(予定):

2002年1月 サンプル実装の parser を完成させる 2002年2月 サンプル実装の HTML/XHTML 転写を完成させる 2002年3月 H2H/2.0 の仕様書の刊行

2002年前半 モジュール *1 の実装, plain-text 転写などの実装

注1 table (表) module, math (数学) module, chem (化学) module を予定。

2002年夏休みまで (遊んでていいのか?) 『冬日』で供用開始 (動的生成対応) 用語集システム本格実装 ヱブサーバーで書き換えの実装 (去年失敗した:-) その他(謎)

わかば。 (予定はあくまで予定。) (2002年1月2日)

現在の H2H/2.0 案による例文:

 #?H2H/2.0 ns:math="urn:x-suika-fam-cx:ns:h2h:math"
 SECTION\ {h=>"はじめに"}
 この文書は、云々かんぬんに付いて説明するものです。
 
 この文書は、かんぬん云々については説明しません。
 SECTION/\
  
 H= 次に。
  
  H2H/2.0 では大胆に構文を変えてしまうことにしますた。
  例えば Wiki でいう
 SEE= BracketName
  は parser により
 CODE= {t=>H2H} SEE
  に展開します。
 
 空行は段落区切りです。勝手に 
 CODE= {t=>H2H}  P/\
 が補われます。
 SECTION\ {h=>要素}
 大文字の要素名の後に
 CODE= {t=>H2H} \
 がつくのが開始タグで、
 CODE= {t=>H2H} /
 がつくのが終了タグ、
 CODE= {t=>H2H} =
 がつくのが一行タグ (その行で完結)。
 NOTE
 CODE= {t=>H2H} \
 は省略可能です。なお、SGML 風に /ELEMENT ではなく
 CODE= {t=>H2H} ELEMENT/
 なのは日本語入力モードの都合です:)
 NOTE/
 SECTION/
 SECTION= {h=>属性} H2H/2.0 には属性はありません。属性のように見えるのは要素の短縮表記です。
 SECTION/
 (わかば 2003-01-26 07:10)

[10] ぐは。 ... が SuikaWiki に解釈されちまった。 例にし忘れましたが、最初の行にあるとおり、名前空間があります。 要素と属性の別がないので XML の名前空間みたいに変なことはありません。

名前空間宣言は parser が知ってる名前空間は省略可能にしようと思ってます。 可搬性はあれですが。だから上の例では暗黙の既定名前空間の指定がなされているとみなすってことで。 (例えば Wiki で使う時には Wiki で便利な語彙の空間を暗黙定義済みにしておく。)

CODE は多分別名前空間になりますね。 (9 2003-01-26 07:15)

[11] 「次に」の節のように、要素の内容の最初の行の行頭に 1*WSP を入れておくと、2行目以降の同じ数の WSP は無視します。

indent がないと入れ子関係が見た目で分かりませんね... どうしようかなあ。 (9=10)

[12] P が勝手に補われるよりは、 (今までの意味の) P 要素は廃止して、 DOM でいう にしてしまって、空行はその区切りにしてしまえばいいですね。 HTML なりなんなりに変換する時に、適切な文脈なら1個の #text を p 要素に変換するという方向で。 (9=10=11 [sage])

[13] なお従来の版の H2H ではその元になった HNF に倣って行頭の FOO を「命令」と呼んできましたが、今後はちょっと SGML 風になって「開始タグ」と呼びます。 (12)

[14] 属性=要素はしんどいだけであんまり利点がなさそうだし、字下げがないのもつらいよなあ。 てことで上のは撤回して再検討でしょう。

とりあえず既定名前空間は urn:x-suika-fam-cx:ns:h2h:core に決定。これだけ決まってもどうしようもないんですが:-) (わかば)

[15] 作りかけの試作品 (といえる域にも達していない段階) :

my $t = <<EOH;
H2H/2.0 (pre)
#A TENKI &hare;
#A SP {time => "00:00:00"} 年の変わり目
STRONG {level => 10; title => "toku ni nashi => aaa"}
ooo
/STRONG
CITE* href source
/H

akakikukeko
/
SOURCE bar
/CITE
foo
CHEM:NANTOKA
foo bar -> Cl-
/CHEM:NANTOKA
EOH

my $s = new T (element_type => {ABBR => "ABBR",
  RUBY => "RUBY"});

for my $line (split /\x0D?\x0A/, $t) {
  if ($line =~ /^(NEW|SUB)(?:\*\s+([^\s]+))?\s+(.+)/) {
    my ($section_type, $text, $uri) = ($1, $3, $2);
    if ($s->{current_element}->{name} eq 'SEC' 
     && $s->{current_element}->{_SEC_TYPE} eq $section_type) {
      $s->end_element (name => 'SEC');
    } elsif ($s->{current_element}->{name} eq 'P') {
      $s->end_element (name => 'P');
      if ($s->{current_element}->{name} eq 'SEC' 
       && $s->{current_element}->{_SEC_TYPE} eq $section_type) {
        $s->end_element (name => 'SEC');
      }
    }
    my ($empty, $element) 
      = $s->start_element (name => 'SEC', text => $text, 
                           uri => $uri, _SEC_TYPE => $section_type);
      my %helement;
      for my $name (keys %$element) {
        $helement{"PARAM_$1"} = $element->{$name} if $name =~ /^PARAM_H_(.+)/;
      }
      $s->start_element (name => 'H', %helement);
        $s->element_content (text => $element->{text});
      $s->end_element (name => 'H');
  } elsif ($line =~ /^CITE(?:\*\s+([^\s]+))?\s+(.+)/) {
    my ($text) = ($2);
    $s->start_element (name => 'CITE', text => $text, PARAM_URI => $1);
    $text =  $s->{current_element}->{PARAM_SOURCE}
          || $s->{current_element}->{text};
    if ($text) {
      my %helement;
      for my $name (keys %$element) {
        $helement{"PARAM_$1"} = $element->{$name} if $name =~ /^PARAM_SOURCE_(.+)/;
      }
      $s->start_element (name => 'SOURCE', %helement);
        $s->element_content (text => $text);
        $s->{current_element}->{PARAM_URI} = $s->{current_element}->{parent}->{PARAM_URI};
      $s->end_element (name => 'SOURCE');
    }
  } elsif ($line =~ /^([A-Z]+(?::[A-Z]+)*)(?:\*\s+([^\s]+))?(?:\s+(.+))?$/) {
    my ($empty, $element)
      = $s->start_element (name => $1, text => $3, PARAM_URI => $2);
    if ($empty && $s->{current_element}->{name} eq 'RUBY') {
      $s->start_element (name => 'RB');
        $s->element_content (text => $s->{current_element}->{parent}->{_RUBY_BASE});
      $s->end_element (name => 'RB');
      $s->start_element (name => 'RT');
        $s->element_content (text => $s->{current_element}->{parent}->{_RUBY_TEXT});
      $s->end_element (name => 'RT');
      $s->end_element (name => 'RUBY');
    } elsif ($empty) {
      $s->element_content (%$element);
      $s->end_element (%$element);
    }
  } elsif ($line =~ m#^/([A-Z]+(?::[A-Z]+)*)$#) {
    my $name = $1;
    if  ($name ne 'P' 
      && $s->{current_element}->{name} eq 'P'
      && $s->{current_element}->{parent}->{name} eq $name
    ) {
      $s->end_element (name => 'P');
    }
    $s->end_element (name => $1);
  } elsif ($line =~ m#^/$#) {
    $s->end_element (name => $s->{current_element}->{name});
  } elsif ($line) {
    $s->element_content (text => $line);
  } else {
    $s->end_element (name => "P");
    $s->start_element (name => "P");
  }
}

print_element ($s, 0);

sub print_element ($$) {
  my $element = shift;
  my $level = shift;
  print "\t" x $level, $element->{name}, "\n";
  $level++;
  for my $el (@{$element->{elements}}) {
    if ($el->{type} eq "text") {
      print "\t" x $level, $el->{text}, "\n";
    } else {
      print_element ($el, $level);
      for my $param (sort grep {/^PARAM_/} keys %$el) {
        print "\t" x ($level+1), $param, "\t", $el->{$param}, "\n"
          if length $el->{ $param };
      }
    }
  }
}

package T;
use strict;
use overload '@{}' => sub {shift ->{elements}};

sub new ($;%) {
  my $class = shift;
  my $self = {name => "H2H", @_};
  $self->{current_element} = $self;
  bless $self, $class;
}

sub start_element ($%) {
  my $self = shift;
  my $new_element = {@_};
  my $empty_element = 0;
  $new_element->{parent} = $self->{current_element};
  if ($new_element->{text} =~ /^\{/) {
    my $t = $new_element->{text};
    $t =~ s{(?:^\{\s*|\G[;,]\s*)([a-z0-9_-]+)
        \s*=>\s*   (?:"([^"]+)"
                     | ([^\s;,\}]+))\s*
    }{
      my ($name, $value) = ($1, $2 || $3); $name =~ tr/a-z-/A-Z_/;
      $new_element->{"PARAM_".$name} = $value;
      ;
    }gex;
    $t =~ s/^\} *//;
    $new_element->{text} = $t;
  }
  my $element_type = $self->{element_type}->{$new_element->{name}};
  if ($element_type eq 'ABBR') {
    if ($new_element->{text}) {
      $new_element->{texts} = [split(/\s+/, $new_element->{text}, 2)];
      $new_element->{text} = $new_element->{texts}->[0];
      $new_element->{PARAM_TITLE} ||= $new_element->{texts}->[1];
      $empty_element = 1;
    }
  } elsif ($element_type eq 'RUBY') {
    if ($new_element->{text}) {
      $new_element->{texts} = [split(/\s+/, $new_element->{text}, 2)];
      $new_element->{_RUBY_BASE} = $new_element->{texts}->[0];
      $new_element->{_RUBY_TEXT} = $new_element->{texts}->[1];
      $empty_element = 1;
    }
  } elsif ($new_element->{text}) {
    my $count = $self->{param_count}->{$new_element->{name}} || 1;
    if (1 < $count) {
      $new_element->{texts} = [split(/\s+/, $new_element->{text}, $count)];
      unless ($count < $#{$new_element->{texts}}) {
        $empty_element = 1;
      }
    } else {$empty_element = 1}
  }
  push @{$self->{current_element}->{elements}}, $new_element;
  $self->{current_element} = $new_element;
  $empty_element, $new_element;
}

sub element_content ($%) {
  my $self = shift;
  my $content = {@_};
  $content->{type} = "text";
  push @{$self->{current_element}->{elements}}, $content;
  $self;
}

sub end_element ($%) {
  my $self = shift;
  my $end_element = {@_};
  if ($end_element->{name} eq $self->{current_element}->{name}
    && ref $self->{current_element}->{parent}) {
    $self->{current_element} = $self->{current_element}->{parent};
  }
  $self;
}

package T::Element;

use overload '@{}' => sub {shift ->{elements}};

sub new ($) {
  bless {}, shift;
}

sub push ($%) {
  my $self = shift;
  my %prop = @_;
  push @{$self->{elements}}, \%prop;
  $self;
}

ライセンスは例によって GPL ですが、 こんなの再利用もできないでしょう。。。

License

[16] >>15 の部分は GPL (任意の版)。

メモ