$cfg_soft_lang ='utf-8';
/**
* 中英文截取字符串,汉字安2个字节
*
* @access public
* @param string $str 需要截取的字符串
* @param int $cutLen 截取的长度
* @param bool $cutSlashes 是否去掉\
* @param bool $addSlashes 是加\
* @param string $oDot 截取后加的字符串,如经常用的三个点
* @param bool $hasHtml 是否有html
* @return string
*/
function cn_substr($str, $cutLen, $oDot = null, $hasHtml = false, $cutSlashes = false, $addSlashes = false)
{
global $cfg_soft_lang;
$str = trim($str);
if ($cutSlashes) $str = stripslashes($str);
if ($hasHtml) {
$str = preg_replace('/(\<[^\<]*\>|\r|\n|\s|\[.+?\])/is','', $str);
$str = htmlspecialchars($str);
} else {
$str = htmlspecialchars($str);
}
if ($cutLen && strlen($str) > $cutLen) {
$nStr ='';
if ($cfg_soft_lang =='utf-8'){
$n = 0;
$tn = 0;
$noc = 0;
while ($n < strlen($str)) {
$t = ord($str [$n]);
if ($t == 9 || $t == 10 || (32 <= $t && $t <= 126)) {
$tn = 1;
$n++;
$noc++;
} elseif (194 <= $t && $t <= 223) {
$tn = 2;
$n += 2;
$noc += 2;
} elseif (224 <= $t && $t < 239) {
$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 >= $cutLen) break;
}
if ($noc > $cutLen) $n -= $tn;
$nStr = substr($str, 0, $n);
}else{
for ($i = 0; $i < $cutLen - 1; $i++) {
if (ord($str [$i]) > 127) {
$nStr .= $str [$i] . $str [$i + 1];
$i++;
} else {
$nStr .= $str [$i];
}
}
}
$str = $nStr . $oDot;
}
if ($addSlashes) $str = addslashes($str);
$str = htmlspecialchars_decode($str);
return trim($str);
}
ecshop 的cn_substr
/**
* 截取UTF-8编码下字符串的函数
*
* @param string $str 被截取的字符串
* @param int $length 截取的长度
* @param bool $append 是否附加省略号
*
* @return string
*/
function sub_str($str, $length = 0, $append = true)
{
$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, EC_CHARSET);
}
elseif (function_exists('iconv_substr'))
{
$newstr = iconv_substr($str, 0, $length, EC_CHARSET);
}
else
{
//$newstr = trim_right(substr($str, 0, $length));
$newstr = substr($str, 0, $length);
}
if ($append && $str != $newstr)
{
$newstr .= '...';
}
return $newstr;
}
中文字符截取是一个十分有用的功能,在很多地方都会用到,比如提取定长标题,抽取标签等
由于各种字符集的存储方式的不一样,存在双字节(GBK)多字节(Unicode)的存储方式,这就导致了统一处理的困难。
国际标准UTF8字符编码中,存储中文字符要3个字节,即把php文件存储为UTF8编码格式可以得到 strlen("中")=3
假如直接用php的字符串函数去处理类似这个字符将可能产生乱友的情况,主要是由于在一个完整字符中间强制截断产生半字符的情况。要避免这种情况的唯一办法就是把截断位置延长到这个字符结束的位置进行截断
在PHP中,提供了mbstring模块功能,mbstring以字符作为操作的基本单元,如mb_strlen("中","utf-8")=1 。 大部分的mbstring都要求输入编码,当然可以在php环境中设置默认的编码。参数设置为mbstring.internal_encoding = UTF-8, mbstring.http_input = UTF-8, mbstring.http_output = UTF-8, mbstring.substitute_character = UTF-8。由于php系统一般是存储字节来计算长度的,为了消除mbstring和string之间的差异,使用mbstring的时候就要格外小心 了。
假如不使用mbstring的功能 的话,可以自行编写中文字符处理的函数。
php中文截取函数,经网上搜索学习发现,大部分网络中都是以从开始位置一个一个完整字符来实现的。这有可能导致效率十分底。下面我写了个截取函数,是针对UTF8的
- <?php
- $str = "d中-在f在你是d我的国家困g在有和,人。工";
- var_dump(strcut($str,0,9));
- function strcut($str,$start,$len){
- if($start < 0)
- $start = strlen($str)+$start;
- $retstart = $start+getOfFirstIndex($str,$start);
- echo $retstart;
- $retend = $start + $len -1 + getOfFirstIndex($str,$start + $len);
- echo $retend;
- return substr($str,$retstart,$retend-$retstart+1);
- }
- //判断字符开始的位置
- function getOfFirstIndex($str,$start){
- $char_aci = ord(substr($str,$start-1,1));
- if(223<$char_aci && $char_aci<240)
- return -1;
- $char_aci = ord(substr($str,$start-2,1));
- if(223<$char_aci && $char_aci<240)
- return -2;
- return 0;
- }
- ?>
224-239是UTF8中文字符高位(第一位,共三位)ascii区间,换算成十六进制为0xE0 - 0xEF 二进制为1100 0000 - 1100 FFFF。通过适当偏移截取位置来避免在一个字符的中间截断的现象。
附UTF8字符编码规则:区间无重复。
存储字节数 | 字节流( 二进制)(x为可变位) | 最高位区间(10进制) | 低位区间 |
一字节 | 0xxxxxxx | 00 - 7F | 无 |
二字节 | 110xxxxx 10xxxxxx | C0 - DF | 80 - BF |
三字节 | 1110xxxx 10xxxxxx 10xxxxxx | E0 - EF | 80 - BF |
四字节 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | F0 - FF | 80 - BF |
在GBK字符编码中,存储中文字符要2个字节,即 把php文件存储为ANSI(GBK)编码格式 可以得到 strlen("中")=2
在GBK字符编码中,中文字符最高位指定是1。
GBK 亦采用双字节表示,总体编码范围为 8140-FEFE,首字节在 81-FE 之间,尾字节在 40-FE之间,剔除 xx7F一条线。总计 23940个码位,共收入 21886个汉字和图形符号,其中汉字(包括部首和构件) 21003个,图形符号 883个。高位和低位有可能相同所以无法判断高低位了。
但我们知道,gbk2312编码是 在区位码的区号和位号上分别加上0xA0就得到的,区位码中01-09区是符号、数字区,16-87区是汉字区,10-15和88-94是未定义的空白区。所有汉字的编码为16-87加上0xa0,那么我们可以通过判断大于oxa1来判定是否是一个汉字字符。
php实现如下
- <?php
- $str = "d中-在f在你是d我的国家困g在有和,人。工";
- echo strcut($str,0,9);
- function strcut($str,$start,$len){
- if($start < 0)
- $start = strlen($str)+$start;
- $retstart = $start+getOfFirstIndex($str,$start);
- echo $retstart;
- $retend = $start + $len -1 + getOfFirstIndex($str,$start + $len);
- echo $retend;
- return substr($str,$retstart,$retend-$retstart+1);
- }
- //判断字符开始的位置支持GBK
- function getOfFirstIndex($str,$start){
- $p = "[".chr(0xa1)."-".chr(0xff)."]+$";
- preg_match("/$p/",substr($str,$start),$res);
- if (isset($res[0]) and fmod(strlen($res[0]),2) == 1)
- return -1;
- return 0;
- }
- ?>