<?php
// PukiWiki - Yet another WikiWikiWeb clone.
//
// $Id: sonots $

/*
*説明 [#m17ed6bb]
[[contents>PukiWiki/1.4/マニュアル/プラグイン/c#vd4dabcd]] プラグインは実際のところプラグインではなく、本体組み込みの機能です。
拡張がしにくいのでプラグイン化しました。
アイデアの多くは[[自作プラグイン/ls2_1.inc.php]]からきています。ということで名前はcontents2_1 です。

MenuBar に
 #contents2_1(fromhere=false,hierarchy=false)
としておくのも便利かもしれません。hierarchy はお好みで。

contents よりも負荷がかかることは承知しておいてください。
とはいえ ls2 の title オプションとやっていることは同じです。対象が１ページなだけそれよりはましです。

関連:([[org:欲しいプラグイン/170]])([[org:欲しいプラグイン/91]])([[org:続・質問箱/221]])([[org:続・質問箱/220]])([[org:質問箱/287]])([[org:欲しいプラグイン/125]])~
([[org:質問箱/59]])([[org:続・質問箱/447]])([[org:続・質問箱/306]])
*書式 [#jcb5f796]
 #contents2_1([オプション])

**オプション [#r8a06bfd]
-page=ページ名
~見出しリストを行うページを指定。デフォルトはカレントページ。
-fromhere=true|false
~#contents2_1 がのある次の行以降の見出しのみをリストする。
ファイル中の PLUGIN_CONTENTS2_1_FROMHEARE で初期値を設定できます。デフォルトは TRUE です。
注意: 現状では #contents2_1 が２つあると一番上のものだけに反応してしまいます。
page オプションでページを指定した場合は発動しません。
-hierarchy=true|false
~階層的リスト表示。hierarchy だけで hierarchy=true になります。
ファイル中の PLUGIN_CONTENTS2_1_HIERARCHY で初期値を設定できます。デフォルトは TRUE です。
-compact=true|false
~リストのレベルを調節する。hierarchy 用のオプションです。compact だけで compact=true になります。
ファイル中の PLUGIN_CONTENTS2_1_COMPACT で初期値を設定できます。デフォルトは TRUE です。
-number=\d+ ((\d+ は正規表現による表記です。例えば \d は数字のことです。))
~表示件数の指定。
注意: include ページ名の表示も１つとカウントします。
-depth=\d*[-+]?\d*((\d*[-+]?\d* は正規表現による表記です。\d は数字のことです。))
~見出しレベル指定。1 なら見出しレベル 1 のみを表示する。
2-3 のような指定も可能 (2,3 の意)。2- のように指定するとレベル 2 以上の見出し。。
2+1 のような指定も可能 (2 とそこから +1 。つまり 2,3 の意)。
注意: include オプション使用時は include ページ名がレベル１。見出しはレベル２以降になります。
現状は compact を使用していても(デフォルトで TRUE)、depth オプションで指定するレベルは変わりません。
よって見た目と指定すべきレベルが違うかもしれません。一旦 compact=false として調べたほうがよいです。
-except=正規表現
~リストしない見出しを正規表現にて指定。
ヒント： [[ereg>http://php.s3.to/man/function.ereg.html]] で判定を行います。
except=Test|sample → Test または sample を含む見出しを除く。
-include=true|false
~#include プラグインで取り込んでいるページとその見出しも扱う。
ページタイトルへのジャンプは #include がアンカーを張ってくれないと無理です。
ファイル中の PLUGIN_CONTENTS2_1_INCLUDE で初期値を設定できます。デフォルトは TRUE です。
注意: include オプションが true の場合(つまりデフォルトで)は include ページ名がレベル１。見出しはレベル２以降になります。
include オプションが false の場合は、見出しはレベル１以降になります。
*/

//見出しアンカーの書式
define('PLUGIN_CONTENTS2_1_ANCHOR_PREFIX', '#content_');

//見出しアンカーの開始番号
define('PLUGIN_CONTENTS2_1_ANCHOR_ORIGIN',0);
define('PLUGIN_CONTENTS2_1_PAGE_ANCHOR_ORIGIN',1);

//#contents2_1 が書いてある次の行以降の見出しのみをリストする(デフォルト TRUE)
define('PLUGIN_CONTENTS2_1_FROMHERE', TRUE);

//リストのレベルを調整する(デフォルト TRUE)
define('PLUGIN_CONTENTS2_1_COMPACT',TRUE);

//階層的リスト表示(デフォルト TRUE)
define('PLUGIN_CONTENTS2_1_HIERARCHY',TRUE);

//#include プラグインで取り込んでいるページの見出しも扱う(デフォルト TRUE)
define('PLUGIN_CONTENTS2_1_INCLUDE',TRUE);

function plugin_contents2_1_init()
{
	$_contents2_1_msg_err;
}

function plugin_contents2_1_convert()
{
	global $vars;

	// true or false のパラメーター
	$params = array(
		'fromhere'   => PLUGIN_CONTENTS2_1_FROMHERE,
		'compact' => PLUGIN_CONTENTS2_1_COMPACT,
		'hierarchy' => PLUGIN_CONTENTS2_1_HIERARCHY,
		'include' => PLUGIN_CONTENTS2_1_INCLUDE,
	);
	// その他の引数を持つパラメーター
	$argparams = array(
		'page'    => '',
		'depth'   => '',
		'number' => '',
		'except' => '',
	);

	$args = func_get_args();
	array_walk($args, 'plugin_contents2_1_check_params', & $params);
	array_walk($args, 'plugin_contents2_1_check_argparams', & $argparams);
	$params = array_merge($params, $argparams);

	if( $params['page'] != '' )
	{
		$page = $params['page'];
		$params['fromhere'] = FALSE;
	}
	else
	{
		$page = $vars['page'];
	}
	if (! check_readable($page, false, false ) )
	{
		return htmlspecialchars($page) . " does not exist.";
	}
	else
	{
		$params['result'] = $params['saved'] = array();
		plugin_contents2_1_get_headings($page, $params);
		return join("\n", $params['result']) . join("\n", $params['saved']);
	}
}

function plugin_contents2_1_get_headings($page, & $params, 
		$page_anchor = PLUGIN_CONTENTS2_1_PAGE_ANCHOR_ORIGIN, $fromhere = FALSE, $number_counter = 0)
{
	//global $script;
	static $_contents2_1_anchor = 0;

	$r_page = rawurlencode($page);
	$s_page = htmlspecialchars($page);
	$title  = $s_page . ' ' . get_pg_passage($page, FALSE);
	//$href   = $script . '?cmd=read&amp;page=' . $r_page;

	// すでにこのページの見出しを表示したかどうかのフラグ
	$is_done = (isset($params["page_$page"]) && $params["page_$page"] > 0);
	if (! $is_done) $params["page_$page"] = ++$_contents2_1_anchor;

	//depth オプション解析
	if($params['depth'] != '')
	{
		list($lowdepth,$highdepth) = plugin_contents2_1_depth_option_analysis($params['depth']);
	}
	//number オプション解析
	if($params['number'] != '')
	{
		if( preg_match('/^\d+$/',$params['number']) )
		{
			$number = $params['number'];
		}
	}
	//except オプション解析
	if ($params['except'])
	{
		$except = $params['except'];
	}
	
	// include ページの場合
	if ($page_anchor > 1)
	{
		// 表示済み
		if ($is_done) {
			return $page_anchor - 1;
		}
		
		// include ページ名のレベルは１
		$level = 1; 
		// number 判定。include ページ名の表示も１つと数える。
		if ($number != '' && $number_counter >= $number)
		{
			//nothing
		}
		elseif($except != '' && ereg($except,$page) )
		{
			//nothing
		}
		// depth 判定。
		elseif($lowdepth != '' && $level < $lowdetph)
		{
			//nothing
		}
		elseif ($highdepth != '' && $level > $highdepth)
		{
			//nothing
		}
		else
		{
			// ページ名の表示
			plugin_contents2_1_list_push($params, $level);
			// 現在は #include プラグインにはアンカーはつかないのでこのアンカーリンクは機能しない。
			// content_2 のようにつくようにちょっと改造すれば機能します。
			$id = PLUGIN_CONTENTS2_1_ANCHOR_PREFIX . $page_anchor;
			$ret .= '<li><a id="list_' . $params["page_$page"] . '" href="' . $id .
				'" title="' . $title . '">' . $s_page . '</a>';
			array_push($params['result'], $ret);
			
			$number_counter++;
		}
	}

	$anchor = PLUGIN_CONTENTS2_1_ANCHOR_ORIGIN;
	$matches = array();
	foreach (get_source($page) as $line)
	{
		// fromhere 判定。まず #contents2_1 がみつかるまで他の処理をしない
		if ($params['fromhere'] && ! $fromhere)
		{
			// include ページの場合、その内部の #contents2_1 を探すことはしない。
			// なぜならそれは明らかに違う #contents2_1 だから。
			// ただ page_anchor のために #include を辿って #include の数は数えないといけない。
			if ($page_anchor > 1)
			{
				if ($params['include'] &&
					preg_match('/^#include\((.+)\)/', $line, $matches) &&
					is_page($matches[1]))
				{
					$page_anchor = plugin_contents2_1_get_headings($matches[1], $params, $page_anchor + 1, $fromhere, $number_counter);
				}
			}
			else
			{
				$fromhere = preg_match('/^#contents2\_1/', $line, $matches);
			}
			continue;
		}

		// fromhere が見つかった
		// 見出し検出
		if (preg_match('/^(\*{1,3})/', $line, $matches))
		{
			// アンカー文字列をつくる。$anchor++ が重要。
			$id    = PLUGIN_CONTENTS2_1_ANCHOR_PREFIX . $page_anchor . '_' . $anchor++;

			// fromhere がまだ見つかっていなければ $anchor++ だけして continue。
			if ($params['fromhere'] && ! $fromhere) continue;
			
			// include したページタイトルのレベルが１なので、見出しレベルは２以降
			$level = strlen($matches[1]) + 1;
						
			// except 判定
			if ($except != '')
			{
				if (ereg($except,$line)) continue;
			}
			
			// depth 判定。
			if($lowdepth != '' && $level < $lowdetph)
			{
				continue;
			}
			elseif ($highdepth != '' && $level > $highdepth)
			{
				continue;
			}
			// number 判定。
			if ($number != '')
			{
				if ( $number_counter >= $number )
				{
					break;
				}
				$number_counter++;
			}
			
			// include オプション時は include ページ名がレベル１。見出しはレベル２以降となる。微妙。
			if ($params['include'] )
			{
				$level += 1;
			}
			
			// $line は 'remove footnotes and HTML tags' される。見出し行のアンカーが返されるがいらない。
			make_heading($line);
			plugin_contents2_1_list_push($params, $level);
			$ret = '<li><a href="' . $id . '">' . $line . '</a>';
			
			array_push($params['result'],$ret);
		}
		// include 検出
		elseif ($params['include'] &&
			preg_match('/^#include\((.+)\)/', $line, $matches) &&
			is_page($matches[1]))
		{
			$page_anchor = plugin_contents2_1_get_headings($matches[1], $params, $page_anchor + 1, $fromhere, $number_counter);
		}
	}
	return $page_anchor;
}

//リスト構造を構築する。<ul> と </li></ul> を適宜挿入する。
function plugin_contents2_1_list_push(& $params, $level)
{
	global $_ul_left_margin, $_ul_margin, $_list_pad_str;

	$result = & $params['result']; // バッファ。これを出力することになる。
	$saved  = & $params['saved'];  // 閉じなければいけない文だけ </ul> をたくわえておく。
	$open   = '<ul%s>';
	$close  = '</li></ul>';

	if (! $params['hierarchy'])
	{
		// 初期化がここにあるのはうれしくないが、まとめておきたかった。
		if ( count($saved) < 1 )
		{
			$left = $_ul_margin;
			$level = 1;
			$str = sprintf($_list_pad_str, $level, $left, $left);
			array_push($result, sprintf($open, $str));
			array_unshift($saved, $close);
		}
		else
		{
			array_push($result, '</li>');
		}
		return;
	}
	
	while (count($saved) > $level || (! empty($saved) && $saved[0] != $close))
		array_push($result, array_shift($saved));

	$margin = $level - count($saved);

	// count($saved)を増やす
	while (count($saved) < ($level - 1)) array_unshift($saved, '');

	if (count($saved) < $level)
	{
		array_unshift($saved, $close);

		$left = ($level == $margin) ? $_ul_left_margin : 0;

		if ($params['compact'])
		{
			$left  += $_ul_margin;   // マージンを固定
			$level -= ($margin - 1); // レベルを修正
		} 
		else
		{
			$left += $margin * $_ul_margin;
		}
		
		$str = sprintf($_list_pad_str, $level, $left, $left);
		array_push($result, sprintf($open, $str));
	}
	else
	{
		array_push($result, '</li>');
	}
}

//オプション \d?[+-]?\d? を解析。
function plugin_contents2_1_depth_option_analysis($arg)
{
	$low=0;
	$high=0;
	$just='';
	if( !preg_match('/^\d*\-?\d*$/',$arg) or $arg == '' )
	{
		return;
	}

	if(substr_count($arg,"-")) // \d-\d の場合
	{
		list($low,$high)=split("-",$arg,2);
	}
	elseif(substr_count($arg,"+")) // \d+\d の場合
	{
		list($low,$high)=split("+",$arg,2);
		$high += $low;
	}
	else // \d だけの場合
	{
		$low = $high = $arg;
	}
	return array($low,$high);
}

// true or false の値を持つオプションを解析する
function plugin_contents2_1_check_params($value, $key, & $params)
{
	// $value に depth=2-3 や hierarchy のような値が入る。実質 $key は意味なし。
	// trim はあえてしていない。
	if ($value == '') return;

	list($key,$val)=split("=", $value);
	if( isset($params[$key]) )
	{
		if ( $val == '' || $val == "true" )
		{
			$params[$key] = TRUE;
		}
		elseif ( $val = "false" )
		{
			$params[$key] = FALSE;
		}
	}
}

// その他の値を持つオプションを解析する
function plugin_contents2_1_check_argparams($value, $key, & $params)
{
	// $value に depth=2-3 や hierarchy のような値が入る。実質 $key は意味なし。
	// trim はあえてしていない。
	if ($value == '') return;

	list($key,$val)=split("=", $value);
	if( isset($params[$key]) )
	{
		if ( $val != '' )
		{
			//$val = htmlspecialchars($val);
			$params[$key] = $val;
		}
	}
}
?>
