# ----------------------------------------------------------------------------- # Tripletail::Template::Node - Templateノードオブジェクト # ----------------------------------------------------------------------------- package Tripletail::Template::Node; use strict; use warnings; use Tripletail; #use Smart::Comments; 1; # テンプレートをパーツ毎に分割 # # # aaa<&FOO>bbb # # # # # $this->{tmplvec} = []; # Template Vector # ==> [0] = "\n aaa" # [1] = ['tag', 'foo'] # [2] = "bbb\n " # [3] = ['mark', 'bar'] # [4] = "\n " # [5] = ['copy', 'baz'] # [6] = "\n " # # $this->[tmpltags] = ['foo']; # # $this->{tmplback} = []; # tmplvec のコピー # # 挿入タグや, への挿入は、{タグ名 => 値} のハッシュへ値を設定する事で行う。 # リセット時にはそのハッシュの内容を空にすると同時に tmplvec をバックアップから書き戻す。 # # $this->{valmap} = {}; # Value Map # ==> [tag:foo] = "FOOに入れたテキスト" # [node:bar] = "ノード bar を add した時の内容" # # flush 時にはテンプレートの先頭から少しずつ削って行く事になる為、 # tmplvec の内容は浅く変化する。(つまり配列は変化しても配列の要素までは # 変化しない。) sub _new { my $class = shift; my $parent = shift; # Tripletail::Template::Node または undef (rootの場合) my $name = shift; # の名前。rootならundef my $html = shift; # template html my $this = bless {} => $class; $this->_reset; $this->{parent} = $parent; $this->{name} = lc $name; if(defined $html) { $this->_setTemplate($html); } $this; } sub _reset { my $this = shift; # 以下はルートにのみ存在する $this->{is_xhtml} = undef; # ソース冒頭参照 $this->{tmplvec} = []; $this->{tmplback} = []; $this->{valmap} = {}; # ノード -- {name => Tripletail::Template::Node} $this->{node} = {}; # タグ属性 $this->{attr} = {}; $this; } sub isRoot { my $this = shift; !defined($this->{parent}); } sub isXHTML { my $this = shift; $this->isRoot ? $this->{is_xhtml} : $this->{parent}->isXHTML; } sub _setTemplate { my $this = shift; my $str = shift; $this->_reset; if($str =~ m/^\s*<\?xml/) { $this->{is_xhtml} = 1; } else { $this->{is_xhtml} = undef; } # テンプレートに既にが入っていたらエラー。 if($str =~ m//) { die __PACKAGE__."#setTemplate, we can't implant in a template by hand anymore. Use instead.\n"; } # - をパースして、ノードを生成。 $str =~ s{(.*?)}{ my ($name, $template) = (lc $1, $2); if($this->{node}{$name}) { # 既に同じノードが存在していたらエラー。 die __PACKAGE__."#setTemplate, node [$name] is duplicated.\n"; } $this->{node}{$name} = Tripletail::Template::Node->_new( $this, $name, $template ); ""; }egs; # 置換されなかったがあったらエラー。 if($str =~ m{():.+?>)}) { die __PACKAGE__."#setTemplate, $1 was not matched to an another side.\n"; } $this->_split($str,1); $this; } sub getHtml { my $this = shift; $this->_compose(save_marks => 1); } sub setHtml { my $this = shift; my $html = shift; if(!defined($html)) { die __PACKAGE__."#setHtml, ARG[1] was undef.\n"; } elsif(ref($html)) { die __PACKAGE__."#setHtml, ARG[1] was Ref.\n"; } $this->_split($html,1); $this; } sub node { my $this = shift; my $name = shift; if(!defined($name)) { die __PACKAGE__."#node, ARG[1] is undef.\n"; } elsif(ref($name)) { die __PACKAGE__."#node, ARG[1] is Ref.\n"; } $name = lc($name); my $node = $this->{node}{$name}; if(!$node) { my $me = $this->isRoot ? "the root" : "node [$this->{name}]"; die __PACKAGE__."#node, $me did not have a child node [$name].\n"; } $node; } sub exists { my $this = shift; my $name = shift; if(!defined($name)) { die __PACKAGE__."#exists, ARG[1] was undef.\n"; } elsif(ref($name)) { die __PACKAGE__."#exists, ARG[1] was Ref.\n"; } $name = lc($name); exists $this->{node}{$name}; } sub setAttr { my $this = shift; my $param = do { if(ref($_[0]) eq 'HASH') { shift; } elsif(!ref($_[0])) { scalar { @_ }; } else { die __PACKAGE__."#setAttr, ARG[1] was neither SCALAR nor HASH Ref. [$_[0]]\n"; } }; foreach my $key (keys %$param) { if($param->{$key} eq 'plain' || $param->{$key} eq 'raw' || $param->{$key} eq 'js' || $param->{$key} eq 'br') { $this->{attr}{lc($key)} = $param->{$key}; } else { die __PACKAGE__."#setAttr, ARG[1] has wrong type. [$param->{$key}]\n"; } } $TL->getDebug->_templateLog( node => $this, type => 'setattr', args => $param ); $this; } sub expand { my $this = shift; my $param = do { if(ref($_[0]) eq 'HASH') { shift; } elsif(!ref($_[0])) { scalar { @_ }; } else { die __PACKAGE__."#expand, ARG[1] was neither SCALAR nor HASH Ref. [$_[0]]\n"; } }; $this->_expand($param, 0); } sub expandAny { my $this = shift; my $param = do { if(ref($_[0]) eq 'HASH') { shift; } elsif(!ref($_[0])) { scalar { @_ }; } else { die __PACKAGE__."#expandAny, ARG[1] was neither SCALAR nor HASH Ref. [$_[0]]\n"; } }; $this->_expand($param, 1); } sub add { my $this = shift; $this->expand(@_); $this->_dieIfDirty('add'); if(!defined($this->{parent})) { die __PACKAGE__."#add, internal error [I have no parents]."; } elsif(!defined($this->{name})) { die __PACKAGE__."#add, internal error [I have no name]."; } $TL->getDebug->_templateLog( node => $this, type => 'add' ); # 文字列化 my $composed = $this->_compose; # 親の及びの前に自分自身を挿入する $this->{parent}{valmap}{"node:$this->{name}"} .= $composed; # 元のテンプレートに戻す if($this->{tmplvec} ne [ @{$this->{tmplback}} ]) { $this->{tmplvec} = [ @{$this->{tmplback}} ]; } %{$this->{valmap}} = (); $this; } sub toStr { my $this = shift; $this->_dieIfDirty('toStr'); $TL->getDebug->_templateLog( node => $this, type => 'toStr' ); # 値の定義されていない挿入タグが残っていたらエラー。(expandAll や # flushなどがある為、これが起こり得る。) foreach my $seg (@{$this->{tmplvec}}) { if(ref($seg) && $seg->[0] eq 'tag' && !defined($this->{valmap}{"tag:$seg->[1]"})) { die __PACKAGE__."#toStr, tag [$seg->[1]] was left unexpanded.\n"; } } $this->_compose; } sub getForm { my $this = shift; my $name = shift; if(ref($name)) { die __PACKAGE__."#getForm, ARG[1] was Ref.\n"; } if(!defined($name)) { $name = ''; } my $filter = $TL->newHtmlFilter( interest => ['input'], track => [qw[form textarea select option]], filter_text => 1, ); $filter->set($this->getHtml); my $form = $TL->newForm; ### html: $this->getHtml my $found; while(my ($context, $elem) = $filter->next) { ### elem: $elem if(my $f = $context->in('form')) { my $curname = $f->attr('name'); $curname = defined($curname) ? $curname : ''; if($curname ne $name) { # 関係無いフォーム next; } else { $found = 1; } } else { # form要素の中でない。 next; } if($elem->isElement) { ### name: $elem->name if(lc($elem->name) eq 'input') { my $name = $elem->attr('name'); my $type = lc $elem->attr('type'); my $value = do { my $str = $elem->attr('value'); defined $str ? $str : ''; }; my $checked = do { my $str = lc($elem->attr('checked')); if($str && $str eq 'checked') { $str; } elsif($elem->end && $elem->end eq 'checked') { $elem->end; } else { undef; } }; if(defined($name)) { if(!defined $type || $type eq '' || $type eq 'text' || $type eq 'password' || $type eq 'hidden' || $type eq 'submit' ) { $form->add( $TL->unescapeTag($name) => $TL->unescapeTag($value) ); } elsif($type eq 'radio' || $type eq 'checkbox') { if($checked) { $form->add( $TL->unescapeTag($name) => $TL->unescapeTag($value) ); } else { if(!$form->exists($name)) { $form->set($TL->unescapeTag($name) => []); } } } } } } elsif($elem->isText) { if(my $textarea = $context->in('textarea')) { if(defined(my $name = $textarea->attr('name'))) { $form->add( $TL->unescapeTag($name) => $TL->unescapeTag($elem->str) ); } } elsif(my $option = $context->in('option')) { my $select = $context->in('select'); if($select && defined(my $name = $select->attr('name'))) { my $value = do { if(my $str = $option->attr('value')) { $str; } else { my $str = $elem->str; $str =~ s/^\s*//; $str =~ s/\s*$//; $str; } }; my $selected = do { my $str = lc $option->attr('selected'); if($str && $str eq 'selected') { $str; } elsif($option->end && $option->end eq 'selected') { $option->end; } }; if($selected) { $form->add( $TL->unescapeTag($name) => $TL->unescapeTag($value) ); } } } } } if(!$found) { die __PACKAGE__."#getForm, form [$name] does not exist.\n"; } $form; } sub setForm { my $this = shift; my $form = shift; my $name = shift; if(!defined($form)) { die __PACKAGE__."#setForm, ARG[1] was undef.\n"; } elsif(ref($form) eq 'HASH') { $form = $TL->newForm($form); } elsif(ref($form) ne 'Tripletail::Form') { die __PACKAGE__."#setForm, ARG[1] was not instance of Tripletail::Form. [$form].\n"; } if(ref($name)) { die __PACKAGE__."#setForm, ARG[2] was Ref.\n"; } # $formは後で変更してしまうのでcloneして置く $form = $form->clone; local *popform = sub { # 指定されたkeyの先頭の値を取り出し、それを消す。 my $key = shift; my @array = $form->getValues($key); my $val = shift @array; $form->remove($key => $val); $val; }; if(!defined $name) { $name = ''; } $TL->getDebug->_templateLog( node => $this, type => 'setForm', form => $form, name => $name, ); my $filter = $TL->newHtmlFilter( interest => ['input'], track => [qw(form textarea select option)], filter_text => 1, ); $filter->set($this->getHtml); my $found; while(my ($context, $elem) = $filter->next) { if(my $f = $context->in('form')) { my $curname = $f->attr('name'); $curname = defined $curname ? $curname : ''; if($curname ne $name) { # 関係無いフォーム next; } else { $found = 1; } } else { # form要素の中でない。 next; } if($elem->isElement) { if(lc $elem->name eq 'input') { if(defined(my $name = $elem->attr('name'))) { $name = $TL->unescapeTag($name); my $type = lc $elem->attr('type'); if(!defined($type) || $type eq '' || $type eq 'text' || $type eq 'password' || $type eq 'hidden' || $type eq 'submit') { if($form->exists($name)) { # valueを書換える $elem->attr( value => $TL->escapeTag(popform($name)) ); } } elsif($type eq 'radio' || $type eq 'checkbox') { if($form->exists($name) && defined($elem->attr('value')) && $form->lookup($name,$TL->unescapeTag($elem->attr('value')))) { if($this->isXHTML) { $elem->attr('checked' => 'checked'); }else { $elem->attr('checked' => undef); $elem->end('checked'); } } else { if($this->isXHTML) { $elem->attr('checked' => undef); } else { $elem->attr('checked' => undef); $elem->end(undef); } } } } } } elsif($elem->isText) { if(my $textarea = $context->in('textarea')) { if(defined(my $name = $textarea->attr('name'))) { $name = $TL->unescapeTag($name); if($form->exists($name)) { # textareaの中身を置き換える $elem->str( $TL->escapeTag(popform($name))); } } } elsif(my $option = $context->in('option')) { my $select = $context->in('select'); if($select && defined(my $name = $select->attr('name'))) { $name = $TL->unescapeTag($name); my $value = do { if(my $str = $option->attr('value')) { $str; } else { my $str = $elem->str; $str =~ s/^\s*//; $str =~ s/\s*$//; $str; } }; if($form->exists($name) && $form->lookup($name,$TL->unescapeTag($value))) { if($this->isXHTML) { $option->attr('selected' => 'selected'); } else { $option->attr(selected => undef); $option->end('selected'); } } else { if($this->isXHTML) { $option->attr(selected => undef); } else { $option->attr(selected => undef); $option->end(undef); } } } } } } if(!$found) { die __PACKAGE__."#setForm, form [$name] does not exist.\n"; } $this->setHtml($filter->toStr); $this; } sub extForm { my $this = shift; my $name = shift; if(ref($name)) { die __PACKAGE__."#extForm, ARG[1] was Ref.\n"; } if(!defined $name) { $name = ''; } $TL->getDebug->_templateLog( node => $this, type => 'extForm', name => $name, ); my $filter = $TL->newHtmlFilter( interest => ['form'], filter_text => 0, ); $filter->set($this->getHtml); my $found; while(my ($context, $elem) = $filter->next) { if($elem->isElement) { if(lc $elem->name eq 'form') { my $curname = $elem->attr('name'); $curname = defined $curname ? $curname : ''; if($curname ne $name) { # 関係無いフォーム next; } else { $elem->attr(EXT => 1); $found = 1; } } } } if(!$found) { die __PACKAGE__."#extForm, form [$name] does not exist.\n"; } $this->setHtml($filter->toStr); $this; } sub addHiddenForm { my $this = shift; my $form = shift; my $name = shift; if(!defined($form)) { die __PACKAGE__."#addHiddenForm, ARG[1] was undef.\n"; } elsif(ref($form) eq 'HASH') { $form = $TL->newForm($form); } elsif(ref($form) ne 'Tripletail::Form') { die __PACKAGE__."#addHiddenForm, ARG[1] was not instance of Tripletail::Form or HASH.\n"; } if(ref($name)) { die __PACKAGE__."#addHiddenForm, ARG[2] was Ref.\n"; } if(!defined($name)) { $name = ''; } $TL->getDebug->_templateLog( node => $this, type => 'addHiddenForm', form => $form, name => $name, ); my $filter = $TL->newHtmlFilter( interest => ['form'], ); $filter->set($this->getHtml); my $found; while(my ($context, $elem) = $filter->next) { if($elem->isElement && lc $elem->name eq 'form') { my $curname = do { my $str = $elem->attr('name'); if(defined($str)) { $TL->unescapeTag($str); } else { ''; } }; if($curname eq $name) { $found = 1; foreach my $key ($form->getKeys) { foreach my $value ($form->getValues($key)) { my $e = $context->newElement('input'); $e->attr(type => 'hidden'); $e->attr(name => $TL->escapeTag($key)); $e->attr(value => $TL->escapeTag($value)); if($this->isXHTML) { $e->end('/'); } $context->add($e); } } } } } if(!$found) { die __PACKAGE__."#addHiddenForm, form [$name] does not exist.\n"; } ### before: $this->getHtml ### filtered: $filter->toStr $this->_setHtml($filter->toStr); $this; } sub addSessionCheck { my $this = shift; my $sessiongroup = shift; my $name = shift; my $issecure = shift; if(!defined($sessiongroup)) { die __PACKAGE__."#addSessionCheck, ARG[1] was undef.\n"; } my $session = $TL->getSession($sessiongroup); if(ref($name)) { die __PACKAGE__."#addSessionCheck, ARG[2] was Ref.\n"; } if(ref($issecure)) { die __PACKAGE__."#addSessionCheck, ARG[3] was Ref.\n"; } my $csrfkey = $TL->INI->get($sessiongroup => 'csrfkey', undef); if(!defined($csrfkey)) { die __PACKAGE__."#addSessionCheck, csrfkey was not set. set INI [$sessiongroup].\n"; } do { local $SIG{__DIE__} = 'DEFAULT'; eval 'use Digest::HMAC_SHA1 qw(hmac_sha1_hex)'; }; if($@) { die __PACKAGE__."#addSessionCheck, failed to load HMAC_SHA1.pm [$@]\n"; } my ($key, $sid, $checkval) = $session->getSessionInfo($issecure); if(!defined($sid)) { die __PACKAGE__."#addSessionCheck, Session was not set. need setValue.\n"; } $key = 'C' . $key; my $value = hmac_sha1_hex(join('.', $sid, $checkval), $csrfkey); if(!defined($name)) { $name = ''; } $TL->getDebug->_templateLog( node => $this, type => 'addSessionCheck', name => $name, ); my $filter = $TL->newHtmlFilter( interest => ['form'], ); $filter->set($this->getHtml); my $found; while(my ($context, $elem) = $filter->next) { if($elem->isElement && lc $elem->name eq 'form') { my $curname = do { my $str = $elem->attr('name'); if(defined($str)) { $TL->unescapeTag($str); } else { ''; } }; if($curname eq $name) { $found = 1; if(lc($elem->attr('method')) ne 'post') { die __PACKAGE__."#addSessionCheck, form isn't post method.\n" } my $e = $context->newElement('input'); $e->attr(type => 'hidden'); $e->attr(name => $TL->escapeTag($key)); $e->attr(value => $TL->escapeTag($value)); if($this->isXHTML) { $e->end('/'); } $context->add($e); } } } if(!$found) { die __PACKAGE__."#addSessionCheck, form [$name] does not exist.\n"; } $this->_setHtml($filter->toStr); $this; } sub flush { my $this = shift; $this->_dieIfDirty('flush'); $TL->getDebug->_templateLog( node => $this, type => 'flush'); $this->_flush; } sub _setHtml { my $this = shift; my $html = shift; if(!defined($html)) { die __PACKAGE__."#setHtml, ARG[1] was undef.\n"; } elsif(ref($html)) { die __PACKAGE__."#setHtml, ARG[1] was Ref.\n"; } $this->_split($html); $this; } sub _finalize { my $this = shift; foreach my $node (values %{$this->{node}}) { $node->_finalize; } $this->{node} = undef; } sub _isDirty { # このノードが dirty であるなら、実際に dirty であるノードを返す。 # そうでなければ undef。 # # 或るノードがdirtyであるとは、自分の valmap が空でないか、または # dirty な子ノードを持っている場合を云う。 my $this = shift; my $ignore_dirtiness_of_myself = shift; if(not $ignore_dirtiness_of_myself and %{$this->{valmap}}) { return $this; } foreach my $child (values %{$this->{node}}) { if(my $dirty = $child->_isDirty) { return $dirty; } } undef; } sub _nodePath { # / => ルートノード # /foo => ルート直下のノード"foo" my $this = shift; if($this->{parent}) { my $parent_path = $this->{parent}->_nodePath; $parent_path eq '/' ? "/$this->{name}" : "$parent_path/$this->{name}"; } else { '/'; } } sub _dieIfDirty { # dirtyな子ノードがあったらdie。 my $this = shift; my $method = shift; if(my $dirty = $this->_isDirty(1)) { die __PACKAGE__."#$method, node [".$dirty->_nodePath."] was modified but not added to the parent.\n"; } $this; } sub _flush { my $this = shift; my $mark = shift; # 名。undefの場合がある。(後述) # ルートノードのflushは、(もしあれば)指定されたまで取り出し、 # それを出力してから消す事で行う。 # ルート以外では、先に自分の親ノードの_flushを自分の名前付きで呼んだ後に、 # (もしあれば)指定されたまでを取り出して、それを出力して消す。 if(defined($this->{parent})) { # ルートでない。 $this->{parent}->_flush($this->{name}); } my $to_flush = do { if(defined($mark)) { my $ret = ''; unless(grep { ref($_) && $_->[0] eq 'mark' && $_->[1] eq $mark; } @{$this->{tmplvec}}) { die __PACKAGE__."#flush, node [$mark] seems to be already flushed.\n"; } while(my $seg = shift @{$this->{tmplvec}}) { if(ref($seg)) { if($seg->[0] eq 'tag') { my $ref = \$this->{valmap}{"tag:$seg->[1]"}; if(defined($$ref)) { $ret .= $$ref; } else { die __PACKAGE__."#flush, tag [$seg->[1]] was left unexpanded.\n"; } } elsif($seg->[0] eq 'mark' || $seg->[0] eq 'copy') { my $ref = \$this->{valmap}{"node:$seg->[1]"}; if(defined($$ref)) { $ret .= $$ref; } if($seg->[0] eq 'mark' && $seg->[1] eq $mark) { # ここで終わり $$ref = undef; unshift @{$this->{tmplvec}}, $seg; last; } } else { die "internal error: unknown segment type: $seg->[0]"; } } else { $ret .= $seg; # ただの文字列 } } $ret; } else { # $markがundefであるのは次の場合。 # 1. ルートノードに対してflush()が呼ばれた場合 # -- この場合は現在の$this->{html}の内容をそのまま出力して消す。 # 2. ルート以外のノードに対してflush()が呼ばれ、且つ_flush()の呼出しが # 全ての祖先に対しての再帰を終えた後。 # -- この場合は何も消さず何も出力せずに終了。 unless(defined($this->{parent})) { my $composed = $this->_compose; $this->{tmplvec} = []; $composed; } else { ''; } } }; $TL->print($to_flush); $this; } sub _expand { my $this = shift; my $param = shift; # always HASH ref my $allow_unexpanded = shift; $TL->getDebug->_templateLog( node => $this, type => 'expand', args => $param, any => $allow_unexpanded ); while(my ($key, $val) = each %$param) { if(!defined($val)) { die __PACKAGE__."#expand, value for key [$key] was undef.\n"; } elsif(ref($val)) { die __PACKAGE__."#expand, value for key [$key] was a ref. [$val]\n"; } $key = lc($key); $val = $this->_filter($key, $val); $this->{valmap}{"tag:$key"} = $val; } unless($allow_unexpanded) { if(keys %{$this->{valmap}} != @{$this->{tmpltags}}) { foreach my $seg (@{$this->{tmplvec}}) { ref $seg or next; $seg->[0] eq 'tag' or next; unless(defined($this->{valmap}{"tag:$seg->[1]"})) { die __PACKAGE__."#expand, key [$seg->[1]] was left unexpanded.\n"; } } } } $this; } sub _filter { my $this = shift; my $key = shift; my $value = shift; # value will be modified, if $key isn't raw. # Return: $value that has been modified. if(!exists($this->{attr}{$key}) || $this->{attr}{$key} eq 'plain') { $value = $TL->escapeTag($value); } elsif($this->{attr}{$key} eq 'raw') { # do nothing } elsif($this->{attr}{$key} eq 'js') { # JavaScript filter $value = $TL->escapeJs($value); } elsif($this->{attr}{$key} eq 'br') { # insert
or
before newlines $value = $TL->escapeTag($value); if($this->{is_xhtml}) { $value =~ s!(\r?\n)!
$1!g; } else { $value =~ s!(\r?\n)!
$1!g; } } else { die __PACKAGE__."#_filter, internal state error.\n"; } $value; } my $re_split = qr{( < (?: & | # 挿入タグの場合 !(?:mark|copy): # mark または copy の場合 ) [^>]+ > )}x; sub _split { my $this = shift; my $src = shift; my $tmpwrite = shift; my $vec = []; my $tags = []; foreach my $part (split $re_split, $src) { defined $part or next; length $part or next; if(substr($part, 0, 1) ne '<') { push @$vec, $part; } else { if($part =~ m/<&(.+?)>/) { push @$vec, [tag => lc $1]; push @$tags, lc $1; } elsif($part =~ m//) { push @$vec, [$1 => lc $2]; } else { push @$vec, $part; } } } $this->{tmplvec} = $vec; $this->{tmpltags} = $tags; $this->{tmplback} = [ @$vec ] if($tmpwrite); $this->{valmap} = {}; } sub _compose { # このメソッドの動作速度は重要。 my $this = shift; my $opts = { @_ }; my $ret; my $save_marks = $opts->{save_marks}; foreach my $seg (@{$this->{tmplvec}}) { if(ref($seg)) { my $dest = ($seg->[0] eq 'tag' ? 'tag' : 'node'); my $ref = \$this->{valmap}{"$dest:$seg->[1]"}; if(defined($$ref)) { $ret .= $$ref; } if($save_marks) { if($seg->[0] eq 'tag') { if(!defined($$ref)) { $ret .= sprintf '<&%s>', $seg->[1]; } } else { $ret .= sprintf '', $seg->[0], $seg->[1]; } } } else { $ret .= $seg; } } $ret; } __END__ =encoding utf-8 =head1 NAME Tripletail::Template::Node - Templateノードオブジェクト =head1 DESCRIPTION L 参照 =head2 METHODS =over 4 =item add L 参照 =item addHiddenForm L 参照 =item addSessionCheck L 参照 =item exists L 参照 =item expand L 参照 =item expandAny L 参照 =item extForm L 参照 =item flush L 参照 =item getForm L 参照 =item getHtml L 参照 =item isRoot L 参照 =item isXHTML L 参照 =item node L 参照 =item setAttr L 参照 =item setForm L 参照 =item setHtml L 参照 =item toStr L 参照 =back =head1 AUTHOR INFORMATION =over 4 Copyright 2006 YMIRLINK Inc. All Rights Reserved. This framework is free software; you can redistribute it and/or modify it under the same terms as Perl itself このフレームワークはフリーソフトウェアです。あなたは Perl と同じライセンスの 元で再配布及び変更を行うことが出来ます。 Address bug reports and comments to: tl@tripletail.jp HP : http://tripletail.jp/ =back =cut