最近在制作 Typecho default ultra 主题,在这个主题中我加入了对文章字数统计的功能。此前我也参考了其他优秀的开发者的文章字数统计方式,我自己也优化了好几次。
文章字数统计,看起来是一个很简单的小功能,为什么值得被优化好几次?其实原因很简单,现存的方案统计出来的结果,与预估结果误差太大。
先看看现有的文章字数统计方案。
方案 1:源自百度搜索结果。
function count($cid) { // 方法传入文章cid,然后根据cid从数据库中获取文章的内容 $db = Typecho_Db::get(); $row = $db->fetchRow ($db->select ('table.contents.text')->from ('table.contents')->where ('table.contents.cid = ?', $cid)->order ('table.contents.cid', Typecho_Db::SORT_ASC)->limit (1)); $text = $row['text']; // 自动检测原文本的编码并转换为UTF-8编码 $text = mb_convert_encoding($text, 'UTF-8', 'auto'); // 过滤空白字符 $text = str_replace('/( |\s|\r\n|\n|\r)+/u', '', $text); // 使用正则替换删除所有非中文字符 $text = preg_replace('/[^\x{4e00}-\x{9fa5}]/u', '', $text); // 计算中文字符长度 $count = mb_strlen($text, 'UTF-8'); // 返回统计结果 return $count; }
这个方案的问题在于:它只统计中文字符的数量,文章中的英文不统计?数字也不统计?按理来说,中文字符、英文、数字都要统计到字数中去的。
方案 2:源自百度搜索结果。
<?php// 方法直接传入文章的内容,输出计算结果echo mb_strlen($this->content);?>
这个方案的问题在于:mb_strlen 函数能够统计中文、日文、英文、数字等等,UTF-8 编码的中文字符计为 1 个长度,但是一个英文字母也会被计为 1 个长度。理论上来说,一个完整的单词才能计为 1 个长度。本质上,mb_strlen 函数计算的是 Unicode 字符数。
基本上所有的文章字数统计方案,都没办法很好的计算出预估结果。
对于一般情况下的字数统计,我的预估效果是这样的:
1 个中文算 1 个字,如“你好”算 2 个字。
1 个中文符号算 1 个字,如“你好。” 算 3 个字。中文标点符号占用的是全角字符的位置,每一个标点符号都占有等宽的空间,相当于一个汉字,因此中文符号也可以做为独立的元素被计入字数。
1 个英文单词算 1 个字,如“hello world”算 2 个字,“user-name”算 1 个字,“don’t”算 1 个字。对于英文来说,字数的统计焦点在于单词的数量。
英文符号不计算在内。英文符号多为半角字符,通常被用来分隔单词或短语。英文符号在字数统计中不被计算是因为它们的主要功能是语法上的,而非构成独立语义单位。
1 串完整的数字算 1 个字,如“123”算 1 个字,“138 888 8899”算 3 个字。
1 串完整的数字 + 英文算 1 个字,如“user123”算 1 个字,“https://www.duozai.cn/page/7.html”算 1 个字。
空字符串不计算在内。
其他 Unicode 如日文、韩文,正常计算字符。
对于 Typecho Markdown 格式的文章字数统计,我的预估效果是这样的:
Markdown 的所有标记都不能算入字数,如标题井号、分隔符横杠、列表星号横杠等。
Markdown 图片链接不能算入字数,图片描述算入字数。
Markdown 链接地址不能算入字数,链接描述算入字数。
Markdown 代码块中的内容算入字数,代码块标记、代码块语言标记不能算入字数。
Typecho Markdown 格式的文章文本开头有一个固定的 markdown 注释,这也不能算入字数。
在多次优化和调整之下,整出了个文章字数统计的方法:
/** * 计算文章字数 */function postWordCount($archive) { $db = Typecho_Db::get (); $cid = $archive->cid; // 获取文章内容 $rs = $db->fetchRow($db->select ('table.contents.text')->from ('table.contents')->where ('table.contents.cid = ?', $cid)->order ('table.contents.cid', Typecho_Db::SORT_ASC)->limit (1)); $content = $rs['text']; // 匹配 Markdown 标记的正则规则 $rules = [ '/<!--markdown-->/' => '', '/^#{1,6}\s+/m' => '', '/(\*{1,2}|_{1,2})/' => '', '/\[([^\]]+)\]\([^)]+\)/' => '$1', '/!\[([^\]]*)\]\([^)]+\)/' => '$1', '/```.*?\R/' => '', '/\R```/' => '', '/`/' => '', '/^[ \t]*[-*+]\s+/m' => '', '/^[ \t]*\d+\.\s+/m' => '', '/^>\s*/m' => '', '/^[-*_]{3,}\s*$/m' => '', '/\|/' => '', '/^[-: ]+$/' => '', ]; foreach ($rules as $pattern => $replacement) { // 移除 Markdown 标记 $content = preg_replace($pattern, $replacement, $content); } if (empty($content)) { return 0; } // 处理空格(合并连续空格为1个,保留分隔作用) $content = preg_replace('/\s+/', ' ', $content); $content = trim($content); if ($content === '') { return 0; } // 字数计数器 $count = 0; // 按空格拆分成独立块 $blocks = preg_split('/\s+/', $content); foreach ($blocks as $block) { if (empty($block)) { // 空格不计数 continue; } // 核心正则:按规则优先级拆分内容(先拆1-2类,再拆3类,最后剩4类) // 匹配单个中文字符、单个中文符号、连续的英文/数字/英文符号序列、其他 Unicode preg_match_all('/([\x{4e00}-\x{9fa5}]|[\x{3000}-\x{303f}\x{ff00}-\x{ffef}]|[a-zA-Z0-9\!\@\#\$\%\^\&\*\(\)\-\_\+\=\[\]\{\}\|\\\;\:\'\"\,\.\<\>\/\?\`]+|.)/ux', $block, $matches); // 按规则计数 foreach ($matches[0] as $part) { // 所有规则均为“单个/连续序列算1”,直接累加 $count++; } } // 向上取整统计大约字数 return ceil($count / 10) * 10; }
这个方案,基本满足了我的预估效果。
版权声明
本站名称:资源百科
本站永久网址:https://ziyuanbaike.com/
本站的文章部分内容可能来源于网络,如有侵权,请联系站长heytool@126.com进行删除处理。
本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
发表评论