################################################################################
# <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-&gt;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-&gt;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, $source) = @_;

	return () if (not defined $source);

	my @array = ();
	my $pre   = q{};
	my @parsed = ();

	# $source が空になるまで繰り返す。
	SOURCE:
	while ($source ne q{}) {

		# どのインライン Wiki 書式の先頭にも match しない場合
		if (!($source =~ /^(.*?)((?:{{|\[\[?|https?:|mailto:|f(?:tp:|ile:)|'''?|==|__|<<).*)$/)) {
			# キーワード検索・置換処理のみ実施して終了する
			push @array, $self->_parse_line_keyword($pre . $source);
			return @array;
		}

		$pre   .= $1;	# match しなかった先頭部分は溜めておいて後で処理する
		$source = $2;	# match 部分は後続処理にて詳細チェックを行う
		@parsed = ();

		# プラグイン
		if ($source =~ /^{{/) {
			$source = $';
			my $plugin = $self->{wiki}->parse_inline_plugin($source);
			unless($plugin){
				push @parsed, '{{';
				push @parsed, $self->parse_line($source);
			} else {
				my $info = $self->{wiki}->get_plugin_info($plugin->{command});
				if($info->{TYPE} eq "inline"){
					push @parsed, $self->plugin($plugin);
				} else {
					push @parsed, $self->parse_line("<<".$plugin->{command}."プラグインは存在しません。>>");
				}
				if ($source ne "") {
					$source = $plugin->{post};
				}
			}
		}

		# InterWikiName
		elsif ($self->{interwiki}->exists_interwiki($source)) {
			my $label = $self->{interwiki}->{g_label};
			my $url   = $self->{interwiki}->{g_url};
			$source = $self->{interwiki}->{g_post};
			push @parsed, $self->url_anchor($url, $label);
		}

		# ページ別名リンク
		elsif ($source =~ /^\[\[([^\[]+?)\|([^\|\[]+?)\]\]/) {
			my $label = $1;
			my $page  = $2;
			$source = $';
			push @parsed, $self->wiki_anchor($page, $label);
		}

		# URL別名リンク
		elsif ($source
			=~ /^\[([^\[]+?)\|((?:http|https|ftp|mailto):[a-zA-Z0-9\.,%~^_+\-%\/\?\(\)!&=:;\*#\@'\$]*)\]/
			|| $source =~ /^\[([^\[]+?)\|(file:[^\[\]]*)\]/
			|| $source
			=~ /^\[([^\[]+?)\|((?:\/|\.\/|\.\.\/)+[a-zA-Z0-9\.,%~^_+\-%\/\?\(\)!&=:;\*#\@'\$]*)\]/
			)
		{
			my $label = $1;
			my $url   = $2;
			$source = $';
			if (   index($url, q{"}) >= 0
				|| index($url, '><') >= 0
				|| index($url, 'javascript:') >= 0)
			{
				push @parsed, $self->parse_line('<<不正なリンクです。>>');
			}
			else {
				push @parsed, $self->url_anchor($url, $label);
			}
		}

		# URLリンク
		elsif ($source
			=~ /^(?:https?|ftp|mailto):[a-zA-Z0-9\.,%~^_+\-%\/\?\(\)!&=:;\*#\@'\$]*/
			|| $source =~ /^file:[^\[\]]*/)
		{
			my $url = $&;
			$source = $';
			if (   index($url, q{"}) >= 0
				|| index($url, '><') >= 0
				|| index($url, 'javascript:') >= 0)
			{
				push @parsed, $self->parse_line('<<不正なリンクです。>>');
			}
			else {
				push @parsed, $self->url_anchor($url);
			}
		}

		# ページリンク
		elsif ($source =~ /^\[\[([^\|]+?)\]\]/) {
			my $page = $1;
			$source = $';
			push @parsed, $self->wiki_anchor($page);
		}

		# 任意のURLリンク
		elsif ($source =~ /^\[([^\[]+?)\|(.+?)\]/) {
			my $label = $1;
			my $url   = $2;
			$source = $';
			if (   index($url, q{"}) >= 0
				|| index($url, '><') >= 0
				|| index($url, 'javascript:') >= 0)
			{
				push @parsed, $self->parse_line('<<不正なリンクです。>>');
			}
			else {

				# URIを作成
				my $wiki = $self->{wiki};
				my $uri  = $wiki->config('server_host');
				if ($uri eq q{}) {
					$uri = $wiki->get_CGI()->url(-path_info => 1);
				}
				else {
					$uri
						= $uri
						. $wiki->get_CGI->url(-absolute => 1)
						. $wiki->get_CGI()->path_info();
				}
				push @parsed, $self->url_anchor($uri . '/../' . $url, $label);
			}
		}

		# ボールド、イタリック、取り消し線、下線
		elsif ($source =~ /^('''?|==|__)(.+?)\1/) {
			my $type  = $1;
			my $label = $2;
			$source = $';
			if ($type eq q{'''}) {
				push @parsed, $self->bold($label);
			}
			elsif ($type eq q{__}) {
				push @parsed, $self->underline($label);
			}
			elsif ($type eq q{''}) {
				push @parsed, $self->italic($label);
			}
			else {							   ## elsif ($type eq q{==}) {
				push @parsed, $self->denialline($label);
			}
		}

		# エラーメッセージ
		elsif ($source =~ /^<<(.+?)>>/) {
			my $label = $1;
			$source = $';
			push @parsed, $self->error($label);
		}

		# インライン Wiki 書式全体には macth しなかったとき
		else {
			# 1 文字進む。
			if ($source =~ /^(.)/) {
				$pre .= $1;
				$source = $';
			}
			
			# parse 結果を @array に保存する処理を飛ばして繰り返し。
			next SOURCE;
		}

		# インライン Wiki 書式全体に macth した後の
		# parse 結果を @array に保存する処理。

		# もし $pre が溜まっているなら、キーワードの処理を実施。
		if ($pre ne q{}) {
			push @array, $self->_parse_line_keyword($pre);
			$pre = q{};
		}

		push @array, @parsed;
	}

	# もし $pre が溜まっているなら、キーワードの処理を実施。
	if ($pre ne q{}) {
		push @array, $self->_parse_line_keyword($pre);
	}

	return @array;
}

#========================================================================
# <p>
# parse_line() から呼び出され、キーワードの検索・置換処理を行います。
# </p>
#========================================================================
sub _parse_line_keyword {
	my $self   = shift;
	my $source = shift;

	return () if (not defined $source);

	my @array = ();

	# $source が空になるまで繰り返す。
	while ($source ne q{}) {

		# キーワード
		if ($self->{keyword}->exists_keyword($source)) {
			my $pre   = $self->{keyword}->{g_pre};
			my $label = $self->{keyword}->{g_label};
			my $url   = $self->{keyword}->{g_url};
			my $page  = $self->{keyword}->{g_page};
			$source = $self->{keyword}->{g_post};
			if ($pre ne q{}) {
				push @array, $self->_parse_line_keyword($pre);
			}
			if (defined($url) && $url ne q{}) {
				push @array, $self->url_anchor($url, $label);
			} else {
				push @array, $self->wiki_anchor($page, $label);
			}

		}

		# WikiName
		elsif ($self->{wiki}->config('wikiname') == 1 && $source =~ /[A-Z]+?[a-z]+?(?:[A-Z]+?[a-z]+)+/) {
			my $pre  = $`;
			my $page = $&;
			$source  = $';
			if ($pre ne q{}) {
				push @array, $self->_parse_line_keyword($pre);
			}
			push @array, $self->wiki_anchor($page);
		}

		# キーワードも WikiName も見つからなかったとき
		else {
			push @array, $self->text($source);
			return @array;
		}
	}
	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;