比较discuz和ecshop的截取字符串函数

网上看到一篇文章 discuz和ecshop截取字符串的两个函数,比较了一下两个版本的函数,都各有局限,只能在特定的前提下使用,但是学习一下有利于拓宽思路,了解PHP的扩展功能。

 

下面先给出两个版本函数的源代码以及简单测试,最后我会给出一个实用性更强的字符串截取函数。需要注意的是:这里讨论的字符串截取问题都是针对UTF-8编码的中文字符串。

 

discuz版本

 1  /* *
 2  * [discuz] 基于PHP没有安装 mb_substr 等扩展截取字符串,如果截取中文字则按2个字符计算
 3  * @param  $string     要截取的字符串
 4  * @param  $length     要截取的字符数
 5  * @param  $dot        替换截掉部分的结尾字符串
 6  * @return 返回截取后的字符串
 7  */
 8  function cutstr( $string$length$dot = '...') {
 9    //  如果字符串小于要截取的长度则直接返回
10    // 此处使用strlen获取字符串长度有很大的弊病,比如对字符串“新年快乐”要截取4个中文字符,
11    // 那么必须知道这4个中文字符的字节数,否则返回的字符串可能会是“新年快乐...”
12     if ( strlen( $string) <=  $length) {
13        return  $string;
14   }
15   
16    //  转换原字符串中htmlspecialchars
17     $pre =  chr(1);
18    $end =  chr(1);
19    $string =  str_replace (  array ('&amp;', '&quot;', '&lt;', '&gt;' ),  array ( $pre . '&' .  $end$pre . '"' .  $end$pre . '<' .  $end$pre . '>' .  $end ),  $string );
20   
21    $strcut = '';    //  初始化返回值
22    
23    // 如果是utf-8编码(这个判断有点不全,有可能是utf8)
24     if ( strtolower ( CHARSET ) == 'utf-8') {
25      //  初始连续循环指针$n,最后一个字位数$tn,截取的字符数$noc
26       $n =  $tn =  $noc = 0;
27      while (  $n <  strlen (  $string ) ) {
28        $t =  ord (  $string [ $n] );
29       
30        if ( $t == 9 ||  $t == 10 || (32 <=  $t &&  $t <= 126)) {
31          //  如果是英语半角符号等,$n指针后移1位,$tn最后字是1位
32           $tn = 1;
33          $n++;
34          $noc++;
35       }  elseif (194 <=  $t &&  $t <= 223) {
36          //  如果是二字节字符$n指针后移2位,$tn最后字是2位
37           $tn = 2;
38          $n += 2;
39          $noc += 2;
40       }  elseif (224 <=  $t &&  $t <= 239) {
41          //  如果是三字节(可以理解为中字词),$n后移3位,$tn最后字是3位
42           $tn = 3;
43          $n += 3;
44          $noc += 2;
45       }  elseif (240 <=  $t &&  $t <= 247) {
46          $tn = 4;
47          $n += 4;
48          $noc += 2;
49       }  elseif (248 <=  $t &&  $t <= 251) {
50          $tn = 5;
51          $n += 5;
52          $noc += 2;
53       }  elseif ( $t == 252 ||  $t == 253) {
54          $tn = 6;
55          $n += 6;
56          $noc += 2;
57       }  else {
58          $n++;
59       }
60       
61        //  超过了要取的数就跳出连续循环
62         if ( $noc >=  $length) {
63          break;
64       }
65     }
66     
67      //  这个地方是把最后一个字去掉,以备加$dot
68       if ( $noc >  $length) {
69        $n -=  $tn;
70     }
71     
72      $strcut =  substr (  $string, 0,  $n );
73   
74   }  else {
75      //  并非utf-8编码的全角就后移2位
76       for ( $i = 0;  $i <  $length$i ++) {
77        $strcut .=  ord (  $string [ $i] ) > 127 ?  $string [ $i] .  $string [++  $i] :  $string [ $i];
78     }
79   }
80   
81    //  再还原最初的htmlspecialchars
82     $strcut =  str_replacearray ( $pre . '&' .  $end$pre . '"' .  $end$pre . '<' .  $end$pre . '>' .  $end ),  array ('&amp;', '&quot;', '&lt;', '&gt;' ),  $strcut );
83 
84    $pos =  strrpos (  $strcutchr ( 1 ) );
85    if ( $pos !==  false) {
86      $strcut =  substr (  $strcut, 0,  $pos );
87   }
88   
89    return  $strcut .  $dot//  最后把截取加上$dot输出
90  }

 

 discuz版本的最大缺陷在于使用 strlen 获取原始字符串的长度,并用来和传入的要截取长度参数(字节数)进行比较,由于UTF-8的中文字符的字节数是不固定的,所以就会面临这样的窘境:如果要截取4个中文字符应该指定多大的截取长度呢?8字节还是12字节呢?。。。这是无法预计的,也正是因为这个问题discuz的cutstr实际是有bug的,通过下面的测试结果能看出: 

$str1 = "欲穷千里目";
echo my_cutstr( $str1, 10, "...")."\n";   //  输出:欲穷千里目...  [这是一个bug,想想是什么原因导致?]
echo my_cutstr( $str1, 15, "...")."\n";   //  输出:欲穷千里目

  

导致上述bug的原因在与cutstr函数在截取字符的时候是将一个中文字按2个字符算,那么5个中文字就是10字符,而原始字符串的长度是15字节,所以cutstr认为“成功地”从15字符的串上截取了10个字符,然后加上了“尾巴”。要解决这个bug只要在判断一下返回的子串是否和原始串相同,如果相同就不加“尾巴”。

 

 

ecshop版

 1  /* *
 2  * [ecshop] 基于PHP的 mb_substr,iconv_substr 这两个扩展来截取字符串,中文字符都是按1个字符长度计算;
 3  * 该函数仅适用于utf-8编码的中文字符串。
 4 
 5  * @param  $str      原始字符串
 6  * @param  $length   截取的字符数
 7  * @param  $append   替换截掉部分的结尾字符串
 8  * @return 返回截取后的字符串
 9  */
10  function sub_str( $str$length = 0,  $append = '...') {
11      $str =  trim( $str);
12      $strlength =  strlen( $str);
13     
14      if ( $length == 0 ||  $length >=  $strlength) {
15          return  $str;
16     }  elseif ( $length < 0) {
17          $length =  $strlength +  $length;
18          if ( $length < 0) {
19              $length =  $strlength;
20         }
21     }
22     
23      if (  function_exists('mb_substr') ) {
24          $newstr = mb_substr( $str, 0,  $length, 'utf-8');
25     }  elseif (  function_exists('iconv_substr') ) {
26          $newstr =  iconv_substr( $str, 0,  $length, 'utf-8');
27     }  else {
28          // $newstr = trim_right(substr($str, 0, $length));
29           $newstr =  substr( $str, 0,  $length);
30     }
31     
32      if ( $append &&  $str !=  $newstr) {
33          $newstr .=  $append;
34     }
35     
36      return  $newstr;
37 }

  

ecshop版的特点和缺点都在于将中文字符算作一个字符,如果原始字符串中不含中文,比如:abcd1234,如果本意是要截取4个中文字符或者8个英文字符,那么使用ecshop的版本就得不到期望的结果,返回值的是:abcd。下面是简单的测试结果: 

$str1 = "白日依山尽,黄河入海流";
echo  $str1."\n";
echo my_sub_str( $str1, 4, "...")."\n";   //  输出:白日依山...
  
$str2 = "白1日2依3山4";
echo  $str2."\n";
echo my_sub_str( $str2, 4, "...")."\n";   //  输出:白1日2...

 

 

优化版

截取中文字符串的大部分应用场景是“原始字符串可以是中文、英文、数字混杂的,中文字按2个字符算,英文数字按1个字符算”,针对这个需求下面给出一个实现版本:

 1  /* *
 2  * 字符串截取,中文字符按2个字符计算,同时支持GBK和UTF-8编码
 3  * @param  $string     要截取的字符串
 4  * @param  $length     要截取的字符数
 5  * @param  $append     添加到子串后的尾巴
 6  * @return 返回截取后的字符串
 7  */
 8  function substring( $string$length$append =  false) {
 9    if (  $length <= 0 ) {
10      return '';
11   }
12   
13    //  检测原始字符串是否为UTF-8编码
14     $is_utf8 =  false;
15    $str1 = @ iconv("UTF-8", "GBK",  $string);
16    $str2 = @ iconv("GBK", "UTF-8",  $str1);
17    if (  $string ==  $str2 ) {
18      $is_utf8 =  true;
19     
20      //  如果是UTF-8编码,则使用GBK编码的
21       $string =  $str1;
22   }
23   
24    $newstr = '';
25    for ( $i = 0;  $i <  $length$i ++) {
26      $newstr .=  ord ( $string[ $i]) > 127 ?  $string[ $i] .  $string[++ $i] :  $string[ $i];
27   }
28   
29    if (  $is_utf8 ) {
30      $newstr = @ iconv("GBK", "UTF-8",  $newstr);
31   }
32   
33    if ( $append &&  $newstr !=  $string) {
34          $newstr .=  $append;
35     }
36     
37      return  $newstr;
38 }

 

测试结果见下(GBK和UTF-8的结果一致):

$str1 = "白日依山尽,黄河入海流";
echo substring( $str1, 4, "...")."\n";   //  输出:白日...
echo substring( $str1, 5, "...")."\n";   //  输出:白日依...
  
$str2 = "12白34日56依78山";
echo substring( $str2, 4, "...")."\n";   //  输出:12白...
echo substring( $str2, 5, "...")."\n";   //  输出:12白3...


本文系原创,欢迎转载,请注明出处!


 

你可能感兴趣的:(discuz)