123 lines
4.0 KiB
PHP
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);
|
|
}
|
|
}
|