<?php
/*
 * framework-spider
 * spider/tags/Foreach.class.php
 * 
 * CopyRight(C)Framework-Spider Developer Team. 2010. All Right Reserved. 
 * URL         : http://sourceforge.jp/projects/frameworkspider/
 * Mail        : frameworkspider-dev@lists.sourceforge.jp
 * Auther      : Masanori Nakashima
 * Modifier    : Masanori Nakashima
 * Last Updated: 2010.06.23
 * 
 */
require_once(dirname(__FILE__).DIRECTORY_SEPARATOR.'TagBase.class.php');
/**
 * HTML用変換タグ:foreachタグクラス
 * 
 * $requestの属性に登録された配列値に対してforeach処理をするタグの変換クラスです。
 * 開始タグのオプションとして以下の値をとることができます。
 * 
 * + キーを伴うループを実現したい場合： foreach( .. as key => value )形式
 * 1番目：配列の属性名
 * 2番目：配列のキー
 * 3番目：配列の値
 * 
 * + キーを伴なわないループを実現したい場合： foreach( .. as value )形式
 * 1番目：配列の属性名
 * 2番目：配列の値
 * 
 * このタグはループ箇所の最後を必ず{/foreach}タグで閉じる必要があります。
 * 
 * 記述例）
 * {foreach:attribute_name key value}
 *   {write:value}<br />
 * {/foreach}
 * 
 * @package spider spiderのコアパッケージ
 * @version 1.2.00
 * @copyright Copyright &copy; 2008, <m_nakashima@users.sourceforge.jp> http://sourceforge.jp/projects/shopformer/
 * @author  m.nakashima <m_nakashima@users.sourceforge.jp>
 * @since PHP 4.3
 */
class spider_tags_Foreach extends spider_tags_TagBase {

	/**
	 * コンストラクタ
	 */
	function spider_tags_Foreach() {
		$this->tag_name	= 'foreach';
	}
	/**
	 * コンバートメソッド
	 */
	function convert( &$result_strings, &$build_information ){
		parent::convert( $result_strings, $build_information );
		$result_strings = str_replace( '{end-foreach}', '<?php } } else { echo \'[target attribute is not array!]\'; } ?>', $result_strings );
		$result_strings = str_replace( '{/foreach}', '<?php } } else { echo \'[target attribute is not array!]\'; } ?>', $result_strings );
	}
	/**
	 * 個々のタグ文字列の変換後文字列を取得します
	 */
	function getConvertedStrings( &$result_strings, &$build_information, $option_array=array(), $valiable_counter=0 ) {
		// ループに必要なオプションを取り出し(順序固定)
		$org_attribute_name		= array_shift( $option_array );
		$loop_key_name			= array_shift( $option_array );
		$loop_val_name			= array_shift( $option_array );
		
		// 属性名指定がない場合はエラー表示で返す
		if( strlen( $org_attribute_name ) == 0 ) {
			return 'echo \'[error: "foreach" need parameters!]\'; if(false){if(false) {';
		}
		// ループのキー名と値名の取り出し
		if ( strlen( $loop_key_name ) > 0 && strlen( $loop_val_name ) > 0 ) {
			// キー名と値名の両方が指定されている場合には何もしない
		} else if ( strlen( $loop_key_name ) > 0 ) {
			// 片方しか入力がない場合、値名がひとつめでキー名は自動
			$loop_val_name	= $loop_key_name;
			$loop_key_name	= 'key';
		} else {
			// 両方とも入力がない場合はエラー表示
			return 'echo \'[error: "foreach" need parameters!]\'; if(false){if(false) {';
		}
		
		// ループカウンタ数字名の決定
		$counter_name = str_replace('::','_',str_replace('.','_',$org_attribute_name)).'_loop_counter';
		// 実際にループする変数名
		$real_loop_var_name	= '$tmp_array_'.sprintf('%08d',$valiable_counter);
		// 置換文字列作成
		$rep_string	= '<?php ';
		if( preg_match( '/\\-\\>/',$org_attribute_name ) > 0 || preg_match('/\\[.+\\]/',$org_attribute_name) > 0 ) {
			// オブジェクトや配列の指定子が含まれている場合
			$real_var_string	= $this->getAvailableVarName( $org_attribute_name );
			$rep_string	.= $real_loop_var_name.'='.$real_var_string.'; ';
		} else if ( strpos( $org_attribute_name, '::' ) > 0 ) {
			// 従来の記述 属性名を::で分岐
			list( $attribute_name, $member_name )	= explode( '::', $org_attribute_name );
			// 対象属性値
			$target_attribute_name		= '$GLOBALS[\''.$attribute_name.'\']';
			$rep_string	.= $real_loop_var_name.'='.$target_attribute_name.'; ';
			if( strlen( $member_name ) > 0 ) {
				// メンバ指定がある場合は属性値がオブジェクトか配列かで分岐処理
				$rep_string	.= 'if( is_array('.$target_attribute_name.') ){ ';
				$rep_string	.= $real_loop_var_name.'='.$target_attribute_name.'[\''.$member_name.'\']; ';
				if( preg_match('/^[a-zA-Z\\_][0-9a-zA-Z\\_]*?/',$member_name) > 0 ) {
					$rep_string	.= ' } else if( is_object('.$target_attribute_name.') ) {';
					$rep_string	.= $real_loop_var_name.'='.$target_attribute_name.'->'.$member_name.'; ';
				}
				$rep_string	.= ' } ';
			}
		} else {
			// 属性名を特に区切る必要がない場合は
			$target_attribute_name		= '$GLOBALS[\''.$org_attribute_name.'\']';
			$rep_string	.= $real_loop_var_name.'='.$target_attribute_name.'; ';
		}
		$rep_string	.= 'if( is_array('.$real_loop_var_name.') ) { ';
		$rep_string	.= '$GLOBALS[\''.$counter_name.'\']=0; ';
		$rep_string	.= 'foreach ( '.$real_loop_var_name
			.' as $GLOBALS[\''.$loop_key_name.'\'] => $GLOBALS[\''.$loop_val_name.'\'] ) {';
		$rep_string	.= '$GLOBALS[\''.$counter_name.'\']++; ';
		// 下位互換の為ループごとに属性設定
		$rep_string	.= '$request_object->setAttribute("'.$loop_key_name.'",$GLOBALS[\''.$loop_key_name.'\']); ';
		$rep_string	.= '$request_object->setAttribute("'.$loop_val_name.'",$GLOBALS[\''.$loop_val_name.'\']); ';
		$rep_string	.= '$request_object->setAttribute("'.$counter_name.'",$GLOBALS[\''.$counter_name.'\']); ';
		// 置換文字列終了
		$rep_string	.= ' ?>';
		
		return $rep_string;
	}
	/**
	 * 第一引数のループ対象指定文字列をPHP実行コードに変換して返します。
	 */
	function getAvailableVarName( $org_attribute_name ) {
		if( strlen( $org_attribute_name ) > 0 ) {
			// オブジェクトや配列の指定子が含まれている場合全ての属性値候補を取り出し
			$attribute_proposed_array	= $this->getReverseSortedProposeAttributeHash( $org_attribute_name );
			// 属性名候補を変換する
			$real_var_string	= $org_attribute_name;
			foreach( $attribute_proposed_array as $attribute_name => $prev_char ) {
				if( preg_match('/^[0-9]+$/',$attribute_name) > 0 ) {
					// 属性名候補が数字のみの場合変換しない
					continue;
				} else if( preg_match('/^\\$[^0-9][.]*$/',$attribute_name) > 0 ) {
					// 属性名候補が$で始まる変数名なら変換しない
					continue;
				} else if( preg_match('/^\\\'[^\\\']*\\\'$/',$attribute_name) > 0
					|| preg_match('/^\\"[^\\"]*\\"$/',$attribute_name) > 0 ) {
					// 属性名候補がクォーテーションで囲まれていたら
					if( '>' == $prev_char ) {
						// 直前の文字がオブジェクトメンバ指定子の場合クォーテーションを除去して確認
						$name	= preg_replace('/^\\\'/','',$attribute_name);
						$name	= preg_replace('/\\\'$/','',$name);
						$name	= preg_replace('/^\\"/','',$name);
						$name	= preg_replace('/\\"$/','',$name);
						if( preg_match('/^[^0-9][0-9a-zA-Z\\_]*$/', $name ) > 0 ) {
							// メンバ名として有効そうであればクォーテーションを除去した文字列へ変換
							$real_var_string	= str_replace($attribute_name,$name,$real_var_string);
						} else {
							// 暫定：有効なメンバでない場合使い方が正しくないのでPHPのコアエラーとし何もしない
							continue;
						}
					} else {
						// 直前の文字列がオブジェクトメンバ指定子でない場合は変換しない
						continue;
					}
				} else {
					// それ以外の場合はグローバル変数名として変換
					$replacew	= '$GLOBALS[\''.$attribute_name.'\']';
					$real_var_string	= str_replace($attribute_name,$replacew,$real_var_string);
				}
			}
			return $real_var_string;
		}
		return '';
	}
	/**
	 * 属性名が配列要素指定やオブジェクトメンバ指定子を含む場合に変換候補属性名文字列を
	 * 文字列長の長い順番にハッシュにして返します
	 */
	function getReverseSortedProposeAttributeHash( $org_attribute_name ) {
		$attribute_sort_array		= array();
		if( preg_match( '/\\-\\>/',$org_attribute_name ) > 0 || preg_match('/\\[.+\\]/',$org_attribute_name) > 0 ) {
			$attribute_proposed_array	= array();
			$pos=0;
			for( $i=0; $i<strlen($org_attribute_name); $i++ ) {
				$char	= substr($org_attribute_name,$i,1);
				if( '-' == $char || '>' == $char
					|| '(' == $char || ')' == $char
					|| '[' == $char || ']' == $char ) {
					// 演算子が登場したらひとつ前までを文字列切り出し
					$str	= trim(substr( $org_attribute_name, $pos, $i-$pos ));
					$pos	= $i+1;
					if( '(' != $char && strlen( $str ) > 0 ) {
						// 関数名でなければ変換対象として評価
						$attribute_sort_array[$str]		= strlen($str);
						$attribute_proposed_array[$str]	= $char;
					}
				}
			}
			// ループ最後のポジションから最後の要素を切り出し
			$str	= trim(substr( $org_attribute_name, $pos, $i-$pos ));
			if( strlen( $str ) > 0 ) {
				$attribute_sort_array[$str]	= strlen($str);
			}
			// 変数配列を文字列の長い順にソート
			arsort($attribute_sort_array);
			// 直前の文字を代入
			foreach( $attribute_sort_array as $key => $value ) {
				$attribute_sort_array[$key]	= $attribute_proposed_array[$key];
			}
		}
		return $attribute_sort_array;
	}
}
?>