################################################################################ # <p> # Wikiフォーマットの文字列をパースし、書式に対応したフックメソッドの呼び出しを行います。 # Wiki::Parserを継承し、これらのフックメソッドをオーバーライドすることで任意のフォーマットへの変換が可能です。 # </p> ################################################################################ package Wiki::Parser; use strict; use Wiki::Keyword; use Wiki::InterWiki; $Wiki::Parser::keyword = undef; $Wiki::Parser::interwiki = undef; #=============================================================================== # <p> # コンストラクタ。 # </p> # <pre> # my $parser = Wiki::HTMLParser->new($wiki); # </pre> #=============================================================================== sub new { my $class = shift; my $wiki = shift; my $self = {}; $self->{wiki} = $wiki; # KeywordとInterWikiは高速化のためモジュール変数として保持する #(ただしmod_perl+Farmの場合はダメなので毎回newする) if(exists $ENV{MOD_PERL}){ $self->{interwiki} = Wiki::InterWiki->new($wiki); $self->{keyword} = Wiki::Keyword->new($wiki,$self->{interwiki}); } else { unless(defined($Wiki::Parser::keyword)){ $Wiki::Parser::interwiki = Wiki::InterWiki->new($wiki); $Wiki::Parser::keyword = Wiki::Keyword->new($wiki,$Wiki::Parser::interwiki); } $self->{interwiki} = $Wiki::Parser::interwiki; $self->{keyword} = $Wiki::Parser::keyword; } $self->{dl_flag} = 0; $self->{dt} = ""; $self->{dd} = ""; return bless $self,$class; } #=============================================================================== # <p> # パース処理を開始します。 # </p> # <pre> # $parser->parse($source); # </pre> #=============================================================================== sub parse { my $self = shift; my $source = shift; $self->start_parse; $source =~ s/\r//g; my @lines = split(/\n/,$source); foreach my $line (@lines){ chomp $line; # 複数行の説明 $self->multi_explanation($line); my $word1 = substr($line,0,1); my $word2 = substr($line,0,2); my $word3 = substr($line,0,3); # 空行 if($line eq "" && !$self->{block}){ $self->l_paragraph(); next; } # パラグラフプラグイン if($line =~ /^{{(.+}})$/){ if(!$self->{block}){ my $plugin = $self->{wiki}->parse_inline_plugin($1); my $info = $self->{wiki}->get_plugin_info($plugin->{command}); if($info->{TYPE} eq "paragraph"){ $self->l_plugin($plugin); } else { my @obj = $self->parse_line($line); $self->l_text(\@obj); } next; } } elsif($line =~ /^{{(.+)$/){ if ($self->{block}) { $self->{block}->{level}++; $self->{block}->{args}->[0] .= $line."\n"; next; } my $plugin = $self->{wiki}->parse_inline_plugin($1); my $info = $self->{wiki}->get_plugin_info($plugin->{command}); if($info->{TYPE} eq "block"){ unshift(@{$plugin->{args}}, ""); $self->{block} = $plugin; $self->{block}->{level} = 0; } else { my @obj = $self->parse_line($line); $self->l_text(\@obj); } next; } if($self->{block}){ if($line eq "}}"){ if ($self->{block}->{level} > 0) { $self->{block}->{level}--; $self->{block}->{args}->[0] .= $line."\n"; next; } my $plugin = $self->{block}; delete($self->{block}); $self->l_plugin($plugin); } else { $self->{block}->{args}->[0] .= $line."\n"; } next; } # PRE if($word1 eq " " || $word1 eq "\t"){ $self->l_verbatim($line); # 見出し } elsif($word3 eq "!!!"){ my @obj = $self->parse_line(substr($line,3)); $self->l_headline(1,\@obj); } elsif($word2 eq "!!"){ my @obj = $self->parse_line(substr($line,2)); $self->l_headline(2,\@obj); } elsif($word1 eq "!"){ my @obj = $self->parse_line(substr($line,1)); $self->l_headline(3,\@obj); # 項目 } elsif($word3 eq "***"){ my @obj = $self->parse_line(substr($line,3)); $self->l_list(3,\@obj); } elsif($word2 eq "**"){ my @obj = $self->parse_line(substr($line,2)); $self->l_list(2,\@obj); } elsif($word1 eq "*"){ my @obj = $self->parse_line(substr($line,1)); $self->l_list(1,\@obj); # 番号付き項目 } elsif($word3 eq "+++"){ my @obj = $self->parse_line(substr($line,3)); $self->l_numlist(3,\@obj); } elsif($word2 eq "++"){ my @obj = $self->parse_line(substr($line,2)); $self->l_numlist(2,\@obj); } elsif($word1 eq "+"){ my @obj = $self->parse_line(substr($line,1)); $self->l_numlist(1,\@obj); # 水平線 } elsif($line eq "----"){ $self->l_line(); # 引用 } elsif($word2 eq '""'){ my @obj = $self->parse_line(substr($line,2)); $self->l_quotation(\@obj); # 説明 } elsif(index($line,":")==0 && index($line,":",1)!=-1){ if(index($line,":::")==0){ $self->{dd} .= substr($line,3); next; } if(index($line,"::")==0){ if($self->{dt} ne "" || $self->{dd} ne ""){ $self->multi_explanation; } $self->{dt} = substr($line,2); $self->{dl_flag} = 1; next; } my $dt = substr($line,1,index($line,":",1)-1); my $dd = substr($line,index($line,":",1)+1); my @obj1 = $self->parse_line($dt); my @obj2 = $self->parse_line($dd); $self->l_explanation(\@obj1,\@obj2); # テーブル } elsif($word1 eq ","){ if($line =~ /,$/){ $line .= " "; } my @spl = map {/^"(.*)"$/ ? scalar($_ = $1, s/\"\"/\"/g, $_) : $_} ($line =~ /,\s*(\"[^\"]*(?:\"\"[^\"]*)*\"|[^,]*)/g); my @array; foreach my $value (@spl){ my @cell = $self->parse_line($value); push @array,\@cell; } $self->l_table(\@array); # コメント } elsif($word2 eq "//"){ # 何もない行 } else { my @obj = $self->parse_line($line); $self->l_text(\@obj); } } # 複数行の説明 $self->multi_explanation; # パース中のブロックプラグインがあった場合、とりあえず評価しておく? if($self->{block}){ $self->l_plugin($self->{block}); delete($self->{block}); } $self->end_parse; } #=============================================================================== # <p> # 複数行の説明文を処理します。 # </p> #=============================================================================== sub multi_explanation { my $self = shift; my $line = shift; if($self->{dl_flag}==1 && (index($line,":")!=0 || !defined($line))){ my @obj1 = $self->parse_line($self->{dt}); my @obj2 = $self->parse_line($self->{dd}); $self->l_explanation(\@obj1,\@obj2); $self->{dl_flag} = 0; $self->{dt} = ""; $self->{dd} = ""; } } #=============================================================================== # <p> # 1行分をパースします。parseメソッドの中から必要に応じて呼び出されます。 # </p> #=============================================================================== sub parse_line { my $self = shift; my $source = shift; my @array = (); # プラグイン if($source =~ /{{/){ my $pre = $`; my $post = $'; if($pre ne ""){ push(@array,$self->parse_line($pre)); } my $plugin = $self->{wiki}->parse_inline_plugin($post); unless($plugin){ push @array,'{{'; push @array,$self->parse_line($post); } else { my $info = $self->{wiki}->get_plugin_info($plugin->{command}); if($info->{TYPE} eq "inline"){ push @array,$self->plugin($plugin); } else { push @array,$self->parse_line("<<".$plugin->{command}."プラグインは存在しません。>>"); } if($post ne ""){ push(@array,$self->parse_line($plugin->{post})); } } # InterWikiName } elsif($self->{interwiki}->exists_interwiki($source)){ my $pre = $self->{interwiki}->{g_pre}; my $post = $self->{interwiki}->{g_post}; my $label = $self->{interwiki}->{g_label}; my $url = $self->{interwiki}->{g_url}; if($pre ne ""){ push(@array,$self->parse_line($pre)); } push @array,$self->url_anchor($url,$label); if($post ne ""){ push(@array,$self->parse_line($post)); } # ページ別名リンク } elsif($source =~ /\[\[([^\[]+?)\|([^\|\[]+?)\]\]/){ my $pre = $`; my $post = $'; my $label = $1; my $page = $2; if($pre ne ""){ push(@array,$self->parse_line($pre)); } push @array,$self->wiki_anchor($page,$label); if($post ne ""){ push(@array,$self->parse_line($post)); } # URL別名リンク } elsif($source =~ /\[([^\[]+?)\|((http|https|ftp|mailto):[a-zA-Z0-9\.,%~^_+\-%\/\?\(\)!\$&=:;\*#\@']*)\]/ || $source =~ /\[([^\[]+?)\|(file:[^\[\]]*)\]/ || $source =~ /\[([^\[]+?)\|((\/|\.\/|\.\.\/)+[a-zA-Z0-9\.,%~^_+\-%\/\?\(\)!\$&=:;\*#\@']*)\]/){ my $pre = $`; my $post = $'; my $label = $1; my $url = $2; if($pre ne ""){ push(@array,$self->parse_line($pre)); } if(index($url,'"') >= 0 || index($url,'><') >= 0 || index($url, 'javascript:') >= 0){ push @array,$self->parse_line("<<不正なリンクです。>>"); } else { push @array,$self->url_anchor($url,$label); } if($post ne ""){ push(@array,$self->parse_line($post)); } # URLリンク } elsif($source =~ /(http|https|ftp|mailto):[a-zA-Z0-9\.,%~^_+\-%\/\?\(\)!\$&=:;\*#\@']*/ || $source =~ /(file:[^\[\]]*)/){ my $pre = $`; my $post = $'; my $url = $&; if($pre ne ""){ push(@array,$self->parse_line($pre)); } if(index($url,'"') >= 0 || index($url,'><') >= 0 || index($url, 'javascript:') >= 0){ push @array,$self->parse_line("<<不正なリンクです。>>"); } else { push @array,$self->url_anchor($url); } if($post ne ""){ push(@array,$self->parse_line($post)); } # ページリンク } elsif($source =~ /\[\[([^\|]+?)\]\]/){ my $pre = $`; my $post = $'; my $page = $1; if($pre ne ""){ push(@array,$self->parse_line($pre)); } push @array,$self->wiki_anchor($page); if($post ne ""){ push(@array,$self->parse_line($post)); } # 任意のURLリンク } elsif($source =~ /\[([^\[]+?)\|(.+?)\]/){ my $pre = $`; my $post = $'; my $label = $1; my $url = $2; if($pre ne ""){ push(@array,$self->parse_line($pre)); } if(index($url,'"') >= 0 || index($url,'><') >= 0 || index($url, 'javascript:') >= 0){ push @array,$self->parse_line("<<不正なリンクです。>>"); } else { # URIを作成 my $wiki = $self->{wiki}; my $uri = $wiki->config('server_host'); if($uri eq ""){ $uri = $wiki->get_CGI()->url(-path_info => 1); } else { $uri = $uri . $wiki->get_CGI->url(-absolute => 1) . $wiki->get_CGI()->path_info(); } push @array,$self->url_anchor($uri."/../".$url, $label); } if($post ne ""){ push(@array,$self->parse_line($post)); } # ボールド、イタリック、取り消し線、下線 } elsif($source =~ /((''')|('')|(==)|(__))(.+?)(\1)/){ my $pre = $`; my $post = $'; my $type = $1; my $label = $6; if($pre ne ""){ push(@array,$self->parse_line($pre)); } if($type eq "'''"){ push @array,$self->bold($label); } elsif($type eq "__"){ push @array,$self->underline($label); } elsif($type eq "''"){ push @array,$self->italic($label); } elsif($type eq "=="){ push @array,$self->denialline($label); } if($post ne ""){ push(@array,$self->parse_line($post)); } # キーワード } elsif($self->{keyword}->exists_keyword($source)){ my $pre = $self->{keyword}->{g_pre}; my $post = $self->{keyword}->{g_post}; my $label = $self->{keyword}->{g_label}; my $url = $self->{keyword}->{g_url}; my $page = $self->{keyword}->{g_page}; if($pre ne ""){ push(@array,$self->parse_line($pre)); } if(defined($url) && $url ne ""){ push @array,$self->url_anchor($url,$label); } else { push @array,$self->wiki_anchor($page,$label); } if($post ne ""){ push(@array,$self->parse_line($post)); } # WikiName } elsif($self->{wiki}->config('wikiname')==1 && $source =~ /[A-Z]+?[a-z]+?([A-Z]+?[a-z]+)+/){ my $pre = $`; my $post = $'; my $page = $&; if($pre ne ""){ push(@array,$self->parse_line($pre)); } push @array,$self->wiki_anchor($page); if($post ne ""){ push(@array,$self->parse_line($post)); } # エラーメッセージ } elsif($source =~ /(<<)(.+?)(>>)/){ my $pre = $`; my $post = $'; my $label = $2; if($pre ne ""){ push(@array,$self->parse_line($pre)); } push @array,$self->error($label); if($post ne ""){ push(@array,$self->parse_line($post)); } } else { push @array,$self->text($source); } return @array; } #=============================================================================== # <p> # パースを開始前に呼び出されます。 # サブクラスで必要な処理がある場合はオーバーライドしてください。 # </p> #=============================================================================== sub start_parse {} #=============================================================================== # <p> # パース終了後に呼び出されます。 # サブクラスで必要な処理がある場合はオーバーライドしてください。 # </p> #=============================================================================== sub end_parse {} #=============================================================================== # <p> # URLアンカにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub url_anchor {} #=============================================================================== # <p> # ページ名アンカにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub wiki_anchor {} #=============================================================================== # <p> # イタリックにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub italic {} #=============================================================================== # <p> # ボールドにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub bold {} #=============================================================================== # <p> # 下線にマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub underline {} #=============================================================================== # <p> # 打ち消し線にマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub denialline {} #=============================================================================== # <p> # プラグインにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub plugin {} #=============================================================================== # <p> # テキストにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub text{} #=============================================================================== # <p> # 項目にマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_list {} #=============================================================================== # <p> # 番号付き項目にマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_numlist {} #=============================================================================== # <p> # 見出しにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_headline {} #=============================================================================== # <p> # PREタグにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_verbatim {} #=============================================================================== # <p> # 水平線にマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_line {} #=============================================================================== # <p> # 特になにもない行にマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_text {} #=============================================================================== # <p> # 説明にマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_explanation {} #=============================================================================== # <p> # 引用にマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_quotation {} #=============================================================================== # <p> # パラグラフの区切りにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_paragraph {} #=============================================================================== # <p> # テーブルにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_table {} #=============================================================================== # <p> # パラグラフプラグインにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_plugin {} #=============================================================================== # <p> # 画像にマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub l_image {} #=============================================================================== # <p> # エラーメッセージにマッチした場合に呼び出されます。 # サブクラスにて処理を実装します。 # </p> #=============================================================================== sub error {} 1;