<?php
/**
 * p2 - 文字列操作クラス
 * staticメソッドで利用する
 */
class StrCtl
{
    /**
     * フォームから送られてきたワードをマッチ関数に適合させる
     *
     * @static
     * @access  public
     * @return  string  $word_fm  適合パターン。SJISで返す。
     */
    function wordForMatch($word, $method = 'regex')
    {
        $word_fm = $word;
        
        // 「そのまま」でなければ、全角空白を半角空白に矯正
        if ($method != 'just') {
            $word_fm = mb_convert_kana($word_fm, 's');
        }
        
        $word_fm = trim($word_fm);
        $word_fm = hs($word_fm);
        
        if (in_array($method, array('and', 'or', 'just'))) {
            // preg_quote()で2バイト目が0x5B("[")の"ー"なども変換されてしまうので
            // UTF-8にしてから正規表現の特殊文字をエスケープ
            $word_fm = mb_convert_encoding($word_fm, 'UTF-8', 'SJIS-win');
            if (P2_MBREGEX_AVAILABLE == 1) {
                $word_fm = preg_quote($word_fm);
            } else {
                $word_fm = preg_quote($word_fm, '/');
            }
            $word_fm = mb_convert_encoding($word_fm, 'SJIS-win', 'UTF-8');
            
        // 他、regex（正規表現）なら
        } else {
            $word_fm = str_replace('/', '\/', $word_fm);
            
            $tmp_pattern = '/' . mb_convert_encoding($word_fm, 'UTF-8', 'SJIS-win') . '/u';
            if (false === @preg_match($tmp_pattern, '.')) {
                P2Util::pushInfoHtml(
                    sprintf(
                        'p2 warning: フィルタ語句の正規表現に誤りがあります "%s"',
                        hs($word_fm)
                    )
                );
                $word_fm = '';
            }
            
            if (P2_MBREGEX_AVAILABLE == 0) {
                $word_fm = str_replace('/', '\/', $word_fm);
            }
            // 末尾のワイルドカードは除去してしまう
            $word_fm = rtrim($word_fm, '.+*');
        }
        return $word_fm;
    }

    /**
     * マルチバイト対応で正規表現マッチ判定する
     *
     * @static
     * @access  public
     * @param   string  $pattern    マッチ文字列。SJISで入ってくる。
     * @param   string  $targetHtml 検索対象文字列。SJISで入ってくる。HTML
     * @param   int     $zenhan     1:全角/半角の区別を完全になくす
     *                             （これをオンにすると、メモリの使用量が倍くらいになる。速度負担はそれほどでもない）
     *                              2:全角/半角を ある程度 区別なくマッチ
     *                              0:全角/半角を 区別しない
     * @return  string|false      マッチしたらマッチ文字列を、マッチしなかったらfalseを返す
     */
    function filterMatch($pattern, $targetHtml, $zenhan = 1)
    {
        global $res_filter;
        
        if ($res_filter['method'] == 'regex') {
            $pattern = StrCtl::replaceRegexAnyChar($pattern);
        }
        
        // IDフィルタリング時は、全角半角を常に区別しない
        if ($res_filter['field'] == 'id' && $res_filter['method'] == 'just') {
            $zenhan = 0;
        }
        
        if ($zenhan == 1) {
            // 全角/半角を 完全に 区別なくマッチ
            $pattern    = StrCtl::getPatternToHan($pattern);
            $targetHtml = StrCtl::getPatternToHan($targetHtml, true);
        
        } elseif ($zenhan == 2) {
            // 全角/半角を ある程度 区別なくマッチ
            $pattern = StrCtl::getPatternZenHan($pattern); // 正規表現パターン
        }
        
        // HTML要素にマッチさせないための否定先読みパターンを付加
        $pattern = $pattern . '(?![^<]*>)';

        if (P2_MBREGEX_AVAILABLE == 1) {
            $result = mb_eregi($pattern, $targetHtml, $matches);    // None|Error:FALSE
        } else {
            // UTF-8に変換してから処理する
            $pattern = str_replace('/', '\/', $pattern);
            $pattern_utf8 = '/' . mb_convert_encoding($pattern, 'UTF-8', 'SJIS-win') . '/iu';
            $target_utf8 = mb_convert_encoding($targetHtml, 'UTF-8', 'SJIS-win');
            $result = preg_match($pattern_utf8, $target_utf8, $matches);    // None:0, Error:FALSE
            //$result = mb_convert_encoding($result, 'SJIS-win', 'UTF-8');
        }
        
        if (!$result) {
            return false;
        }
        return $matches[0];
    }
    
    /**
     * マルチバイト対応でHTML中の検索語句をマーキングする
     *
     * @static
     * @access  public
     * @param   string  $pattern    マッチ文字列。SJISで入ってくる。あらかじめhtmlspecialchars()されていること。
     * @param   string  $targetHtml 置換対象文字列。SJISで入ってくる。HTML。
     * @return  string  HTML
     */
    function filterMarking($pattern, $targetHtml, $marker = '<b class="filtering">\\0</b>')
    {
        global $res_filter;
        
        if ($res_filter['method'] == 'regex') {
            $pattern = StrCtl::replaceRegexAnyChar($pattern);
        }
        
        // 全角/半角を（ある程度）区別なくマッチ
        $pattern = StrCtl::getPatternZenHan($pattern); // 正規表現パターン

        // HTML要素にマッチさせないための否定先読みパターンを付加
        $pattern = $pattern . '(?![^<]*>)';

        $result = false;
        if (P2_MBREGEX_AVAILABLE == 1) {
            $result = mb_eregi_replace($pattern, $marker, $targetHtml);    // Error => FALSE
        } else {
            // UTF-8に変換してから処理する
            $pattern = str_replace('/', '\/', $pattern);
            $pattern_utf8 = '/' . mb_convert_encoding($pattern, 'UTF-8', 'SJIS-win') . '/iu';
            $target_utf8 = mb_convert_encoding($targetHtml, 'UTF-8', 'SJIS-win');
            $result = preg_replace($pattern_utf8, $marker, $target_utf8);
            $result = mb_convert_encoding($result, 'SJIS-win', 'UTF-8');
        }

        if ($result === false) {
            return $targetHtml;
        }
        return $result;
    }
    
    /**
     * 正規表現中の「.」を（タグを含まないように）置換する
     *
     * @static
     * @access  private
     * @param   string  $regex
     * @return  string
     */
    function replaceRegexAnyChar($regex, $replace = '[^<>]')
    {
        static $cache_ = array();
        
        // 一応キャッシュしておく
        if (array_key_exists($regex, $cache_)) {
            return $cache_[$regex];
        }
        
        $len = strlen($regex);
        $new = '';
        $esc = false;
        $cls = false;

        for ($i = 0; $i < $len; $i++) {
            $c = $regex[$i];

            if ($c == '\\') {
                $esc = !$esc;
                $new .= '\\';
                continue;
            }

            switch ($c) {
            case '.':
                if (!$esc && !$cls) {
                    $new .= $replace;
                } else {
                    $new .= '.';
                }
                break;

            case '[':
                if (!$esc && !$cls) {
                    $cls = true;
                }
                $new .= '[';
                break;

            case ']':
                if (!$esc && $cls) {
                    $cls = false;
                }
                $new .= ']';
                break;

            default:
                $new .= $c;
            }

            $esc = false;
        }
        
        $cache_[$regex] = $new;
        return $new;
    }

    /**
     * 全角/半角を（ある程度）区別なくパッチするための正規表現パターンを得る
     * （注意：正規表現の特殊文字も一緒くたに処理してしまうという欠点が解消されていない。\d \ｄ）
     *
     * @static
     * @access  private
     * @return  string
     */
    function getPatternZenHan($pattern)
    {
        $petterns = array();
        
        $pattern_han = StrCtl::getPatternToHan($pattern);
        if ($pattern != $pattern_han) {
            $petterns[] = $pattern_han;
        }
        $pattern_zen = StrCtl::getPatternToZen($pattern);
        if ($pattern != $pattern_zen) {
            $petterns[] = $pattern_zen;
        }
        if ($petterns) {
            $pattern = '(?:' . implode('|', array_merge(array($pattern), $petterns)) . ')';
        }

        return $pattern;
    }

    /**
     * （パターン）文字列を半角にする
     *
     * @static
     * @access  private
     * @return  string
     */
    function getPatternToHan($pattern, $no_escape = false)
    {
        $kigou = StrCtl::getKigouPattern($no_escape);
        
        // 壊れる
        //$pattern = str_replace($kigou['zen'], $kigou['han'], $pattern);

        if (P2_MBREGEX_AVAILABLE == 1) {

            foreach ($kigou['zen'] as $k => $v) {
        
                $word_fm = $kigou['zen'][$k];
                
                /*
                // preg_quote()で2バイト目が0x5B("[")の"ー"なども変換されてしまうので
                // UTF-8にしてから正規表現の特殊文字をエスケープ
                $word_fm = mb_convert_encoding($word_fm, 'UTF-8', 'SJIS-win');
                $word_fm = preg_quote($word_fm);
                $word_fm = mb_convert_encoding($word_fm, 'SJIS-win', 'UTF-8');
                */
                
                $pattern = mb_ereg_replace($word_fm, $kigou['han'][$k], $pattern);
            }
        }
        
        //echo $pattern;
        $pattern = mb_convert_kana($pattern, 'rnk');
        
        
        
        return $pattern;
    }
    
    /**
     * （パターン）文字列を全角にする
     *
     * @static
     * @access  private
     * @return  string
     */
    function getPatternToZen($pattern, $no_escape = false)
    {
        $kigou = StrCtl::getKigouPattern($no_escape);
        
        // 壊れる
        // $pattern = str_replace($kigou['han'], $kigou['zen'], $pattern);
        
        if (P2_MBREGEX_AVAILABLE == 1) {
            foreach ($kigou['zen'] as $k => $v) {
        
                $word_fm = $kigou['han'][$k];
                
                
                // preg_quote()で2バイト目が0x5B("[")の"ー"なども変換されてしまうので
                // UTF-8にしてから正規表現の特殊文字をエスケープ
                $word_fm = mb_convert_encoding($word_fm, 'UTF-8', 'SJIS-win');
                $word_fm = preg_quote($word_fm);
                $word_fm = mb_convert_encoding($word_fm, 'SJIS-win', 'UTF-8');
                
                
                $pattern = mb_ereg_replace($word_fm, $kigou['zen'][$k], $pattern);
            }
        }
        
        $pattern = mb_convert_kana($pattern, 'RNKV');
        
        return $pattern;
    }
    
    /**
     * 全角/半角の記号パターンを得る
     *
     * @static
     * @access  private
     * @return  string
     */
    function getKigouPattern($no_escape = false)
    {
        $kigou['zen'] = array(
            '｀', '（', '）', '？', '＃', '＄', '％', '＠', '＜',   '＞', '！',
            '＊', '＋', '＆',  '＝', '〜', '｜', '｛', '｝', '＿', '．'
        );
        $kigou['han'] = array(
            '`',  '\(', '\)', '\?', '#',  '\$', '%',  '@',  '&lt;', '&gt;', '\!',
            '\*', '\+', '&amp;', '=', '~', '\|', '\{', '\}', '_', '\.'
        );
        
        // NG ---- $ < 
        // str_replace を通した時に、文字が壊れるの回避。。
        //$kigou['zen'] = array('｀', '（', '）', '？', '＃', '％', '＠', '＞', '！',   '＊', '＋', '＆');
        //$kigou['han'] = array('`',  '\(', '\)', '\?', '#',  '%',  '@',  '&gt;', '\!', '\*', '\+', '&amp;');

        if ($no_escape) {
            $kigou['han'] = array_map(create_function('$str', 'return ltrim($str, "\\\");'), $kigou['han']);
            /*
            foreach ($kigou['han'] as $k => $v) {
                $kigou['han'][$k] = ltrim($v, '\\');
            }
            */
        }
        
        return $kigou;
    }
}

/*
 * Local Variables:
 * mode: php
 * coding: cp932
 * tab-width: 4
 * c-basic-offset: 4
 * indent-tabs-mode: nil
 * End:
 */
// vim: set syn=php fenc=cp932 ai et ts=4 sw=4 sts=4 fdm=marker:
