Files

123 lines
4.0 KiB
PHP

<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace addon\haonav\library;
/**
* 轻量拼音工具(零依赖)
*
* - 首字母:基于 GBK 编码区间推导(覆盖 GB2312 常用汉字),如「淘宝网」-> tbw
* - 全拼:若安装了 overtrue/pinyin 则自动启用;未安装时降级为仅首字母
* - keywordsOf() 产出「首字母 + 全拼」搜索串,存入 links.pinyin 供 LIKE 检索
*/
class Pinyin
{
/**
* GBK 双字节码位 -> 拼音首字母 区间表(GB2312 一级/二级汉字按拼音排序的特性)
* 每项:[起始码位, 结束码位, 字母]
*/
protected const RANGES = [
[0xB0A1, 0xB0C4, 'a'],
[0xB0C5, 0xB2C0, 'b'],
[0xB2C1, 0xB4ED, 'c'],
[0xB4EE, 0xB6E9, 'd'],
[0xB6EA, 0xB7A1, 'e'],
[0xB7A2, 0xB8C0, 'f'],
[0xB8C1, 0xB9FD, 'g'],
[0xB9FE, 0xBBF6, 'h'],
[0xBBF7, 0xBFA5, 'j'],
[0xBFA6, 0xC0AB, 'k'],
[0xC0AC, 0xC2E7, 'l'],
[0xC2E8, 0xC4C2, 'm'],
[0xC4C3, 0xC5B5, 'n'],
[0xC5B6, 0xC5BD, 'o'],
[0xC5BE, 0xC6D9, 'p'],
[0xC6DA, 0xC8BA, 'q'],
[0xC8BB, 0xC8F5, 'r'],
[0xC8F6, 0xCBF9, 's'],
[0xCBFA, 0xCDD9, 't'],
[0xCDDA, 0xCEF3, 'w'],
[0xCEF4, 0xD1B8, 'x'],
[0xD1B9, 0xD4D0, 'y'],
[0xD4D1, 0xD7F9, 'z'],
];
/**
* 取单字符的拼音首字母;ASCII 字母数字原样小写返回,非汉字返回空串
*/
public static function firstLetter(string $char): string
{
if ($char === '') {
return '';
}
if (preg_match('/^[a-zA-Z0-9]$/', $char)) {
return strtolower($char);
}
$gbk = @iconv('UTF-8', 'GBK//IGNORE', $char);
if ($gbk === false || strlen($gbk) < 2) {
return '';
}
$code = ord($gbk[0]) * 256 + ord($gbk[1]);
foreach (self::RANGES as [$min, $max, $letter]) {
if ($code >= $min && $code <= $max) {
return $letter;
}
}
return '';
}
/**
* 整串首字母:如「淘宝网」-> "tbw",「哔哩哔哩B站」-> "blblbz"
*/
public static function initials(string $str): string
{
$out = '';
foreach (preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY) ?: [] as $ch) {
$out .= self::firstLetter($ch);
}
return $out;
}
/**
* 全拼(依赖 overtrue/pinyin,未安装返回空串)
*/
public static function full(string $str): string
{
if (!class_exists(\Overtrue\Pinyin\Pinyin::class)) {
return '';
}
try {
// v5 静态 API
if (method_exists(\Overtrue\Pinyin\Pinyin::class, 'sentence')) {
$r = \Overtrue\Pinyin\Pinyin::sentence($str);
$s = is_object($r) && method_exists($r, 'join') ? $r->join('') : (string)$r;
return preg_replace('/[^a-z0-9]/', '', strtolower($s)) ?? '';
}
// v4 实例 API
$py = new \Overtrue\Pinyin\Pinyin();
return preg_replace('/[^a-z0-9]/', '', strtolower(implode('', $py->convert($str)))) ?? '';
} catch (\Throwable $e) {
return '';
}
}
/**
* 生成搜索串:「首字母 全拼」(全拼可能为空),限长 250
*/
public static function keywordsOf(string $str): string
{
$initials = self::initials($str);
$full = self::full($str);
$out = trim($initials . ($full !== '' && $full !== $initials ? ' ' . $full : ''));
return mb_substr($out, 0, 250);
}
}