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


Posted in PHP onSeptember 03, 2012

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

/** 
* [discuz] 基于PHP没有安装 mb_substr 等扩展截取字符串,如果截取中文字则按2个字符计算 
* @param $string 要截取的字符串 
* @param $length 要截取的字符数 
* @param $dot 替换截掉部分的结尾字符串 
* @return 返回截取后的字符串 
*/ 
function cutstr($string, $length, $dot = '...') { 
// 如果字符串小于要截取的长度则直接返回 
// 此处使用strlen获取字符串长度有很大的弊病,比如对字符串“新年快乐”要截取4个中文字符, 
// 那么必须知道这4个中文字符的字节数,否则返回的字符串可能会是“新年快乐...” 
if (strlen($string) <= $length) { 
return $string; 
} 
// 转换原字符串中htmlspecialchars 
$pre = chr(1); 
$end = chr(1); 
$string = str_replace ( array ('&', '"', '<', '>' ), array ($pre . '&' . $end, $pre . '"' . $end, $pre . '<' . $end, $pre . '>' . $end ), $string ); 
$strcut = ''; // 初始化返回值 
// 如果是utf-8编码(这个判断有点不全,有可能是utf8) 
if (strtolower ( CHARSET ) == 'utf-8') { 
// 初始连续循环指针$n,最后一个字位数$tn,截取的字符数$noc 
$n = $tn = $noc = 0; 
while ( $n < strlen ( $string ) ) { 
$t = ord ( $string [$n] ); 
if ($t == 9 || $t == 10 || (32 <= $t && $t <= 126)) { 
// 如果是英语半角符号等,$n指针后移1位,$tn最后字是1位 
$tn = 1; 
$n++; 
$noc++; 
} elseif (194 <= $t && $t <= 223) { 
// 如果是二字节字符$n指针后移2位,$tn最后字是2位 
$tn = 2; 
$n += 2; 
$noc += 2; 
} elseif (224 <= $t && $t <= 239) { 
// 如果是三字节(可以理解为中字词),$n后移3位,$tn最后字是3位 
$tn = 3; 
$n += 3; 
$noc += 2; 
} elseif (240 <= $t && $t <= 247) { 
$tn = 4; 
$n += 4; 
$noc += 2; 
} elseif (248 <= $t && $t <= 251) { 
$tn = 5; 
$n += 5; 
$noc += 2; 
} elseif ($t == 252 || $t == 253) { 
$tn = 6; 
$n += 6; 
$noc += 2; 
} else { 
$n++; 
} 
// 超过了要取的数就跳出连续循环 
if ($noc >= $length) { 
break; 
} 
} 
// 这个地方是把最后一个字去掉,以备加$dot 
if ($noc > $length) { 
$n -= $tn; 
} 
$strcut = substr ( $string, 0, $n ); 
} else { 
// 并非utf-8编码的全角就后移2位 
for ($i = 0; $i < $length; $i ++) { 
$strcut .= ord ( $string [$i] ) > 127 ? $string [$i] . $string [++ $i] : $string [$i]; 
} 
} 
// 再还原最初的htmlspecialchars 
$strcut = str_replace( array ($pre . '&' . $end, $pre . '"' . $end, $pre . '<' . $end, $pre . '>' . $end ), array ('&', '"', '<', '>' ), $strcut ); 
$pos = strrpos ( $strcut, chr ( 1 ) ); 
if ($pos !== false) { 
$strcut = substr ( $strcut, 0, $pos ); 
} 
return $strcut . $dot; // 最后把截取加上$dot输出 
}

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版
/** 
* [ecshop] 基于PHP的 mb_substr,iconv_substr 这两个扩展来截取字符串,中文字符都是按1个字符长度计算; 
* 该函数仅适用于utf-8编码的中文字符串。 
* 
* @param $str 原始字符串 
* @param $length 截取的字符数 
* @param $append 替换截掉部分的结尾字符串 
* @return 返回截取后的字符串 
*/ 
function sub_str($str, $length = 0, $append = '...') { 
$str = trim($str); 
$strlength = strlen($str); 
if ($length == 0 || $length >= $strlength) { 
return $str; 
} elseif ($length < 0) { 
$length = $strlength + $length; 
if ($length < 0) { 
$length = $strlength; 
} 
} 
if ( function_exists('mb_substr') ) { 
$newstr = mb_substr($str, 0, $length, 'utf-8'); 
} elseif ( function_exists('iconv_substr') ) { 
$newstr = iconv_substr($str, 0, $length, 'utf-8'); 
} else { 
//$newstr = trim_right(substr($str, 0, $length)); 
$newstr = substr($str, 0, $length); 
} 
if ($append && $str != $newstr) { 
$newstr .= $append; 
} 
return $newstr; 
}

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个字符算”,针对这个需求下面给出一个实现版本:
/** 
* 字符串截取,中文字符按2个字符计算,同时支持GBK和UTF-8编码 
* @param $string 要截取的字符串 
* @param $length 要截取的字符数 
* @param $append 添加到子串后的尾巴 
* @return 返回截取后的字符串 
*/ 
function substring($string, $length, $append = false) { 
if ( $length <= 0 ) { 
return ''; 
} 
// 检测原始字符串是否为UTF-8编码 
$is_utf8 = false; 
$str1 = @iconv("UTF-8", "GBK", $string); 
$str2 = @iconv("GBK", "UTF-8", $str1); 
if ( $string == $str2 ) { 
$is_utf8 = true; 
// 如果是UTF-8编码,则使用GBK编码的 
$string = $str1; 
} 
$newstr = ''; 
for ($i = 0; $i < $length; $i ++) { 
$newstr .= ord ($string[$i]) > 127 ? $string[$i] . $string[++$i] : $string[$i]; 
} 
if ( $is_utf8 ) { 
$newstr = @iconv("GBK", "UTF-8", $newstr); 
} 
if ($append && $newstr != $string) { 
$newstr .= $append; 
} 
return $newstr; 
}

测试结果见下(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...

作者:edwardlost' blog
PHP 相关文章推荐
坏狼php学习 计数器实例代码
Jun 15 PHP
php 分库分表hash算法
Nov 12 PHP
一些被忽视的PHP函数(简单整理)
Apr 30 PHP
PHP开发微信支付的代码分享
May 25 PHP
2个Codeigniter文件批量上传控制器写法例子
Jul 25 PHP
PHP中feof()函数实例测试
Aug 23 PHP
php用ini_get获取php.ini里变量值的方法
Mar 04 PHP
php获取网页上所有链接的方法
Apr 03 PHP
在WordPress中实现评论头像的自定义默认和延迟加载
Nov 24 PHP
在openSUSE42.1下编译安装PHP7 的方法
Dec 24 PHP
php实现的二分查找算法示例
Jun 20 PHP
tp5 实现列表数据根据状态排序
Oct 18 PHP
Windows下部署Apache+PHP+MySQL运行环境实战
Aug 31 #PHP
关于UEditor编辑器远程图片上传失败的解决办法
Aug 31 #PHP
php数组一对一替换实现代码
Aug 31 #PHP
PHP 利用AJAX获取网页并输出的实现代码(Zjmainstay)
Aug 31 #PHP
PHP的简易冒泡法代码分享
Aug 28 #PHP
php 解决旧系统 查出所有数据分页的类
Aug 27 #PHP
PHP实现手机归属地查询API接口实现代码
Aug 27 #PHP
You might like
php+AJAX传送中文会导致乱码的问题的解决方法
2008/09/08 PHP
php xml实例 留言本
2009/03/20 PHP
查找php配置文件php.ini所在路径的二种方法
2014/05/26 PHP
ThinkPHP3.1新特性之Action参数绑定
2014/06/19 PHP
PHP获取真实客户端的真实IP
2017/03/07 PHP
php如何计算两坐标点之间的距离
2018/12/29 PHP
用jquery实现学校的校历(asp.net+jquery ui 1.72)
2010/01/01 Javascript
jQuery学习笔记之jQuery.fn.init()的参数分析
2014/06/09 Javascript
javascript中with()方法的语法格式及使用
2014/08/04 Javascript
ANGULARJS中用NG-BIND指令实现单向绑定的例子
2014/12/08 Javascript
jQuery中:gt选择器用法实例
2014/12/29 Javascript
javascript中this的四种用法
2015/05/11 Javascript
详解JavaScript编程中正则表达式的使用
2015/10/25 Javascript
JavaScript判断微信浏览器实例代码
2016/06/13 Javascript
浅谈JavaScript的计时器对象
2016/12/26 Javascript
ES6学习笔记之Set和Map数据结构详解
2017/04/07 Javascript
JS实现基于拖拽改变物体大小的方法
2018/01/23 Javascript
在vue中获取token,并将token写进header的方法
2018/09/26 Javascript
微信小程序人脸识别功能代码实例
2019/05/07 Javascript
layui实现checkbox的目录树tree的例子
2019/09/12 Javascript
js将日期格式转换为YYYY-MM-DD HH:MM:SS
2020/09/18 Javascript
[04:27]2014DOTA2国际邀请赛 NAVI战队官方纪录片
2014/07/21 DOTA
python实现批量下载新浪博客的方法
2015/06/15 Python
Python3使用requests登录人人影视网站的方法
2016/05/11 Python
Python用5行代码写一个自定义简单二维码
2018/10/21 Python
快速解决pyqt5窗体关闭后子线程不同时退出的问题
2019/06/19 Python
python集合删除多种方法详解
2020/02/10 Python
scrapy框架携带cookie访问淘宝购物车功能的实现代码
2020/07/07 Python
HTML5地理定位_动力节点Java学院整理
2017/07/12 HTML / CSS
大学总结自我鉴定
2014/01/18 职场文书
淘宝客服专员岗位职责
2014/04/11 职场文书
初中生操行评语大全
2014/04/24 职场文书
骨干教师考核方案
2014/05/09 职场文书
安全生产年活动总结
2014/08/29 职场文书
创先争优公开承诺书
2014/08/30 职场文书
Angular性能优化之第三方组件和懒加载技术
2021/05/10 Javascript