比较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*/8functioncutstr($string,$length,$dot=’…’){9//如果字符串小于要截取的长度则直接返回10//此处使用strlen获取字符串长度有很大的弊病,比如对字符串“新年快乐”要截取4个中文字符,11//那么必须知道这4个中文字符的字节数,否则返回的字符串可能会是“新年快乐…”12if(strlen($string)<=$length){13return$string;14}1516//转换原字符串中htmlspecialchars17$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);2021$strcut=”;//初始化返回值2223//如果是utf-8编码(这个判断有点不全,有可能是utf8)24if(strtolower(CHARSET)==’utf-8′){25//初始连续循环指针$n,最后一个字位数$tn,截取的字符数$noc26$n=$tn=$noc=0;27while($n<strlen($string)){28$t=ord($string[$n]);2930if($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}6061//超过了要取的数就跳出连续循环62if($noc>=$length){63break;64}65}6667//这个地方是把最后一个字去掉,以备加$dot68if($noc>$length){69$n-=$tn;70}7172$strcut=substr($string,0,$n);7374}else{75//并非utf-8编码的全角就后移2位76for($i=0;$i<$length;$i++){77$strcut.=ord($string[$i])>127?$string[$i].$string[++$i]:$string[$i];78}79}8081//再还原最初的htmlspecialchars82$strcut=str_replace(array($pre.’&’.$end,$pre.'”‘.$end,$pre.'<‘.$end,$pre.’>’.$end),array(‘&amp;’,’&quot;’,’&lt;’,’&gt;’),$strcut);8384$pos=strrpos($strcut,chr(1));85if($pos!==false){86$strcut=substr($strcut,0,$pos);87}8889return$strcut.$dot;//最后把截取加上$dot输出90}

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

$str1=”欲穷千里目”;echomy_cutstr($str1,10,”…”).”\n”;//输出:欲穷千里目…[这是一个bug,想想是什么原因导致?]echomy_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*/10functionsub_str($str,$length=0,$append=’…’){11$str=trim($str);12$strlength=strlen($str);1314if($length==0||$length>=$strlength){15return$str;16}elseif($length<0){17$length=$strlength+$length;18if($length<0){19$length=$strlength;20}21}2223if(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}3132if($append&&$str!=$newstr){33$newstr.=$append;34}3536return$newstr;37}

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

$str1=”白日依山尽,黄河入海流”;echo$str1.”\n”;echomy_sub_str($str1,4,”…”).”\n”;//输出:白日依山…$str2=”白1日2依3山4″;echo$str2.”\n”;echomy_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*/8functionsubstring($string,$length,$append=false){9if($length<=0){10return”;11}1213//检测原始字符串是否为UTF-8编码14$is_utf8=false;15$str1=@iconv(“UTF-8″,”GBK”,$string);16$str2=@iconv(“GBK”,”UTF-8″,$str1);17if($string==$str2){18$is_utf8=true;1920//如果是UTF-8编码,则使用GBK编码的21$string=$str1;22}2324$newstr=”;25for($i=0;$i<$length;$i++){26$newstr.=ord($string[$i])>127?$string[$i].$string[++$i]:$string[$i];27}2829if($is_utf8){30$newstr=@iconv(“GBK”,”UTF-8″,$newstr);31}3233if($append&&$newstr!=$string){34$newstr.=$append;35}3637return$newstr;38}

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

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

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

思想如钻子,必须集中在一点钻下去才有力量

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

相关文章:

你感兴趣的文章:

标签云: