Ver Fonte

Update Chinese Util library

朱金辉 há 2 anos atrás
pai
commit
071568c1a4
77 ficheiros alterados com 4587 adições e 0 exclusões
  1. 1 0
      src/Library/Third/ChineseUtil/.travis/php.ini
  2. 20 0
      src/Library/Third/ChineseUtil/LICENSE
  3. 259 0
      src/Library/Third/ChineseUtil/README.md
  4. BIN
      src/Library/Third/ChineseUtil/clib/chinese_util-php7.4-x64.dll
  5. BIN
      src/Library/Third/ChineseUtil/clib/chinese_util-php7.4-x86.dll
  6. 1 0
      src/Library/Third/ChineseUtil/clib/include.h
  7. BIN
      src/Library/Third/ChineseUtil/clib/libchinese_util-php7.4-swoole4.5.dylib
  8. BIN
      src/Library/Third/ChineseUtil/clib/libchinese_util-php7.4-swoole4.5.so
  9. BIN
      src/Library/Third/ChineseUtil/clib/libchinese_util-php7.4.dylib
  10. BIN
      src/Library/Third/ChineseUtil/clib/libchinese_util-php7.4.so
  11. BIN
      src/Library/Third/ChineseUtil/clib/libchinese_util-php8.0-swoole4.5.dylib
  12. BIN
      src/Library/Third/ChineseUtil/clib/libchinese_util-php8.0-swoole4.5.so
  13. BIN
      src/Library/Third/ChineseUtil/clib/libchinese_util-php8.0.dylib
  14. BIN
      src/Library/Third/ChineseUtil/clib/libchinese_util-php8.0.so
  15. 26 0
      src/Library/Third/ChineseUtil/composer.json
  16. 0 0
      src/Library/Third/ChineseUtil/data/charsData.json
  17. BIN
      src/Library/Third/ChineseUtil/data/chineseData.sqlite
  18. 0 0
      src/Library/Third/ChineseUtil/data/pinyinData.json
  19. 74 0
      src/Library/Third/ChineseUtil/demo/money.php
  20. 73 0
      src/Library/Third/ChineseUtil/demo/number.php
  21. 45 0
      src/Library/Third/ChineseUtil/demo/pinyin.php
  22. 31 0
      src/Library/Third/ChineseUtil/demo/pinyinSplit.php
  23. 33 0
      src/Library/Third/ChineseUtil/demo/st.php
  24. 168 0
      src/Library/Third/ChineseUtil/src/Chinese.php
  25. 25 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Money/BaseInterface.php
  26. 38 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Money/FFI.php
  27. 351 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Money/Memory.php
  28. 38 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Money/SwooleFFI.php
  29. 25 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Number/BaseInterface.php
  30. 38 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Number/FFI.php
  31. 259 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Number/Memory.php
  32. 38 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Number/SwooleFFI.php
  33. 62 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/Base.php
  34. 20 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/BaseInterface.php
  35. 36 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/FFI.php
  36. 254 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/JSON.php
  37. 251 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/Memory.php
  38. 250 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/SQLite.php
  39. 36 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/SwooleFFI.php
  40. 16 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/PinyinSplit/BaseInterface.php
  41. 33 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/PinyinSplit/FFI.php
  42. 203 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/PinyinSplit/Memory.php
  43. 33 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/PinyinSplit/SwooleFFI.php
  44. 71 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/Base.php
  45. 24 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/BaseInterface.php
  46. 37 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/FFI.php
  47. 45 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/JSON.php
  48. 44 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/Memory.php
  49. 43 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/SQLite.php
  50. 37 0
      src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/SwooleFFI.php
  51. 110 0
      src/Library/Third/ChineseUtil/src/Chinese/FFIDriver.php
  52. 31 0
      src/Library/Third/ChineseUtil/src/Chinese/JSONIndex.php
  53. 72 0
      src/Library/Third/ChineseUtil/src/Chinese/Money.php
  54. 72 0
      src/Library/Third/ChineseUtil/src/Chinese/Number.php
  55. 101 0
      src/Library/Third/ChineseUtil/src/Chinese/Pinyin.php
  56. 71 0
      src/Library/Third/ChineseUtil/src/Chinese/PinyinSplit.php
  57. 94 0
      src/Library/Third/ChineseUtil/src/Chinese/SQLiteData.php
  58. 71 0
      src/Library/Third/ChineseUtil/src/Chinese/SimplifiedAndTraditional.php
  59. 46 0
      src/Library/Third/ChineseUtil/src/Chinese/Traits/JSONInit.php
  60. 37 0
      src/Library/Third/ChineseUtil/src/Chinese/Traits/MemoryInit.php
  61. 16 0
      src/Library/Third/ChineseUtil/src/Chinese/Util.php
  62. 7 0
      src/Library/Third/ChineseUtil/tests/bootstrap.php
  63. 16 0
      src/Library/Third/ChineseUtil/tests/phpunit.xml
  64. 348 0
      src/Library/Third/ChineseUtil/tests/unit/BaseTest.php
  65. 32 0
      src/Library/Third/ChineseUtil/tests/unit/FFIModeTest.php
  66. 16 0
      src/Library/Third/ChineseUtil/tests/unit/JSONModeTest.php
  67. 16 0
      src/Library/Third/ChineseUtil/tests/unit/MemoryModeTest.php
  68. 103 0
      src/Library/Third/ChineseUtil/tests/unit/Money/BaseMoneyTest.php
  69. 32 0
      src/Library/Third/ChineseUtil/tests/unit/Money/FFIMoneyTest.php
  70. 16 0
      src/Library/Third/ChineseUtil/tests/unit/Money/MemoryNumberTest.php
  71. 36 0
      src/Library/Third/ChineseUtil/tests/unit/Money/SwooleFFINumberTest.php
  72. 70 0
      src/Library/Third/ChineseUtil/tests/unit/Number/BaseNumberTest.php
  73. 32 0
      src/Library/Third/ChineseUtil/tests/unit/Number/FFINumberTest.php
  74. 16 0
      src/Library/Third/ChineseUtil/tests/unit/Number/MemoryNumberTest.php
  75. 36 0
      src/Library/Third/ChineseUtil/tests/unit/Number/SwooleFFINumberTest.php
  76. 16 0
      src/Library/Third/ChineseUtil/tests/unit/SQLiteModeTest.php
  77. 36 0
      src/Library/Third/ChineseUtil/tests/unit/SwooleFFIModeTest.php

+ 1 - 0
src/Library/Third/ChineseUtil/.travis/php.ini

@@ -0,0 +1 @@
+extension=ffi

+ 20 - 0
src/Library/Third/ChineseUtil/LICENSE

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 宇润
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 259 - 0
src/Library/Third/ChineseUtil/README.md

@@ -0,0 +1,259 @@
+# ChineseUtil
+
+PHP 中文工具包,支持汉字转拼音、拼音分词、简繁互转、数字转换、金额数字转换。
+
+由于中文的博大精深,字有多音字,简体字和繁体字也有多种对应。并且本类库返回的所有结果,均为包含所有组合的数组。
+
+本类库字典数据总共收录 73925 个汉字,包括:3955 个简体字,1761 个繁体字,68209 个其它汉字。
+
+## 模式
+
+### 性能模式 (Memory)
+
+使用 SQLite 作为数据载体,一次性加载所有数据到变量,内存占用高(80+ MB),性能最佳。
+
+适合用于运行 Cli 任务。
+
+需要 PDO 和 PDO_SQLITE 扩展支持。
+
+### 通用模式 (SQLite)
+
+使用 SQLite 作为数据载体,每次查询都通过 SQL 查询,内存占用低(100-200 KB),性能中等。
+
+适合用于大部分场景。
+
+需要 PDO 和 PDO_SQLITE 扩展支持。
+
+### 兼容模式 (JSON)
+
+使用精简过的 JSON 数据作为数据载体,一次性加载所有数据到变量,内存占用中(30+ MB),性能差。
+
+> 内存占用量以实际为准,根据版本、扩展等环境的不同,占用的内存容量不一样,上述值为我电脑上的情况,仅供参考。
+
+适合无法使用 PDO 的场景。
+
+由于精简了数据,一些拼音结果需要经过代码计算处理才可以得出,所以性能较差。
+
+### FFI 模式 (FFI)
+
+需要 PHP >= 7.4 并且启用 FFI 扩展,代码全部由 C++ 开发,性能和内存占用都比 PHP 实现的要好。
+
+> FFI 动态库 C++ 代码:<https://github.com/Yurunsoft/chinese-util-cpp>
+
+### Swoole FFI 模式 (SwooleFFI)
+
+需要 PHP >= 7.4 并且启用 FFI、Swoole 扩展,代码全部由 C++ 开发,性能和内存占用都比 PHP 实现的要好。
+
+不会阻塞 PHP 代码所在线程。
+
+---
+
+默认情况下,优先使用通用模式,如果环境不支持 PDO 将采用兼容模式。
+
+你可以在未执行任何初始化或者转换处理之前,设置使用何种模式运行。
+
+```php
+// 设为性能模式
+Chinese::setMode('Memory');
+// 设为通用模式
+Chinese::setMode('SQLite');
+// 设为兼容模式
+Chinese::setMode('JSON');
+// 设为 FFI 模式
+Chinese::setMode('FFI');
+// 设为Swoole FFI 模式
+Chinese::setMode('SwooleFFI');
+```
+
+无论何种模式,拼音分词所需数据总是从 JSON 数据中加载。
+
+FFI 参数设置:(一般用于自己编译的情况)
+
+```php
+use Yurun\Util\Chinese\FFIDriver;
+
+FFIDriver::$library = '.so 文件路径';
+FFIDriver::$characterDataPath = '字符数据文件路径';
+FFIDriver::$pinyinDataPath = '拼音数据文件路径';
+```
+
+## 使用说明
+
+### Composer 直接安装
+
+`composer require yurunsoft/chinese-util`
+
+### Composer 项目配置引入
+
+```
+"require": {
+    "yurunsoft/chinese-util" : "~2.0"
+}
+```
+
+## 功能
+
+### 汉字转拼音
+
+```php
+use \Yurun\Util\Chinese;
+use \Yurun\Util\Chinese\Pinyin;
+$string = '恭喜發財!123';
+echo $string, PHP_EOL;
+
+echo '全拼:', PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN));
+
+echo '首字母:', PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN_FIRST));
+
+echo '读音:', PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN_SOUND));
+
+echo '读音数字:', PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER));
+
+echo '自选返回格式 + 以文本格式返回 + 自定义分隔符:', PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN | Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER, ' '));
+
+echo '所有结果:', PHP_EOL;
+var_dump(Chinese::toPinyin($string));
+
+echo '不分割无拼音字符:', PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN, ' ', false));
+
+// 结果太长,请自行运行代码查看
+```
+
+### 拼音分词
+
+**结果是字符串:**
+
+```php
+use \Yurun\Util\Chinese;
+$string2 = 'xianggang';
+echo '"', $string2, '"的分词结果:', PHP_EOL;
+var_dump(Chinese::splitPinyin($string2));
+```
+
+输出结果:
+
+```shell
+"xianggang"的分词结果:
+array(2) {
+  [0]=>
+  string(11) "xiang gang"
+  [1]=>
+  string(12) "xi ang gang"
+}
+```
+
+**结果是数组:**
+
+```php
+use \Yurun\Util\Chinese;
+$string2 = 'xianggang';
+echo '"', $string2, '"的分词结果:', PHP_EOL;
+var_dump(Chinese::splitPinyinArray($string2));
+```
+
+输出结果:
+
+```shell
+"xianggang"的分词结果:
+array(2) {
+  [0]=>
+  array(2) {
+    [0]=>
+    string(5) "xiang"
+    [1]=>
+    string(4) "gang"
+  }
+  [1]=>
+  array(3) {
+    [0]=>
+    string(2) "xi"
+    [1]=>
+    string(3) "ang"
+    [2]=>
+    string(4) "gang"
+  }
+}
+```
+
+### 简繁互转
+
+```php
+use \Yurun\Util\Chinese;
+$string3 = '中华人民共和国!恭喜發財!';
+echo '"', $string3, '"的简体转换:', PHP_EOL;
+var_dump(Chinese::toSimplified($string3));
+echo '"', $string3, '"的繁体转换:', PHP_EOL;
+var_dump(Chinese::toTraditional($string3));
+```
+
+输出结果:
+
+```shell
+"中华人民共和国!恭喜發財!"的简体转换:
+array(1) {
+  [0]=>
+  string(39) "中华人民共和国!恭喜发财!"
+}
+"中华人民共和国!恭喜發財!"的繁体转换:
+array(1) {
+  [0]=>
+  string(39) "中華人民共和國!恭喜發財!"
+}
+```
+
+### 数字转换
+
+```php
+use Yurun\Util\Chinese\Number;
+function test($number)
+{
+    $chinese = Number::toChinese($number, [
+        'tenMin'    =>  true, // “一十二” => “十二”
+    ]);
+    $afterNumber = Number::toNumber($chinese);
+    echo $number, '=>', $chinese, '=>', $afterNumber, '=>', 0 === bccomp($number, $afterNumber, 20) ? 'true' : 'false', PHP_EOL;
+}
+
+test(1.234);
+test(-1234567890.666);
+test(pi());
+```
+
+输出结果:
+
+```shell
+1.234=>一点二三四=>1.234=>true
+-1234567890.666=>负十二亿三千四百五十六万七千八百九十点六六六=>-1234567890.666=>true
+3.1415926535898=>三点一四一五九二六五三五八九八=>3.1415926535898=>true
+```
+
+### 金额数字转换
+
+```php
+use Yurun\Util\Chinese\Money;
+function test($number)
+{
+    $chinese = Money::toChinese($number, [
+        'tenMin'    =>  true, // “一十二” => “十二”
+    ]);
+    $afterMoney = Money::toNumber($chinese);
+    echo $number, '=>', $chinese, '=>', $afterMoney, '=>', 0 === bccomp($number, $afterMoney) ? 'true' : 'false', PHP_EOL;
+}
+
+test(1.234);
+test(-1234567890.666);
+```
+
+输出结果:
+
+```shell
+输出结果:
+1.234=>壹圆贰角叁分肆厘=>1.234=>true
+-1234567890.666=>负壹拾贰亿叁仟肆佰伍拾陆万柒仟捌佰玖拾圆陆角陆分陆厘=>-1234567890.666=>true
+```

BIN
src/Library/Third/ChineseUtil/clib/chinese_util-php7.4-x64.dll


BIN
src/Library/Third/ChineseUtil/clib/chinese_util-php7.4-x86.dll


+ 1 - 0
src/Library/Third/ChineseUtil/clib/include.h

@@ -0,0 +1 @@
+void init_chinese_util();

BIN
src/Library/Third/ChineseUtil/clib/libchinese_util-php7.4-swoole4.5.dylib


BIN
src/Library/Third/ChineseUtil/clib/libchinese_util-php7.4-swoole4.5.so


BIN
src/Library/Third/ChineseUtil/clib/libchinese_util-php7.4.dylib


BIN
src/Library/Third/ChineseUtil/clib/libchinese_util-php7.4.so


BIN
src/Library/Third/ChineseUtil/clib/libchinese_util-php8.0-swoole4.5.dylib


BIN
src/Library/Third/ChineseUtil/clib/libchinese_util-php8.0-swoole4.5.so


BIN
src/Library/Third/ChineseUtil/clib/libchinese_util-php8.0.dylib


BIN
src/Library/Third/ChineseUtil/clib/libchinese_util-php8.0.so


+ 26 - 0
src/Library/Third/ChineseUtil/composer.json

@@ -0,0 +1,26 @@
+{
+    "name": "yurunsoft/chinese-util",
+    "description": "PHP 中文工具包,支持汉字转拼音、拼音分词、简繁互转、数字转换、金额数字转换。",
+    "type": "library",
+    "license": "MIT",
+    "autoload": {
+        "psr-4": {
+            "Yurun\\Util\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Yurun\\Util\\ChineseUtil\\Test\\": "tests/unit/"
+        }
+    },
+    "require-dev": {
+        "phpunit/phpunit": ">=4"
+    },
+    "scripts": {
+        "test": "./vendor/bin/phpunit -c ./tests/phpunit.xml",
+        "install-test": [
+            "@composer install",
+            "@composer test"
+        ]
+    }
+}

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/Library/Third/ChineseUtil/data/charsData.json


BIN
src/Library/Third/ChineseUtil/data/chineseData.sqlite


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/Library/Third/ChineseUtil/data/pinyinData.json


+ 74 - 0
src/Library/Third/ChineseUtil/demo/money.php

@@ -0,0 +1,74 @@
+<?php
+/**
+ * 中文金额转换示例.
+ */
+require_once dirname(__DIR__) . '/vendor/autoload.php';
+
+use Yurun\Util\Chinese\Money;
+
+function test($number)
+{
+    $chinese = Money::toChinese((string) $number, [
+        'tenMin'    => true, // “一十二” => “十二”
+    ]);
+    $afterMoney = Money::toNumber($chinese);
+    echo $number, '=>', $chinese, '=>', $afterMoney, '=>', 0 === bccomp($number, $afterMoney, 4) ? 'true' : 'false', \PHP_EOL;
+}
+
+/**
+ * 随机生成文本.
+ *
+ * @param string $chars
+ * @param int    $min
+ * @param int    $max
+ *
+ * @return string
+ */
+function text($chars, $min, $max)
+{
+    $length = mt_rand($min, $max);
+    $charLength = mb_strlen($chars);
+    $result = '';
+    for ($i = 0; $i < $length; ++$i)
+    {
+        $result .= mb_substr($chars, mt_rand(1, $charLength) - 1, 1);
+    }
+
+    return $result;
+}
+
+/**
+ * 随机生成数字.
+ *
+ * @param int $min
+ * @param int $max
+ *
+ * @return string
+ */
+function digital($min, $max)
+{
+    return text('0123456789', $min, $max);
+}
+
+$count = 10;
+echo '整数:', \PHP_EOL;
+for ($i = 1; $i <= $count; ++$i)
+{
+    do
+    {
+        $number = ltrim(digital(1, 14), '0');
+    } while ('' == $number);
+    test($number * [1, -1][mt_rand(0, 1)]);
+}
+echo \PHP_EOL;
+echo '小数:', \PHP_EOL;
+for ($i = 1; $i <= $count; ++$i)
+{
+    do
+    {
+        $number = ltrim(digital(1, 14), '0');
+    } while ('' == $number);
+    $number .= '.' . digital(1, 4);
+    test($number * [1, -1][mt_rand(0, 1)]);
+}
+echo \PHP_EOL;

+ 73 - 0
src/Library/Third/ChineseUtil/demo/number.php

@@ -0,0 +1,73 @@
+<?php
+/**
+ * 中文数字转换示例.
+ */
+require_once dirname(__DIR__) . '/vendor/autoload.php';
+
+use Yurun\Util\Chinese\Number;
+
+function test($number)
+{
+    $chinese = Number::toChinese($number, [
+        'tenMin'    => true, // “一十二” => “十二”
+    ]);
+    $afterNumber = Number::toNumber($chinese);
+    echo $number, '=>', $chinese, '=>', $afterNumber, '=>', 0 === bccomp($number, $afterNumber, 20) ? 'true' : 'false', \PHP_EOL;
+}
+
+/**
+ * 随机生成文本.
+ *
+ * @param string $chars
+ * @param int    $min
+ * @param int    $max
+ *
+ * @return string
+ */
+function text($chars, $min, $max)
+{
+    $length = mt_rand($min, $max);
+    $charLength = mb_strlen($chars);
+    $result = '';
+    for ($i = 0; $i < $length; ++$i)
+    {
+        $result .= mb_substr($chars, mt_rand(1, $charLength) - 1, 1);
+    }
+
+    return $result;
+}
+
+/**
+ * 随机生成数字.
+ *
+ * @param int $min
+ * @param int $max
+ *
+ * @return string
+ */
+function digital($min, $max)
+{
+    return text('0123456789', $min, $max);
+}
+$count = 10;
+echo '整数:', \PHP_EOL;
+for ($i = 1; $i <= $count; ++$i)
+{
+    do
+    {
+        $number = ltrim(digital(1, 14), '0');
+    } while ('' == $number);
+    test($number * [1, -1][mt_rand(0, 1)]);
+}
+echo \PHP_EOL;
+echo '小数:', \PHP_EOL;
+for ($i = 1; $i <= $count; ++$i)
+{
+    do
+    {
+        $number = ltrim(digital(1, 14), '0');
+    } while ('' == $number);
+    $number .= '.' . digital(1, 4);
+    test($number * [1, -1][mt_rand(0, 1)]);
+}
+echo \PHP_EOL;

+ 45 - 0
src/Library/Third/ChineseUtil/demo/pinyin.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * 汉字转拼音示例.
+ */
+
+namespace Yurun\Util;
+
+require_once \dirname(__DIR__) . '/vendor/autoload.php';
+use Yurun\Util\Chinese\Pinyin;
+
+$time = microtime(true);
+$mem1 = memory_get_usage();
+
+// 设为性能模式
+// Chinese::setMode('Memory');
+// 性能模式占用内存大,如果提示内存不足,请扩大内存限制
+// ini_set('memory_limit','256M');
+
+// 设为通用模式,支持 PDO_SQLITE 的情况下为默认
+// Chinese::setMode('SQLite');
+
+// 设为兼容模式,不支持 PDO_SQLITE 的情况下为默认
+// Chinese::setMode('JSON');
+
+// 汉字转拼音
+$string = '恭喜發財!把我翻译成拼音看下?123';
+echo $string, \PHP_EOL;
+echo '所有结果:', \PHP_EOL;
+var_dump(Chinese::toPinyin($string));
+echo '全拼:', \PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN));
+echo '首字母:', \PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN_FIRST));
+echo '读音:', \PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN_SOUND));
+echo '读音数字:', \PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER));
+echo '自选返回格式 + 以文本格式返回 + 自定义分隔符:', \PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN | Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER, ' '));
+echo '不分割无拼音字符:', \PHP_EOL;
+var_dump(Chinese::toPinyin($string, Pinyin::CONVERT_MODE_PINYIN, ' ', false));
+
+echo '当前模式:', Chinese::getMode(), \PHP_EOL;
+echo '开始内存:', $mem1, '; 结束内存:', memory_get_usage(), '; 峰值内存:', memory_get_peak_usage(), \PHP_EOL;
+echo '耗时:', microtime(true) - $time, 's', \PHP_EOL;

+ 31 - 0
src/Library/Third/ChineseUtil/demo/pinyinSplit.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * 拼音分词示例.
+ */
+
+namespace Yurun\Util;
+
+require_once \dirname(__DIR__) . '/vendor/autoload.php';
+
+$time = microtime(true);
+$mem1 = memory_get_usage();
+
+// 设为性能模式
+// Chinese::setMode('Memory');
+// 性能模式占用内存大,如果提示内存不足,请扩大内存限制
+// ini_set('memory_limit','256M');
+
+// 设为通用模式,支持 PDO_SQLITE 的情况下为默认
+// Chinese::setMode('SQLite');
+
+// 设为兼容模式,不支持 PDO_SQLITE 的情况下为默认
+// Chinese::setMode('JSON');
+
+// 拼音分词
+$string2 = 'xianggang';
+echo '"', $string2, '"的分词结果:', \PHP_EOL;
+var_dump(Chinese::splitPinyin($string2));
+
+echo '当前模式:', Chinese::getMode(), \PHP_EOL;
+echo '开始内存:', $mem1, '; 结束内存:', memory_get_usage(), '; 峰值内存:', memory_get_peak_usage(), \PHP_EOL;
+echo '耗时:', microtime(true) - $time, 's', \PHP_EOL;

+ 33 - 0
src/Library/Third/ChineseUtil/demo/st.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * 汉字简繁互转示例.
+ */
+
+namespace Yurun\Util;
+
+require_once \dirname(__DIR__) . '/vendor/autoload.php';
+
+$time = microtime(true);
+$mem1 = memory_get_usage();
+
+// 设为性能模式
+// Chinese::setMode('Memory');
+// 性能模式占用内存大,如果提示内存不足,请扩大内存限制
+// ini_set('memory_limit','256M');
+
+// 设为通用模式,支持 PDO_SQLITE 的情况下为默认
+// Chinese::setMode('SQLite');
+
+// 设为兼容模式,不支持 PDO_SQLITE 的情况下为默认
+// Chinese::setMode('JSON');
+
+// 简繁互转
+$string3 = '中华人民共和国!恭喜發財!';
+echo '"', $string3, '"的简体转换:', \PHP_EOL;
+var_dump(Chinese::toSimplified($string3));
+echo '"', $string3, '"的繁体转换:', \PHP_EOL;
+var_dump(Chinese::toTraditional($string3));
+
+echo '当前模式:', Chinese::getMode(), \PHP_EOL;
+echo '开始内存:', $mem1, '; 结束内存:', memory_get_usage(), '; 峰值内存:', memory_get_peak_usage(), \PHP_EOL;
+echo '耗时:', microtime(true) - $time, 's', \PHP_EOL;

+ 168 - 0
src/Library/Third/ChineseUtil/src/Chinese.php

@@ -0,0 +1,168 @@
+<?php
+
+namespace Yurun\Util;
+
+use Yurun\Util\Chinese\Pinyin;
+use Yurun\Util\Chinese\PinyinSplit;
+use Yurun\Util\Chinese\SimplifiedAndTraditional;
+
+class Chinese
+{
+    /**
+     * 是否已初始化.
+     *
+     * @var bool
+     */
+    public static $isInited = false;
+
+    /**
+     * 配置数据.
+     *
+     * @var array
+     */
+    public static $option = [];
+
+    /**
+     * 中文数据.
+     *
+     * @var array
+     */
+    public static $chineseData = [];
+
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    private static $mode;
+
+    /**
+     * 初始化.
+     *
+     * @param array $option 初始化配置
+     *
+     * @return void
+     */
+    public static function init($option = [])
+    {
+        static::$option = $option;
+        if (null === static::$mode)
+        {
+            // 优先使用通用模式,如果环境不支持 PDO 将采用兼容模式。
+            if (\extension_loaded('pdo_sqlite'))
+            {
+                static::setMode('SQLite');
+            }
+            else
+            {
+                static::setMode('JSON');
+            }
+        }
+        static::$isInited = true;
+    }
+
+    /**
+     * 将字符串转换为拼音,非中文原样保留.
+     *
+     * @param string $string
+     * @param int    $mode
+     * @param string $wordSplit
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    public static function toPinyin($string, $mode = Pinyin::CONVERT_MODE_FULL, $wordSplit = null, $splitNotPinyinChar = true)
+    {
+        if (!static::$isInited)
+        {
+            static::init();
+        }
+
+        return Pinyin::toText($string, $mode, $wordSplit, $splitNotPinyinChar);
+    }
+
+    /**
+     * 拼音分词.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public static function splitPinyin($string, $wordSplit = ' ')
+    {
+        return PinyinSplit::split($string, $wordSplit);
+    }
+
+    /**
+     * 拼音分词.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public static function splitPinyinArray($string)
+    {
+        return PinyinSplit::split($string, null);
+    }
+
+    /**
+     * 繁体转简体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public static function toSimplified($string)
+    {
+        if (!static::$isInited)
+        {
+            static::init();
+        }
+
+        return SimplifiedAndTraditional::toSimplified($string);
+    }
+
+    /**
+     * 简体转繁体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public static function toTraditional($string)
+    {
+        if (!static::$isInited)
+        {
+            static::init();
+        }
+
+        return SimplifiedAndTraditional::toTraditional($string);
+    }
+
+    /**
+     * 设置模式.
+     *
+     * @param string $mode
+     *
+     * @return void
+     */
+    public static function setMode($mode)
+    {
+        if (static::$mode !== $mode)
+        {
+            static::$mode = $mode;
+            static::$isInited = false;
+            static::$chineseData = [];
+        }
+    }
+
+    /**
+     * 获取模式.
+     *
+     * @return string
+     */
+    public static function getMode()
+    {
+        return static::$mode;
+    }
+}

+ 25 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Money/BaseInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Money;
+
+interface BaseInterface
+{
+    /**
+     * 中文金额大写转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public function toNumber($text);
+
+    /**
+     * 数字转为中文金额大写.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public function toChinese($number, $options = []);
+}

+ 38 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Money/FFI.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Money;
+
+use Yurun\Util\Chinese\FFIDriver;
+
+class FFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('FFI');
+    }
+
+    /**
+     * 中文金额大写转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public function toNumber($text)
+    {
+        return convert_chinese_to_money($text);
+    }
+
+    /**
+     * 数字转为中文金额大写.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public function toChinese($number, $options = [])
+    {
+        return convert_money_to_chinese($number);
+    }
+}

+ 351 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Money/Memory.php

@@ -0,0 +1,351 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Money;
+
+use Yurun\Util\Chinese\Util;
+
+class Memory implements BaseInterface
+{
+    public static $numberMap = [
+        0   => '零',
+        1   => '壹',
+        2   => '贰',
+        3   => '叁',
+        4   => '肆',
+        5   => '伍',
+        6   => '陆',
+        7   => '柒',
+        8   => '捌',
+        9   => '玖',
+        '-' => '负',
+        '.' => '',
+    ];
+
+    public static $unitMap = [
+        '拾',
+        '佰',
+        '仟',
+        '万',
+        '亿',
+    ];
+
+    public static $moneyUnitMap = [
+        ['圆', '元'],
+        '角',
+        '分',
+        '厘',
+        '毫',
+    ];
+
+    /**
+     * 中文金额大写转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public function toNumber($text)
+    {
+        $length = mb_strlen($text);
+        $number = $partNumber = $lastNum = $decimal = 0;
+        $pom = 1; // 正数或负数,1或-1
+        $isContain = array_filter(static::$moneyUnitMap[0], function ($moneyUnit) use ($text) {
+            return strpos($text, $moneyUnit);
+        });
+        $flattenMoneyUnitMap = array_reduce(static::$moneyUnitMap, function ($result, $value) {
+            return array_merge($result, \is_array($value) ? $value : [$value]);
+        }, []);
+        $isDecimal = !$isContain;
+        $scale = \count(static::$moneyUnitMap) - 1;
+
+        $lastKey = -1;
+        for ($i = 0; $i < $length; ++$i)
+        {
+            $char = mb_substr($text, $i, 1);
+            if (0 === $i && static::$numberMap['-'] === $char)
+            {
+                $pom = -1;
+                continue;
+            }
+
+            $key = array_search($char, static::$numberMap);
+
+            // 小数
+            if ($isDecimal)
+            {
+                ++$i;
+                $unit = mb_substr($text, $i, 1);
+                $unitKey = array_search($unit, $flattenMoneyUnitMap) - 1;
+                if (false === $unitKey)
+                {
+                    --$i;
+                    $decimal .= $key;
+                }
+                else
+                {
+                    $decimal = bcadd($decimal, bcmul($key, bcpow(10, -($unitKey), $scale), $scale), $scale);
+                }
+            }
+            elseif (false === $key)
+            {
+                $key = array_search($char, static::$unitMap);
+
+                if (false === $key)
+                {
+                    $key = array_search($char, $flattenMoneyUnitMap) - 1;
+                    if (false !== $key)
+                    {
+                        $isDecimal = true;
+                        continue;
+                    }
+                    throw new \InvalidArgumentException(sprintf('%s is not a valied chinese number text', $text));
+                }
+
+                // 单位
+                if ($key > 3)
+                {
+                    $tNumber = bcpow(10, (($key - 2) * 4));
+                }
+                else
+                {
+                    $tNumber = bcpow(10, $key + 1);
+                }
+
+                if (null === $lastNum)
+                {
+                    $lastNum = 1;
+                }
+
+                if ($key > 3 || (3 === $key && $partNumber >= 10))
+                {
+                    if ($key < $lastKey)
+                    {
+                        $number = bcadd($number, bcmul(bcadd($partNumber, $lastNum, $scale), $tNumber, $scale), $scale);
+                    }
+                    else
+                    {
+                        $number = bcmul(bcadd($number, bcadd($partNumber, $lastNum, $scale), $scale), $tNumber, $scale);
+                    }
+                    $partNumber = 0;
+                    $lastNum = null;
+                    $lastKey = $key;
+                }
+                else
+                {
+                    $partNumber = bcadd($partNumber, bcmul($lastNum, $tNumber, $scale), $scale);
+                    $lastNum = 0;
+                }
+            }
+            else
+            {
+                $lastNum = $key;
+            }
+        }
+        $result = bcmul(bcadd(bcadd($number, bcadd($partNumber, $lastNum, $scale), $scale), $decimal, $scale), $pom, $scale);
+        if (false === strpos($result, '.'))
+        {
+            return $result;
+        }
+        else
+        {
+            return rtrim(rtrim($result, '0'), '.');
+        }
+    }
+
+    /**
+     * 数字转为中文金额大写.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public function toChinese($number, $options = [])
+    {
+        if (!static::verifyNumber($number))
+        {
+            throw new \InvalidArgumentException(sprintf('%s is not a valied number', $number));
+        }
+
+        list($integer, $decimal) = explode('.', $number . '.');
+
+        if ($integer < 0)
+        {
+            $pom = static::$numberMap['-'];
+            $integer = abs($integer);
+        }
+        else
+        {
+            $pom = '';
+        }
+
+        if ($integer > 0)
+        {
+            return $pom . static::parseInteger($integer, $options) . static::parseDecimal($decimal, $options);
+        }
+        elseif (!$decimal)
+        {
+            return static::$numberMap[0] . static::$moneyUnitMap[0][0];
+        }
+        else
+        {
+            return $pom . static::parseDecimal($decimal, $options);
+        }
+    }
+
+    /**
+     * 验证数值
+     *
+     * @param string $number
+     *
+     * @return bool
+     */
+    public static function verifyNumber($number)
+    {
+        return preg_match('/^-?\d+(\.\d+)?$/', $number) > 0;
+    }
+
+    /**
+     * 处理整数部分.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    private static function parseInteger($number, $options)
+    {
+        // 准备数据,分割为4个数字一组
+        $length = \strlen($number);
+        // 同 % 4
+        $firstItems = $length & 3;
+        $leftStr = substr($number, $firstItems);
+        if ('' === $leftStr || false === $leftStr)
+        {
+            $split4 = [];
+        }
+        else
+        {
+            $split4 = str_split($leftStr, 4);
+        }
+        if ($firstItems > 0)
+        {
+            array_unshift($split4, substr($number, 0, $firstItems));
+        }
+        $split4Count = \count($split4);
+
+        $unitIndex = ($length - 1) / 4 >> 0;
+
+        if (0 === $unitIndex)
+        {
+            $unitIndex = -1;
+        }
+        else
+        {
+            $unitIndex += 2;
+        }
+
+        $result = '';
+        foreach ($split4 as $i => $item)
+        {
+            $index = $unitIndex - $i;
+
+            $length = \strlen($item);
+
+            $itemResult = '';
+            $has0 = false;
+            for ($j = 0; $j < $length; ++$j)
+            {
+                if (0 == $item[$j])
+                {
+                    $has0 = true;
+                }
+                else
+                {
+                    if ($has0)
+                    {
+                        $itemResult .= static::$numberMap[0];
+                        $has0 = false;
+                    }
+                    $itemResult .= static::$numberMap[$item[$j]];
+                    if (isset(static::$unitMap[$length - $j - 2]))
+                    {
+                        $itemResult .= static::$unitMap[$length - $j - 2];
+                    }
+                }
+            }
+            if ($has0)
+            {
+                $itemResult .= static::$numberMap[0];
+            }
+            if ('' === $itemResult)
+            {
+                if (isset(static::$unitMap[$index]))
+                {
+                    if ($index > 3)
+                    {
+                        $result .= static::$unitMap[$index];
+                    }
+                }
+                elseif ('0' != $item)
+                {
+                    $result .= isset(static::$unitMap[$index + 1]) ? static::$unitMap[$index + 1] : str_repeat(static::$unitMap[3], max($index - 3, 0));
+                }
+            }
+            else
+            {
+                if ($i !== $split4Count - 1 && isset(static::$unitMap[$index]))
+                {
+                    $unit = static::$unitMap[$index];
+                }
+                else
+                {
+                    $unit = $index > 4 ? static::$unitMap[3] : '';
+                }
+                $result .= $itemResult . $unit;
+            }
+        }
+        if ('' !== $result)
+        {
+            $result = Util::mbRtrim($result, static::$numberMap[0]);
+            if ('' !== $result)
+            {
+                $result .= static::$moneyUnitMap[0][0];
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * 处理小数部分.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    private static function parseDecimal($number, $options)
+    {
+        if ('' === $number)
+        {
+            return '';
+        }
+        $result = '';
+        $length = \strlen($number);
+        for ($i = 0; $i < $length; ++$i)
+        {
+            if (0 == $number[$i])
+            {
+                $result .= static::$numberMap[$number[$i]];
+            }
+            else
+            {
+                $result .= static::$numberMap[$number[$i]] . (isset(static::$moneyUnitMap[$i + 1]) ? static::$moneyUnitMap[$i + 1] : '');
+            }
+        }
+        $ltrimResult = Util::mbLtrim($result, static::$numberMap[0]);
+
+        return '' === $ltrimResult || $ltrimResult === $result ? $ltrimResult : (static::$numberMap[0] . $ltrimResult);
+    }
+}

+ 38 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Money/SwooleFFI.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Money;
+
+use Yurun\Util\Chinese\FFIDriver;
+
+class SwooleFFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('SwooleFFI');
+    }
+
+    /**
+     * 中文金额大写转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public function toNumber($text)
+    {
+        return swoole_convert_chinese_to_money($text);
+    }
+
+    /**
+     * 数字转为中文金额大写.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public function toChinese($number, $options = [])
+    {
+        return swoole_convert_money_to_chinese($number);
+    }
+}

+ 25 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Number/BaseInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Number;
+
+interface BaseInterface
+{
+    /**
+     * 中文口语化数字转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public function toNumber($text);
+
+    /**
+     * 数字转为中文口语化数字.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public function toChinese($number, $options = []);
+}

+ 38 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Number/FFI.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Number;
+
+use Yurun\Util\Chinese\FFIDriver;
+
+class FFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('FFI');
+    }
+
+    /**
+     * 中文口语化数字转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public function toNumber($text)
+    {
+        return convert_chinese_to_number($text);
+    }
+
+    /**
+     * 数字转为中文口语化数字.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public function toChinese($number, $options = [])
+    {
+        return convert_number_to_chinese($number, isset($options['tenMin']) ? $options['tenMin'] : false);
+    }
+}

+ 259 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Number/Memory.php

@@ -0,0 +1,259 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Number;
+
+class Memory implements BaseInterface
+{
+    public static $numberMap = [
+        0   => '零',
+        1   => '一',
+        2   => '二',
+        3   => '三',
+        4   => '四',
+        5   => '五',
+        6   => '六',
+        7   => '七',
+        8   => '八',
+        9   => '九',
+        '-' => '负',
+        '.' => '点',
+    ];
+
+    public static $unitMap = [
+        '十',
+        '百',
+        '千',
+        '万',
+        '亿',
+        '兆',
+        '京',
+    ];
+
+    /**
+     * 中文口语化数字转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public function toNumber($text)
+    {
+        $length = mb_strlen($text);
+        $number = $partNumber = 0;
+        $pom = 1; // 正数或负数,1或-1
+        $lastNum = 0;
+        $isDecimal = false;
+        $decimal = '';
+        for ($i = 0; $i < $length; ++$i)
+        {
+            $char = mb_substr($text, $i, 1);
+            if (0 === $i && static::$numberMap['-'] === $char)
+            {
+                $pom = -1;
+                continue;
+            }
+            if (static::$numberMap['.'] === $char)
+            {
+                $isDecimal = true;
+                continue;
+            }
+            $key = array_search($char, static::$numberMap);
+            if (false === $key)
+            {
+                $key = array_search($char, static::$unitMap);
+                if (false === $key)
+                {
+                    throw new \InvalidArgumentException(sprintf('%s is not a valied chinese number text', $text));
+                }
+
+                if (0 === $key && 0 === $lastNum)
+                {
+                    $lastNum = 1;
+                }
+
+                // 单位
+                if ($key >= 3)
+                {
+                    $partNumber += $lastNum;
+                    $number += $partNumber * bcpow(10, (($key - 3) * 4) + 4);
+                    $partNumber = 0;
+                }
+                else
+                {
+                    $partNumber += $lastNum * bcpow(10, $key + 1);
+                }
+
+                $lastNum = 0;
+            }
+            else
+            {
+                // 数字
+                if ($isDecimal)
+                {
+                    $decimal .= $key;
+                }
+                else
+                {
+                    $lastNum = $key;
+                }
+            }
+        }
+
+        return bcmul(bcadd($number, bcadd($partNumber, $lastNum)), $pom) . ($isDecimal ? ('.' . $decimal) : '');
+    }
+
+    /**
+     * 数字转为中文口语化数字.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public function toChinese($number, $options = [])
+    {
+        if (!static::verifyNumber($number))
+        {
+            throw new \InvalidArgumentException(sprintf('%s is not a valied number', $number));
+        }
+
+        list($integer, $decimal) = explode('.', $number . '.');
+
+        if ($integer < 0)
+        {
+            $pom = static::$numberMap['-'];
+            $integer = abs($integer);
+        }
+        else
+        {
+            $pom = '';
+        }
+        $integerPart = static::parseInteger($integer, $options);
+        if ('' === $integerPart)
+        {
+            $integerPart = static::$numberMap[0];
+        }
+        $decimalPart = static::parseDecimal($decimal, $options);
+
+        return $pom . $integerPart . $decimalPart;
+    }
+
+    /**
+     * 验证数值
+     *
+     * @param string $number
+     *
+     * @return bool
+     */
+    public static function verifyNumber($number)
+    {
+        return preg_match('/^-?\d+(\.\d+)?$/', $number) > 0;
+    }
+
+    /**
+     * 处理整数部分.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    private static function parseInteger($number, $options)
+    {
+        // “一十二” => “十二”
+        $tenMin = isset($options['tenMin']) ? $options['tenMin'] : false;
+
+        // 准备数据,分割为4个数字一组
+        $length = \strlen($number);
+        // 同 % 4
+        $firstItems = $length & 3;
+        $leftStr = substr($number, $firstItems);
+        if ('' === $leftStr || false === $leftStr)
+        {
+            $split4 = [];
+        }
+        else
+        {
+            $split4 = str_split($leftStr, 4);
+        }
+        if ($firstItems > 0)
+        {
+            array_unshift($split4, substr($number, 0, $firstItems));
+        }
+        $split4Count = \count($split4);
+
+        $unitIndex = ($length - 1) / 4 >> 0;
+        if (0 === $unitIndex)
+        {
+            $unitIndex = -1;
+        }
+        else
+        {
+            $unitIndex += 2;
+        }
+
+        $result = '';
+        foreach ($split4 as $i => $item)
+        {
+            $index = $unitIndex - $i;
+
+            $length = \strlen($item);
+
+            $itemResult = '';
+            $has0 = false;
+            for ($j = 0; $j < $length; ++$j)
+            {
+                if (0 == $item[$j])
+                {
+                    $has0 = true;
+                }
+                else
+                {
+                    if ($has0)
+                    {
+                        $itemResult .= static::$numberMap[0];
+                        $has0 = false;
+                    }
+                    if (!($tenMin && 2 === $length && 0 === $j && 1 == $item[$j]))
+                    {
+                        $itemResult .= static::$numberMap[$item[$j]];
+                    }
+                    if (0 != $item[$j])
+                    {
+                        $itemResult .= (isset(static::$unitMap[$length - $j - 2]) ? static::$unitMap[$length - $j - 2] : '');
+                    }
+                }
+            }
+            if ('' != $itemResult)
+            {
+                $result .= $itemResult . (($i != $split4Count - 1 && isset(static::$unitMap[$index])) ? static::$unitMap[$index] : '');
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * 处理小数部分.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    private static function parseDecimal($number, $options)
+    {
+        if ('' === $number)
+        {
+            return '';
+        }
+        $result = static::$numberMap['.'];
+        $length = \strlen($number);
+        for ($i = 0; $i < $length; ++$i)
+        {
+            $result .= static::$numberMap[$number[$i]];
+        }
+
+        return $result;
+    }
+}

+ 38 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Number/SwooleFFI.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Number;
+
+use Yurun\Util\Chinese\FFIDriver;
+
+class SwooleFFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('SwooleFFI');
+    }
+
+    /**
+     * 中文口语化数字转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public function toNumber($text)
+    {
+        return swoole_convert_chinese_to_number($text);
+    }
+
+    /**
+     * 数字转为中文口语化数字.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public function toChinese($number, $options = [])
+    {
+        return swoole_convert_number_to_chinese($number, isset($options['tenMin']) ? $options['tenMin'] : false);
+    }
+}

+ 62 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/Base.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Pinyin;
+
+use Yurun\Util\Chinese\Pinyin;
+
+abstract class Base implements BaseInterface
+{
+    /**
+     * 把字符串转为拼音结果,返回的数组成员为字符串.
+     *
+     * @param string $string
+     * @param int    $mode
+     * @param string $wordSplit
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    public function convert($string, $mode = Pinyin::CONVERT_MODE_FULL, $wordSplit = null, $splitNotPinyinChar = true)
+    {
+        return $this->parseResult($this->getResult($string, $splitNotPinyinChar), $mode, $wordSplit);
+    }
+
+    /**
+     * 结果去重.
+     *
+     * @param array $array
+     *
+     * @return array
+     */
+    protected function uniqueResult($array)
+    {
+        $newArray = array_map('unserialize', array_unique(array_map('serialize', $array)));
+        if ($array !== $newArray)
+        {
+            $newArray = array_values($newArray);
+        }
+
+        return $newArray;
+    }
+
+    /**
+     * 处理结果.
+     *
+     * @param array  $list
+     * @param int    $mode
+     * @param string $wordSplit
+     *
+     * @return void
+     */
+    abstract protected function parseResult($list, $mode, $wordSplit);
+
+    /**
+     * 把字符串转为拼音数组结果.
+     *
+     * @param string $string
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    abstract protected function getResult($string, $splitNotPinyinChar = true);
+}

+ 20 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/BaseInterface.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Pinyin;
+
+use Yurun\Util\Chinese\Pinyin;
+
+interface BaseInterface
+{
+    /**
+     * 把字符串转为拼音结果,返回的数组成员为字符串.
+     *
+     * @param string $string
+     * @param int    $mode
+     * @param string $wordSplit
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    public function convert($string, $mode = Pinyin::CONVERT_MODE_FULL, $wordSplit = null, $splitNotPinyinChar = true);
+}

+ 36 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/FFI.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Pinyin;
+
+use Yurun\Util\Chinese\FFIDriver;
+use Yurun\Util\Chinese\Pinyin;
+
+class FFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('FFI');
+    }
+
+    /**
+     * 把字符串转为拼音结果,返回的数组成员为字符串.
+     *
+     * @param string $string
+     * @param int    $mode
+     * @param string $wordSplit
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    public function convert($string, $mode = Pinyin::CONVERT_MODE_FULL, $wordSplit = null, $splitNotPinyinChar = true)
+    {
+        if (null === $wordSplit)
+        {
+            return convert_to_pinyin_array($string, $mode, $splitNotPinyinChar);
+        }
+        else
+        {
+            return convert_to_pinyin_string($string, $mode, $splitNotPinyinChar, $wordSplit);
+        }
+    }
+}

+ 254 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/JSON.php

@@ -0,0 +1,254 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Pinyin;
+
+use Yurun\Util\Chinese;
+use Yurun\Util\Chinese\JSONIndex;
+use Yurun\Util\Chinese\Pinyin;
+
+class JSON extends Base
+{
+    use \Yurun\Util\Chinese\Traits\JSONInit;
+
+    public function __construct()
+    {
+        $this->loadChars();
+        $this->loadPinyinSound();
+    }
+
+    /**
+     * 处理结果.
+     *
+     * @param array  $list
+     * @param int    $mode
+     * @param string $wordSplit
+     *
+     * @return void
+     */
+    protected function parseResult($list, $mode, $wordSplit)
+    {
+        $pinyinSounds = [[]];
+        $oldResultCount = null;
+        foreach ($list as $item)
+        {
+            $item[JSONIndex::INDEX_PINYIN] = explode(',', $item[JSONIndex::INDEX_PINYIN]);
+            // 拼音和拼音首字母
+            $count = \count($item[JSONIndex::INDEX_PINYIN]);
+            $oldResultCount = \count($pinyinSounds);
+            $oldResultPinyin = $pinyinSounds;
+            for ($i = 0; $i < $count - 1; ++$i)
+            {
+                $pinyinSounds = array_merge($pinyinSounds, $oldResultPinyin);
+            }
+            foreach ($item[JSONIndex::INDEX_PINYIN] as $index => $pinyin)
+            {
+                for ($i = 0; $i < $oldResultCount; ++$i)
+                {
+                    $j = $index * $oldResultCount + $i;
+                    $pinyinSounds[$j][] = $pinyin;
+                }
+            }
+        }
+
+        $isPinyin = (($mode & Pinyin::CONVERT_MODE_PINYIN) === Pinyin::CONVERT_MODE_PINYIN);
+        $isPinyinSound = (($mode & Pinyin::CONVERT_MODE_PINYIN_SOUND) === Pinyin::CONVERT_MODE_PINYIN_SOUND);
+        $isPinyinSoundNumber = (($mode & Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER) === Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER);
+        $isPinyinFirst = (($mode & Pinyin::CONVERT_MODE_PINYIN_FIRST) === Pinyin::CONVERT_MODE_PINYIN_FIRST);
+        $result = [];
+        if ($isPinyin)
+        {
+            $result['pinyin'] = [];
+        }
+        if ($isPinyinSound)
+        {
+            $result['pinyinSound'] = [[]];
+        }
+        if ($isPinyinSoundNumber)
+        {
+            $result['pinyinSoundNumber'] = [];
+        }
+        if ($isPinyinFirst)
+        {
+            $result['pinyinFirst'] = [];
+        }
+
+        if ((($mode & Pinyin::CONVERT_MODE_PINYIN_SOUND) === Pinyin::CONVERT_MODE_PINYIN_SOUND))
+        {
+            if (null === $wordSplit)
+            {
+                $result['pinyinSound'] = $pinyinSounds;
+            }
+            else
+            {
+                foreach ($pinyinSounds as $pinyinSoundItem)
+                {
+                    $result['pinyinSound'][] = implode($wordSplit, $pinyinSoundItem);
+                }
+            }
+        }
+
+        foreach ($pinyinSounds as $pinyinSound)
+        {
+            $itemResult = $this->parseSoundItem($pinyinSound, $mode);
+            if ($isPinyin)
+            {
+                $result['pinyin'][] = null === $wordSplit ? $itemResult['pinyin'] : implode($wordSplit, $itemResult['pinyin']);
+            }
+            if ($isPinyinSoundNumber)
+            {
+                $result['pinyinSoundNumber'][] = null === $wordSplit ? $itemResult['pinyinSoundNumber'] : implode($wordSplit, $itemResult['pinyinSoundNumber']);
+            }
+            if ($isPinyinFirst)
+            {
+                $result['pinyinFirst'][] = null === $wordSplit ? $itemResult['pinyinFirst'] : implode($wordSplit, $itemResult['pinyinFirst']);
+            }
+        }
+
+        if ($isPinyin)
+        {
+            $result['pinyin'] = $this->uniqueResult($result['pinyin']);
+        }
+        if ($isPinyinSoundNumber)
+        {
+            $result['pinyinSoundNumber'] = $this->uniqueResult($result['pinyinSoundNumber']);
+        }
+        if ($isPinyinFirst)
+        {
+            $result['pinyinFirst'] = $this->uniqueResult($result['pinyinFirst']);
+        }
+
+        return $result;
+    }
+
+    /**
+     * 把字符串转为拼音数组结果.
+     *
+     * @param string $string
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    protected function getResult($string, $splitNotPinyinChar = true)
+    {
+        $len = mb_strlen($string, 'UTF-8');
+        $list = [];
+        $noResultItem = null;
+        for ($i = 0; $i < $len; ++$i)
+        {
+            $word = mb_substr($string, $i, 1, 'UTF-8');
+            if (isset(Chinese::$chineseData['chars'][$word]))
+            {
+                if (!$splitNotPinyinChar && null !== $noResultItem)
+                {
+                    $list[] = $noResultItem;
+                    $noResultItem = null;
+                }
+                $list[] = Chinese::$chineseData['chars'][$word];
+            }
+            else
+            {
+                if ($splitNotPinyinChar)
+                {
+                    $list[] = [
+                        JSONIndex::INDEX_PINYIN => $word,
+                    ];
+                }
+                else
+                {
+                    if (null === $noResultItem)
+                    {
+                        $noResultItem[JSONIndex::INDEX_PINYIN] = '';
+                    }
+                    $noResultItem[JSONIndex::INDEX_PINYIN] .= $word;
+                }
+            }
+        }
+        if (!$splitNotPinyinChar && null !== $noResultItem)
+        {
+            $list[] = $noResultItem;
+        }
+
+        return $list;
+    }
+
+    private function parseSoundItem($array, $mode)
+    {
+        static $pattern, $splitSeparator;
+        if (null === $pattern)
+        {
+            $pattern = '/([' . implode('', array_keys(Chinese::$chineseData['pinyinSound'])) . '])/u';
+        }
+        if (null === $splitSeparator)
+        {
+            $splitSeparator = '/' . uniqid('', true) . '/';
+        }
+        $isPinyin = (($mode & Pinyin::CONVERT_MODE_PINYIN) === Pinyin::CONVERT_MODE_PINYIN);
+        $isPinyinSoundNumber = (($mode & Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER) === Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER);
+        $isPinyinFirst = (($mode & Pinyin::CONVERT_MODE_PINYIN_FIRST) === Pinyin::CONVERT_MODE_PINYIN_FIRST);
+
+        $result = [];
+
+        if ($isPinyin)
+        {
+            $result['pinyin'] = [];
+        }
+        if ($isPinyinSoundNumber)
+        {
+            $result['pinyinSoundNumber'] = [];
+        }
+        if ($isPinyinFirst)
+        {
+            $result['pinyinFirst'] = [];
+        }
+
+        if ($isPinyin)
+        {
+            $result['pinyin'] = explode($splitSeparator, preg_replace_callback(
+                $pattern,
+                function ($matches) {
+                    return Chinese::$chineseData['pinyinSound'][$matches[0]]['ab'];
+                },
+                implode($splitSeparator, $array)
+            ));
+        }
+
+        foreach ($array as $pinyinSoundItem)
+        {
+            if ($isPinyinSoundNumber)
+            {
+                $tone = null;
+                $str = preg_replace_callback(
+                    $pattern,
+                    function ($matches) use (&$tone) {
+                        $tone = Chinese::$chineseData['pinyinSound'][$matches[0]]['tone'];
+
+                        return Chinese::$chineseData['pinyinSound'][$matches[0]]['ab'];
+                    },
+                    $pinyinSoundItem,
+                    1
+                );
+                if (null === $tone)
+                {
+                    $result['pinyinSoundNumber'][] = $str;
+                }
+                else
+                {
+                    $result['pinyinSoundNumber'][] = $str . $tone;
+                }
+            }
+            if ($isPinyinFirst)
+            {
+                $result['pinyinFirst'][] = mb_substr(preg_replace_callback(
+                    $pattern,
+                    function ($matches) {
+                        return Chinese::$chineseData['pinyinSound'][$matches[0]]['ab'];
+                    },
+                    $pinyinSoundItem,
+                    1
+                ), 0, 1);
+            }
+        }
+
+        return $result;
+    }
+}

+ 251 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/Memory.php

@@ -0,0 +1,251 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Pinyin;
+
+use Yurun\Util\Chinese;
+use Yurun\Util\Chinese\Pinyin;
+
+class Memory extends Base
+{
+    use \Yurun\Util\Chinese\Traits\MemoryInit;
+
+    public function __construct()
+    {
+        $this->initData();
+    }
+
+    /**
+     * 处理结果.
+     *
+     * @param array  $list
+     * @param int    $mode
+     * @param string $wordSplit
+     *
+     * @return void
+     */
+    public function parseResult($list, $mode, $wordSplit)
+    {
+        $isPinyin = (($mode & Pinyin::CONVERT_MODE_PINYIN) === Pinyin::CONVERT_MODE_PINYIN);
+        $isPinyinSound = (($mode & Pinyin::CONVERT_MODE_PINYIN_SOUND) === Pinyin::CONVERT_MODE_PINYIN_SOUND);
+        $isPinyinSoundNumber = (($mode & Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER) === Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER);
+        $isPinyinFirst = (($mode & Pinyin::CONVERT_MODE_PINYIN_FIRST) === Pinyin::CONVERT_MODE_PINYIN_FIRST);
+        $result = [];
+        if ($isPinyin)
+        {
+            $result['pinyin'] = [[]];
+        }
+        if ($isPinyinSound)
+        {
+            $result['pinyinSound'] = [[]];
+        }
+        if ($isPinyinSoundNumber)
+        {
+            $result['pinyinSoundNumber'] = [[]];
+        }
+        if ($isPinyinFirst)
+        {
+            $result['pinyinFirst'] = [[]];
+        }
+        foreach ($list as $item)
+        {
+            // 拼音和拼音首字母
+            $count = \count($item['pinyin']);
+            if ($isPinyin || $isPinyinFirst)
+            {
+                $oldResultCount = null;
+                if ($isPinyin)
+                {
+                    $oldResultCount = \count($result['pinyin']);
+                    $oldResultPinyin = $result['pinyin'];
+                }
+                if ($isPinyinFirst)
+                {
+                    if (null === $oldResultCount)
+                    {
+                        $oldResultCount = \count($result['pinyinFirst']);
+                    }
+                    $oldResultPinyinFirst = $result['pinyinFirst'];
+                }
+                for ($i = 0; $i < $count - 1; ++$i)
+                {
+                    if ($isPinyin)
+                    {
+                        $result['pinyin'] = array_merge($result['pinyin'], $oldResultPinyin);
+                    }
+                    if ($isPinyinFirst)
+                    {
+                        $result['pinyinFirst'] = array_merge($result['pinyinFirst'], $oldResultPinyinFirst);
+                    }
+                }
+                foreach ($item['pinyin'] as $index => $pinyin)
+                {
+                    for ($i = 0; $i < $oldResultCount; ++$i)
+                    {
+                        $j = $index * $oldResultCount + $i;
+                        if ($isPinyin)
+                        {
+                            $result['pinyin'][$j][] = $pinyin;
+                        }
+                        if ($isPinyinFirst)
+                        {
+                            $result['pinyinFirst'][$j][] = mb_substr($pinyin, 0, 1);
+                        }
+                    }
+                }
+            }
+            // 拼音读音
+            if ($isPinyinSound || $isPinyinSoundNumber)
+            {
+                $oldResultCount = null;
+                if (isset($item['pinyinSound']))
+                {
+                    $count = \count($item['pinyinSound']);
+                }
+                else
+                {
+                    $count = 0;
+                }
+                if ($isPinyinSound)
+                {
+                    $oldResultCount = \count($result['pinyinSound']);
+                    $oldResultPinyinSound = $result['pinyinSound'];
+                }
+                if ($isPinyinSoundNumber)
+                {
+                    if (null === $oldResultCount)
+                    {
+                        $oldResultCount = \count($result['pinyinSoundNumber']);
+                    }
+                    $oldResultPinyinSoundNumber = $result['pinyinSoundNumber'];
+                }
+                for ($i = 0; $i < $count - 1; ++$i)
+                {
+                    if ($isPinyinSound)
+                    {
+                        $result['pinyinSound'] = array_merge($result['pinyinSound'], $oldResultPinyinSound);
+                    }
+                    if ($isPinyinSoundNumber)
+                    {
+                        $result['pinyinSoundNumber'] = array_merge($result['pinyinSoundNumber'], $oldResultPinyinSoundNumber);
+                    }
+                }
+                for ($index = 0; $index < $count; ++$index)
+                {
+                    for ($i = 0; $i < $oldResultCount; ++$i)
+                    {
+                        $j = $index * $oldResultCount + $i;
+                        if ($isPinyinSound)
+                        {
+                            $result['pinyinSound'][$j][] = $item['pinyinSound'][$index];
+                        }
+                        if ($isPinyinSoundNumber)
+                        {
+                            $result['pinyinSoundNumber'][$j][] = $item['pinyinSoundNumber'][$index];
+                        }
+                    }
+                }
+            }
+        }
+
+        if (null !== $wordSplit)
+        {
+            if ($isPinyin)
+            {
+                foreach ($result['pinyin'] as $index => $value)
+                {
+                    $result['pinyin'][$index] = implode($wordSplit, $value);
+                }
+            }
+            if ($isPinyinSound)
+            {
+                foreach ($result['pinyinSound'] as $index => $value)
+                {
+                    $result['pinyinSound'][$index] = implode($wordSplit, $value);
+                }
+            }
+            if ($isPinyinSoundNumber)
+            {
+                foreach ($result['pinyinSoundNumber'] as $index => $value)
+                {
+                    $result['pinyinSoundNumber'][$index] = implode($wordSplit, $value);
+                }
+            }
+            if ($isPinyinFirst)
+            {
+                foreach ($result['pinyinFirst'] as $index => $value)
+                {
+                    $result['pinyinFirst'][$index] = implode($wordSplit, $value);
+                }
+            }
+        }
+
+        if ($isPinyin)
+        {
+            $result['pinyin'] = $this->uniqueResult($result['pinyin']);
+        }
+        if ($isPinyinSoundNumber)
+        {
+            $result['pinyinSoundNumber'] = $this->uniqueResult($result['pinyinSoundNumber']);
+        }
+        if ($isPinyinFirst)
+        {
+            $result['pinyinFirst'] = $this->uniqueResult($result['pinyinFirst']);
+        }
+
+        return $result;
+    }
+
+    /**
+     * 把字符串转为拼音数组结果.
+     *
+     * @param string $string
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    public function getResult($string, $splitNotPinyinChar = true)
+    {
+        $len = mb_strlen($string, 'UTF-8');
+        $list = [];
+        $noResultItem = null;
+        for ($i = 0; $i < $len; ++$i)
+        {
+            $word = mb_substr($string, $i, 1, 'UTF-8');
+            if (isset(Chinese::$chineseData['chars'][$word]))
+            {
+                if (!$splitNotPinyinChar && null !== $noResultItem)
+                {
+                    $list[] = $noResultItem;
+                    $noResultItem = null;
+                }
+                $list[] = Chinese::$chineseData['chars'][$word];
+            }
+            else
+            {
+                if ($splitNotPinyinChar)
+                {
+                    $list[] = [
+                        'pinyin'            => [$word],
+                        'pinyinSound'       => [$word],
+                        'pinyinSound'       => [$word],
+                        'pinyinSoundNumber' => [$word],
+                    ];
+                }
+                else
+                {
+                    if (null === $noResultItem)
+                    {
+                        $noResultItem['pinyin'][0] = '';
+                    }
+                    $noResultItem['pinyin'][0] .= $word;
+                }
+            }
+        }
+        if (!$splitNotPinyinChar && null !== $noResultItem)
+        {
+            $list[] = $noResultItem;
+        }
+
+        return $list;
+    }
+}

+ 250 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/SQLite.php

@@ -0,0 +1,250 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Pinyin;
+
+use Yurun\Util\Chinese\Pinyin;
+use Yurun\Util\Chinese\SQLiteData;
+
+class SQLite extends Base
+{
+    public function __construct()
+    {
+        SQLiteData::init();
+    }
+
+    /**
+     * 处理结果.
+     *
+     * @param array  $list
+     * @param int    $mode
+     * @param string $wordSplit
+     *
+     * @return void
+     */
+    protected function parseResult($list, $mode, $wordSplit)
+    {
+        $isPinyin = (($mode & Pinyin::CONVERT_MODE_PINYIN) === Pinyin::CONVERT_MODE_PINYIN);
+        $isPinyinSound = (($mode & Pinyin::CONVERT_MODE_PINYIN_SOUND) === Pinyin::CONVERT_MODE_PINYIN_SOUND);
+        $isPinyinSoundNumber = (($mode & Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER) === Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER);
+        $isPinyinFirst = (($mode & Pinyin::CONVERT_MODE_PINYIN_FIRST) === Pinyin::CONVERT_MODE_PINYIN_FIRST);
+        $result = [];
+        if ($isPinyin)
+        {
+            $result['pinyin'] = [[]];
+        }
+        if ($isPinyinSound)
+        {
+            $result['pinyinSound'] = [[]];
+        }
+        if ($isPinyinSoundNumber)
+        {
+            $result['pinyinSoundNumber'] = [[]];
+        }
+        if ($isPinyinFirst)
+        {
+            $result['pinyinFirst'] = [[]];
+        }
+        foreach ($list as $item)
+        {
+            // 拼音和拼音首字母
+            $count = \count($item['pinyin']);
+            if ($isPinyin || $isPinyinFirst)
+            {
+                $oldResultCount = null;
+                if ($isPinyin)
+                {
+                    $oldResultCount = \count($result['pinyin']);
+                    $oldResultPinyin = $result['pinyin'];
+                }
+                if ($isPinyinFirst)
+                {
+                    if (null === $oldResultCount)
+                    {
+                        $oldResultCount = \count($result['pinyinFirst']);
+                    }
+                    $oldResultPinyinFirst = $result['pinyinFirst'];
+                }
+                for ($i = 0; $i < $count - 1; ++$i)
+                {
+                    if ($isPinyin)
+                    {
+                        $result['pinyin'] = array_merge($result['pinyin'], $oldResultPinyin);
+                    }
+                    if ($isPinyinFirst)
+                    {
+                        $result['pinyinFirst'] = array_merge($result['pinyinFirst'], $oldResultPinyinFirst);
+                    }
+                }
+                foreach ($item['pinyin'] as $index => $pinyin)
+                {
+                    for ($i = 0; $i < $oldResultCount; ++$i)
+                    {
+                        $j = $index * $oldResultCount + $i;
+                        if ($isPinyin)
+                        {
+                            $result['pinyin'][$j][] = $pinyin;
+                        }
+                        if ($isPinyinFirst)
+                        {
+                            $result['pinyinFirst'][$j][] = mb_substr($pinyin, 0, 1);
+                        }
+                    }
+                }
+            }
+            // 拼音读音
+            if ($isPinyinSound || $isPinyinSoundNumber)
+            {
+                $oldResultCount = null;
+                if (isset($item['pinyinSound']))
+                {
+                    $count = \count($item['pinyinSound']);
+                }
+                else
+                {
+                    $count = 0;
+                }
+                if ($isPinyinSound)
+                {
+                    $oldResultCount = \count($result['pinyinSound']);
+                    $oldResultPinyinSound = $result['pinyinSound'];
+                }
+                if ($isPinyinSoundNumber)
+                {
+                    if (null === $oldResultCount)
+                    {
+                        $oldResultCount = \count($result['pinyinSoundNumber']);
+                    }
+                    $oldResultPinyinSoundNumber = $result['pinyinSoundNumber'];
+                }
+                for ($i = 0; $i < $count - 1; ++$i)
+                {
+                    if ($isPinyinSound)
+                    {
+                        $result['pinyinSound'] = array_merge($result['pinyinSound'], $oldResultPinyinSound);
+                    }
+                    if ($isPinyinSoundNumber)
+                    {
+                        $result['pinyinSoundNumber'] = array_merge($result['pinyinSoundNumber'], $oldResultPinyinSoundNumber);
+                    }
+                }
+                for ($index = 0; $index < $count; ++$index)
+                {
+                    for ($i = 0; $i < $oldResultCount; ++$i)
+                    {
+                        $j = $index * $oldResultCount + $i;
+                        if ($isPinyinSound)
+                        {
+                            $result['pinyinSound'][$j][] = $item['pinyinSound'][$index];
+                        }
+                        if ($isPinyinSoundNumber)
+                        {
+                            $result['pinyinSoundNumber'][$j][] = $item['pinyinSoundNumber'][$index];
+                        }
+                    }
+                }
+            }
+        }
+
+        if (null !== $wordSplit)
+        {
+            if ($isPinyin)
+            {
+                foreach ($result['pinyin'] as $index => $value)
+                {
+                    $result['pinyin'][$index] = implode($wordSplit, $value);
+                }
+            }
+            if ($isPinyinSound)
+            {
+                foreach ($result['pinyinSound'] as $index => $value)
+                {
+                    $result['pinyinSound'][$index] = implode($wordSplit, $value);
+                }
+            }
+            if ($isPinyinSoundNumber)
+            {
+                foreach ($result['pinyinSoundNumber'] as $index => $value)
+                {
+                    $result['pinyinSoundNumber'][$index] = implode($wordSplit, $value);
+                }
+            }
+            if ($isPinyinFirst)
+            {
+                foreach ($result['pinyinFirst'] as $index => $value)
+                {
+                    $result['pinyinFirst'][$index] = implode($wordSplit, $value);
+                }
+            }
+        }
+
+        if ($isPinyin)
+        {
+            $result['pinyin'] = $this->uniqueResult($result['pinyin']);
+        }
+        if ($isPinyinSoundNumber)
+        {
+            $result['pinyinSoundNumber'] = $this->uniqueResult($result['pinyinSoundNumber']);
+        }
+        if ($isPinyinFirst)
+        {
+            $result['pinyinFirst'] = $this->uniqueResult($result['pinyinFirst']);
+        }
+
+        return $result;
+    }
+
+    /**
+     * 把字符串转为拼音数组结果.
+     *
+     * @param string $string
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    protected function getResult($string, $splitNotPinyinChar = true)
+    {
+        $len = mb_strlen($string, 'UTF-8');
+        $list = [];
+        $noResultItem = null;
+        for ($i = 0; $i < $len; ++$i)
+        {
+            $word = mb_substr($string, $i, 1, 'UTF-8');
+            $data = SQLiteData::getData($word);
+            if (isset($data['char']))
+            {
+                if (!$splitNotPinyinChar && null !== $noResultItem)
+                {
+                    $list[] = $noResultItem;
+                    $noResultItem = null;
+                }
+                $list[] = $data;
+            }
+            else
+            {
+                if ($splitNotPinyinChar)
+                {
+                    $list[] = [
+                        'pinyin'            => [$word],
+                        'pinyinSound'       => [$word],
+                        'pinyinSound'       => [$word],
+                        'pinyinSoundNumber' => [$word],
+                    ];
+                }
+                else
+                {
+                    if (null === $noResultItem)
+                    {
+                        $noResultItem['pinyin'][0] = '';
+                    }
+                    $noResultItem['pinyin'][0] .= $word;
+                }
+            }
+        }
+        if (!$splitNotPinyinChar && null !== $noResultItem)
+        {
+            $list[] = $noResultItem;
+        }
+
+        return $list;
+    }
+}

+ 36 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/Pinyin/SwooleFFI.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\Pinyin;
+
+use Yurun\Util\Chinese\FFIDriver;
+use Yurun\Util\Chinese\Pinyin;
+
+class SwooleFFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('SwooleFFI');
+    }
+
+    /**
+     * 把字符串转为拼音结果,返回的数组成员为字符串.
+     *
+     * @param string $string
+     * @param int    $mode
+     * @param string $wordSplit
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    public function convert($string, $mode = Pinyin::CONVERT_MODE_FULL, $wordSplit = null, $splitNotPinyinChar = true)
+    {
+        if (null === $wordSplit)
+        {
+            return swoole_convert_to_pinyin_array($string, $mode, $splitNotPinyinChar);
+        }
+        else
+        {
+            return swoole_convert_to_pinyin_string($string, $mode, $splitNotPinyinChar, $wordSplit);
+        }
+    }
+}

+ 16 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/PinyinSplit/BaseInterface.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\PinyinSplit;
+
+interface BaseInterface
+{
+    /**
+     * 拼音分词.
+     *
+     * @param string      $text
+     * @param string|null $wordSplit
+     *
+     * @return array
+     */
+    public function split($text, $wordSplit = ' ');
+}

+ 33 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/PinyinSplit/FFI.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\PinyinSplit;
+
+use Yurun\Util\Chinese\FFIDriver;
+
+class FFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('FFI');
+    }
+
+    /**
+     * 拼音分词.
+     *
+     * @param string      $text
+     * @param string|null $wordSplit
+     *
+     * @return array
+     */
+    public function split($text, $wordSplit = ' ')
+    {
+        if (null === $wordSplit)
+        {
+            return split_pinyin_array($text);
+        }
+        else
+        {
+            return split_pinyin_string($text, $wordSplit);
+        }
+    }
+}

+ 203 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/PinyinSplit/Memory.php

@@ -0,0 +1,203 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\PinyinSplit;
+
+use Yurun\Util\Chinese;
+
+class Memory implements BaseInterface
+{
+    public function __construct()
+    {
+        // 拼音分词数据加载
+        if (!isset(Chinese::$option['pinyinSplitData']))
+        {
+            if (!empty(Chinese::$option['pinyinSplitData']))
+            {
+                Chinese::$chineseData['pinyin'] = Chinese::$option['pinyinSplitData'];
+            }
+            elseif (empty(Chinese::$option['pinyinSplitDataPath']))
+            {
+                Chinese::$chineseData['pinyin'] = json_decode(file_get_contents(\dirname(\dirname(\dirname(\dirname(__DIR__)))) . '/data/pinyinData.json'), true)['split'];
+            }
+            else
+            {
+                Chinese::$chineseData['pinyin'] = json_decode(file_get_contents(Chinese::$option['pinyinSplitDataPath']), true)['split'];
+            }
+        }
+    }
+
+    /**
+     * 拼音分词.
+     *
+     * @param string      $text
+     * @param string|null $wordSplit
+     *
+     * @return array
+     */
+    public function split($text, $wordSplit = ' ')
+    {
+        if ('' === $text)
+        {
+            return [];
+        }
+        $this->parseBlock($text, $beginMaps, $endMaps, $length);
+        if (!isset($beginMaps[0]))
+        {
+            throw new \RuntimeException('Data error');
+        }
+        $result = [];
+        $stacks = [
+            [
+                'index'     => 0,
+                'result'    => [[]],
+            ],
+        ];
+        while ($stacks)
+        {
+            $stack = array_pop($stacks);
+            $index = $stack['index'];
+            if (!isset($beginMaps[$index]))
+            {
+                throw new \RuntimeException('Index value error');
+            }
+            foreach ($beginMaps[$index] as $item)
+            {
+                if (!$item['isPinyin'] && isset($endMaps[$index]))
+                {
+                    continue;
+                }
+                $itemNextIndex = $item['end'] + 1;
+                if (!isset($beginMaps[$itemNextIndex]) && $itemNextIndex < $length - 1)
+                {
+                    continue;
+                }
+                $itemResult = [];
+                foreach ($stack['result'] as $resultItem)
+                {
+                    $resultItem[] = $item['text'];
+                    $itemResult[] = $resultItem;
+                }
+                if ($itemNextIndex < $length)
+                {
+                    $stacks[] = [
+                        'index'     => $itemNextIndex,
+                        'result'    => $itemResult,
+                    ];
+                }
+                else
+                {
+                    $result = array_merge($result, $itemResult);
+                }
+            }
+        }
+        if (null !== $wordSplit)
+        {
+            foreach ($result as &$item)
+            {
+                $item = implode($wordSplit, $item);
+            }
+        }
+
+        return $result;
+    }
+
+    private function parseBlock($text, &$beginMaps, &$endMaps, &$length)
+    {
+        // 把每个连续的拼音连成块
+        $blocks = preg_split('/([^a-zA-Z]+)/', $text, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
+        $hasNoPinyinChars = isset($blocks[1]);
+        if ($hasNoPinyinChars)
+        {
+            $oddIsPinyin = preg_match('/^([a-zA-Z]+)$/', $blocks[0]) > 0;
+        }
+
+        $relationList = Chinese::$chineseData['pinyin']['relation'];
+
+        $length = 0;
+        $beginMaps = $endMaps = [];
+        // 遍历每个块
+        foreach ($blocks as $blockIndex => $block)
+        {
+            $blockLength = mb_strlen($block, 'UTF-8');
+            if ($hasNoPinyinChars)
+            {
+                $blockIndexIsOdd = (1 === ($blockIndex & 1));
+                if ($oddIsPinyin === $blockIndexIsOdd)
+                {
+                    $begin = $length;
+                    $length += $blockLength;
+                    $beginMaps[$begin][] = [
+                        'text'      => $block,
+                        'isPinyin'  => false,
+                        'relation'  => null,
+                        'begin'     => $begin,
+                        'end'       => $length - 1,
+                    ];
+                    continue;
+                }
+            }
+            $tempBlockResults = [];
+            // 遍历每个字
+            for ($i = 0; $i < $blockLength; ++$i)
+            {
+                $character = mb_substr($block, $i, 1, 'UTF-8');
+                foreach (array_keys($tempBlockResults) as $j)
+                {
+                    $tempBlockResultItem = &$tempBlockResults[$j];
+                    $relation = &$tempBlockResultItem['relation'];
+                    if (isset($relation[$character]))
+                    {
+                        if ($tempBlockResultItem['isPinyin'])
+                        {
+                            $tempBlockResultItem2 = $tempBlockResultItem;
+                            $tempBlockResultItem2['end'] = $end = $length + $i - 1;
+                            unset($tempBlockResultItem2['relation']);
+                            $beginMaps[$tempBlockResultItem2['begin']][] = $tempBlockResultItem2;
+                            if ($tempBlockResultItem2['isPinyin'])
+                            {
+                                $endMaps[$end] = true;
+                            }
+                        }
+                        $tempBlockResultItem['isPinyin'] = isset($relation[$character]['py']);
+                        $tempBlockResultItem['text'] .= $character;
+                        $tempBlockResultItem['relation'] = &$relation[$character];
+                    }
+                    else
+                    {
+                        // 保存
+                        $tempBlockResultItem['end'] = $end = $length + $i - 1;
+                        unset($tempBlockResultItem['relation']);
+                        $beginMaps[$tempBlockResultItem['begin']][] = $tempBlockResultItem;
+                        if ($tempBlockResultItem['isPinyin'])
+                        {
+                            $endMaps[$end] = true;
+                        }
+                        unset($tempBlockResults[$j]);
+                    }
+                    unset($tempBlockResultItem, $relation);
+                }
+                $tempBlockResults[] = [
+                    'text'      => $character,
+                    'isPinyin'  => isset($relationList[$character]['py']),
+                    'relation'  => &$relationList[$character],
+                    'begin'     => $length + $i,
+                ];
+            }
+            if ($tempBlockResults)
+            {
+                foreach ($tempBlockResults as $tempBlockResultItem)
+                {
+                    // 保存
+                    $tempBlockResultItem['end'] = $end = $length + $i - 1;
+                    unset($tempBlockResultItem['relation']);
+                    $beginMaps[$tempBlockResultItem['begin']][] = $tempBlockResultItem;
+                    if ($tempBlockResultItem['isPinyin'])
+                    {
+                        $endMaps[$end] = true;
+                    }
+                }
+            }
+            $length += $blockLength;
+        }
+    }
+}

+ 33 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/PinyinSplit/SwooleFFI.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\PinyinSplit;
+
+use Yurun\Util\Chinese\FFIDriver;
+
+class SwooleFFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('SwooleFFI');
+    }
+
+    /**
+     * 拼音分词.
+     *
+     * @param string      $text
+     * @param string|null $wordSplit
+     *
+     * @return array
+     */
+    public function split($text, $wordSplit = ' ')
+    {
+        if (null === $wordSplit)
+        {
+            return swoole_split_pinyin_array($text);
+        }
+        else
+        {
+            return swoole_split_pinyin_string($text, $wordSplit);
+        }
+    }
+}

+ 71 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/Base.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\SimplifiedTraditional;
+
+abstract class Base implements BaseInterface
+{
+    /**
+     * 繁体转简体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public function toSimplified($string)
+    {
+        return $this->parseResult($this->getResult($string, 'sc'));
+    }
+
+    /**
+     * 简体转繁体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public function toTraditional($string)
+    {
+        return $this->parseResult($this->getResult($string, 'tc'));
+    }
+
+    /**
+     * 处理结果.
+     *
+     * @param array $list
+     *
+     * @return void
+     */
+    protected function parseResult($list)
+    {
+        $strings = [''];
+        foreach ($list as $pinyins)
+        {
+            $count = \count($pinyins);
+            $oldResultCount = \count($strings);
+            $oldResult = $strings;
+            for ($i = 0; $i < $count - 1; ++$i)
+            {
+                $strings = array_merge($strings, $oldResult);
+            }
+            foreach ($pinyins as $index => $pinyin)
+            {
+                for ($i = 0; $i < $oldResultCount; ++$i)
+                {
+                    $j = $index * $oldResultCount + $i;
+                    $strings[$j] .= $pinyin;
+                }
+            }
+        }
+
+        return $strings;
+    }
+
+    /**
+     * 把字符串转为数组结果.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    abstract protected function getResult($string, $key);
+}

+ 24 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/BaseInterface.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\SimplifiedTraditional;
+
+interface BaseInterface
+{
+    /**
+     * 繁体转简体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public function toSimplified($string);
+
+    /**
+     * 简体转繁体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public function toTraditional($string);
+}

+ 37 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/FFI.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\SimplifiedTraditional;
+
+use Yurun\Util\Chinese\FFIDriver;
+
+class FFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('FFI');
+    }
+
+    /**
+     * 繁体转简体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public function toSimplified($string)
+    {
+        return convert_to_simplified($string);
+    }
+
+    /**
+     * 简体转繁体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public function toTraditional($string)
+    {
+        return convert_to_traditional($string);
+    }
+}

+ 45 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/JSON.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\SimplifiedTraditional;
+
+use Yurun\Util\Chinese;
+
+class JSON extends Base
+{
+    use \Yurun\Util\Chinese\Traits\JSONInit;
+
+    public function __construct()
+    {
+        $this->loadChars();
+    }
+
+    /**
+     * 把字符串转为数组结果.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    protected function getResult($string, $key)
+    {
+        $len = mb_strlen($string, 'UTF-8');
+        $list = [];
+        $index = \constant('\Yurun\Util\Chinese\JSONIndex::INDEX_' . strtoupper($key));
+        for ($i = 0; $i < $len; ++$i)
+        {
+            $word = mb_substr($string, $i, 1, 'UTF-8');
+            if (isset(Chinese::$chineseData['chars'][$word][$index]) && '' !== Chinese::$chineseData['chars'][$word][$index])
+            {
+                $list[] = explode(',', Chinese::$chineseData['chars'][$word][$index]);
+            }
+            else
+            {
+                $list[] = [
+                    $word,
+                ];
+            }
+        }
+
+        return $list;
+    }
+}

+ 44 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/Memory.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\SimplifiedTraditional;
+
+use Yurun\Util\Chinese;
+
+class Memory extends Base
+{
+    use \Yurun\Util\Chinese\Traits\MemoryInit;
+
+    public function __construct()
+    {
+        $this->initData();
+    }
+
+    /**
+     * 把字符串转为数组结果.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    protected function getResult($string, $key)
+    {
+        $len = mb_strlen($string, 'UTF-8');
+        $list = [];
+        for ($i = 0; $i < $len; ++$i)
+        {
+            $word = mb_substr($string, $i, 1, 'UTF-8');
+            if (isset(Chinese::$chineseData['chars'][$word][$key]) && [] !== Chinese::$chineseData['chars'][$word][$key])
+            {
+                $list[] = Chinese::$chineseData['chars'][$word][$key];
+            }
+            else
+            {
+                $list[] = [
+                    $word,
+                ];
+            }
+        }
+
+        return $list;
+    }
+}

+ 43 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/SQLite.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\SimplifiedTraditional;
+
+use Yurun\Util\Chinese\SQLiteData;
+
+class SQLite extends Base
+{
+    public function __construct()
+    {
+        SQLiteData::init();
+    }
+
+    /**
+     * 把字符串转为数组结果.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    protected function getResult($string, $key)
+    {
+        $len = mb_strlen($string, 'UTF-8');
+        $list = [];
+        for ($i = 0; $i < $len; ++$i)
+        {
+            $word = mb_substr($string, $i, 1, 'UTF-8');
+            $data = SQLiteData::getData($word, $key);
+            if (isset($data[$key][0]))
+            {
+                $list[] = $data[$key];
+            }
+            else
+            {
+                $list[] = [
+                    $word,
+                ];
+            }
+        }
+
+        return $list;
+    }
+}

+ 37 - 0
src/Library/Third/ChineseUtil/src/Chinese/Driver/SimplifiedTraditional/SwooleFFI.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Yurun\Util\Chinese\Driver\SimplifiedTraditional;
+
+use Yurun\Util\Chinese\FFIDriver;
+
+class SwooleFFI implements BaseInterface
+{
+    public function __construct()
+    {
+        FFIDriver::getHandler('SwooleFFI');
+    }
+
+    /**
+     * 繁体转简体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public function toSimplified($string)
+    {
+        return swoole_convert_to_simplified($string);
+    }
+
+    /**
+     * 简体转繁体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public function toTraditional($string)
+    {
+        return swoole_convert_to_traditional($string);
+    }
+}

+ 110 - 0
src/Library/Third/ChineseUtil/src/Chinese/FFIDriver.php

@@ -0,0 +1,110 @@
+<?php
+
+namespace Yurun\Util\Chinese;
+
+use FFI as PHPFFI;
+
+class FFIDriver
+{
+    /**
+     * 处理器集合.
+     *
+     * @var static[]
+     */
+    private static $handlers;
+
+    /**
+     * .so 文件路径.
+     *
+     * @var string|null
+     */
+    public static $library;
+
+    /**
+     * 字符数据文件路径.
+     *
+     * @var string|null
+     */
+    public static $characterDataPath;
+
+    /**
+     * 拼音数据文件路径.
+     *
+     * @var string|null
+     */
+    public static $pinyinDataPath;
+
+    /**
+     * FFI 对象
+     *
+     * @var \FFI
+     */
+    public $ffi;
+
+    public function __construct($library = null, $characterDataPath = null, $pinyinDataPath = null)
+    {
+        if (!\extension_loaded('FFI'))
+        {
+            throw new \RuntimeException('If you want to use FFI mode, you must use PHP>=7.4 and enable FFI extension');
+        }
+        $clibPath = \dirname(__DIR__, 2) . '/clib';
+        if (null === $library)
+        {
+            $swooleInstalled = \defined('SWOOLE_VERSION');
+            $phpVersion = \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION;
+            switch (\PHP_OS_FAMILY)
+            {
+                case 'Darwin':
+                    if ($swooleInstalled)
+                    {
+                        $library = "libchinese_util-php{$phpVersion}-swoole4.5.dylib";
+                    }
+                    else
+                    {
+                        $library = "libchinese_util-php{$phpVersion}.dylib";
+                    }
+                    break;
+                case 'Windows':
+                    $library = "chinese_util-php{$phpVersion}-x" . (4 === \PHP_INT_SIZE ? '86' : '64') . '.dll';
+                    break;
+                default:
+                    if ($swooleInstalled)
+                    {
+                        $library = "libchinese_util-php{$phpVersion}-swoole4.5.so";
+                    }
+                    else
+                    {
+                        $library = "libchinese_util-php{$phpVersion}.so";
+                    }
+            }
+            $library = $clibPath . '/' . $library;
+        }
+        $this->ffi = $ffi = PHPFFI::cdef(file_get_contents($clibPath . '/include.h'), $library);
+        $ffi->init_chinese_util();
+        $dataPath = \dirname(__DIR__, 2) . '/data';
+        if (!$characterDataPath)
+        {
+            $characterDataPath = $dataPath . '/charsData.json';
+        }
+        if (!$pinyinDataPath)
+        {
+            $pinyinDataPath = $dataPath . '/pinyinData.json';
+        }
+        init_chinese_dict($characterDataPath, $pinyinDataPath);
+    }
+
+    /**
+     * 获取拼音处理器.
+     *
+     * @return static
+     */
+    public static function getHandler(string $type)
+    {
+        if (!isset(static::$handlers[$type]))
+        {
+            static::$handlers[$type] = new static(static::$library, static::$characterDataPath, static::$pinyinDataPath);
+        }
+
+        return static::$handlers[$type];
+    }
+}

+ 31 - 0
src/Library/Third/ChineseUtil/src/Chinese/JSONIndex.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Yurun\Util\Chinese;
+
+class JSONIndex
+{
+    /**
+     * 数据索引-拼音.
+     */
+    const INDEX_PINYIN = 0;
+
+    /**
+     * 数据索引-对应的简体字.
+     */
+    const INDEX_SC = 1;
+
+    /**
+     * 数据索引-对应的繁体字.
+     */
+    const INDEX_TC = 2;
+
+    /**
+     * 数据索引-是否为简体字.
+     */
+    const INDEX_IS_SC = 3;
+
+    /**
+     * 数据索引-是否为繁体字.
+     */
+    const INDEX_IS_TC = 4;
+}

+ 72 - 0
src/Library/Third/ChineseUtil/src/Chinese/Money.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace Yurun\Util\Chinese;
+
+use Yurun\Util\Chinese;
+
+abstract class Money
+{
+    /**
+     * 处理器.
+     *
+     * @var \Yurun\Util\Chinese\Driver\Money\BaseInterface
+     */
+    public static $handler;
+
+    /**
+     * 处理器的模式.
+     *
+     * @var string
+     */
+    private static $handlerMode = 'Memory';
+
+    /**
+     * 中文金额大写转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public static function toNumber($text)
+    {
+        return static::getHandler()->toNumber($text);
+    }
+
+    /**
+     * 数字转为中文金额大写.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public static function toChinese($number, $options = [])
+    {
+        return static::getHandler()->toChinese($number, $options);
+    }
+
+    /**
+     * 获取处理器.
+     *
+     * @return \Yurun\Util\Chinese\Driver\Money\BaseInterface
+     */
+    protected static function getHandler()
+    {
+        $mode = Chinese::getMode();
+        if (null === static::$handler || $mode !== static::$handlerMode)
+        {
+            if (null === $mode)
+            {
+                $mode = static::$handlerMode;
+            }
+            else
+            {
+                static::$handlerMode = $mode;
+            }
+            $className = '\Yurun\Util\Chinese\Driver\Money\\' . $mode;
+            static::$handler = new $className();
+        }
+
+        return static::$handler;
+    }
+}

+ 72 - 0
src/Library/Third/ChineseUtil/src/Chinese/Number.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace Yurun\Util\Chinese;
+
+use Yurun\Util\Chinese;
+
+abstract class Number
+{
+    /**
+     * 处理器.
+     *
+     * @var \Yurun\Util\Chinese\Driver\Number\BaseInterface
+     */
+    public static $handler;
+
+    /**
+     * 处理器的模式.
+     *
+     * @var string
+     */
+    private static $handlerMode = 'Memory';
+
+    /**
+     * 中文口语化数字转数字.
+     *
+     * @param string $text
+     *
+     * @return string
+     */
+    public static function toNumber($text)
+    {
+        return static::getHandler()->toNumber($text);
+    }
+
+    /**
+     * 数字转为中文口语化数字.
+     *
+     * @param string $number
+     * @param array  $options
+     *
+     * @return string
+     */
+    public static function toChinese($number, $options = [])
+    {
+        return static::getHandler()->toChinese($number, $options);
+    }
+
+    /**
+     * 获取处理器.
+     *
+     * @return \Yurun\Util\Chinese\Driver\Number\BaseInterface
+     */
+    protected static function getHandler()
+    {
+        $mode = Chinese::getMode();
+        if (null === static::$handler || $mode !== static::$handlerMode)
+        {
+            if (null === $mode)
+            {
+                $mode = static::$handlerMode;
+            }
+            else
+            {
+                static::$handlerMode = $mode;
+            }
+            $className = '\Yurun\Util\Chinese\Driver\Number\\' . $mode;
+            static::$handler = new $className();
+        }
+
+        return static::$handler;
+    }
+}

+ 101 - 0
src/Library/Third/ChineseUtil/src/Chinese/Pinyin.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace Yurun\Util\Chinese;
+
+use Yurun\Util\Chinese;
+
+class Pinyin
+{
+    /**
+     * 转换为全拼
+     */
+    const CONVERT_MODE_PINYIN = 1;
+
+    /**
+     * 转换为带声调读音的拼音.
+     */
+    const CONVERT_MODE_PINYIN_SOUND = 2;
+
+    /**
+     * 转换为带声调读音的拼音,但声调表示为数字.
+     */
+    const CONVERT_MODE_PINYIN_SOUND_NUMBER = 4;
+
+    /**
+     * 转换为拼音首字母.
+     */
+    const CONVERT_MODE_PINYIN_FIRST = 8;
+
+    /**
+     * 转换为上面支持的所有类型.
+     */
+    const CONVERT_MODE_FULL = 15;
+
+    /**
+     * 拼音处理器.
+     *
+     * @var \Yurun\Util\Chinese\Driver\Pinyin\BaseInterface
+     */
+    public static $handler;
+
+    /**
+     * 处理器的模式.
+     *
+     * @var string
+     */
+    private static $handlerMode = 'JSON';
+
+    /**
+     * 把字符串转为拼音结果,返回的数组成员为数组.
+     *
+     * @param string $string
+     * @param int    $mode
+     * @param string $wordSplit
+     *
+     * @return array
+     */
+    public static function convert($string, $mode = self::CONVERT_MODE_FULL)
+    {
+        return static::getHandler()->convert($string, $mode);
+    }
+
+    /**
+     * 把字符串转为拼音结果,返回的数组成员为字符串.
+     *
+     * @param string $string
+     * @param int    $mode
+     * @param string $wordSplit
+     * @param bool   $splitNotPinyinChar 分割无拼音字符。如果为true,如123结果分割为['1','2','3'];如果为false,如123结果分割为['123']
+     *
+     * @return array
+     */
+    public static function toText($string, $mode = self::CONVERT_MODE_FULL, $wordSplit = ' ', $splitNotPinyinChar = true)
+    {
+        return static::getHandler()->convert($string, $mode, $wordSplit, $splitNotPinyinChar);
+    }
+
+    /**
+     * 获取拼音处理器.
+     *
+     * @return \Yurun\Util\Chinese\Driver\Pinyin\BaseInterface
+     */
+    protected static function getHandler()
+    {
+        $mode = Chinese::getMode();
+        if (null === static::$handler || $mode !== static::$handlerMode)
+        {
+            if (null === $mode)
+            {
+                $mode = static::$handlerMode;
+            }
+            else
+            {
+                static::$handlerMode = $mode;
+            }
+            $className = '\Yurun\Util\Chinese\Driver\Pinyin\\' . $mode;
+            static::$handler = new $className();
+        }
+
+        return static::$handler;
+    }
+}

+ 71 - 0
src/Library/Third/ChineseUtil/src/Chinese/PinyinSplit.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace Yurun\Util\Chinese;
+
+use Yurun\Util\Chinese;
+
+class PinyinSplit
+{
+    /**
+     * 拼音分词处理器.
+     *
+     * @var \Yurun\Util\Chinese\Driver\PinyinSplit\BaseInterface
+     */
+    public static $handler;
+
+    /**
+     * 处理器的模式.
+     *
+     * @var string
+     */
+    private static $handlerMode = 'Memory';
+
+    /**
+     * 拼音分词.
+     *
+     * @param string      $text
+     * @param string|null $wordSplit
+     *
+     * @return array
+     */
+    public static function split($text, $wordSplit = ' ')
+    {
+        return static::getHandler()->split($text, $wordSplit);
+    }
+
+    /**
+     * 获取拼音处理器.
+     *
+     * @return \Yurun\Util\Chinese\Driver\PinyinSplit\BaseInterface
+     */
+    protected static function getHandler()
+    {
+        $mode = Chinese::getMode();
+        if (null === static::$handler || $mode !== static::$handlerMode)
+        {
+            if (null === $mode)
+            {
+                $mode = static::$handlerMode;
+            }
+            else
+            {
+                static::$handlerMode = $mode;
+            }
+            if (\in_array($mode, [
+                'JSON',
+                'Memory',
+                'SQLite',
+            ]))
+            {
+                $className = '\Yurun\Util\Chinese\Driver\PinyinSplit\Memory';
+            }
+            else
+            {
+                $className = '\Yurun\Util\Chinese\Driver\PinyinSplit\\' . $mode;
+            }
+            static::$handler = new $className();
+        }
+
+        return static::$handler;
+    }
+}

+ 94 - 0
src/Library/Third/ChineseUtil/src/Chinese/SQLiteData.php

@@ -0,0 +1,94 @@
+<?php
+
+namespace Yurun\Util\Chinese;
+
+use Yurun\Util\Chinese;
+
+class SQLiteData
+{
+    public static $pdo;
+
+    public static function init()
+    {
+        if (null === static::$pdo)
+        {
+            if (isset(Chinese::$option['sqliteDbPath']))
+            {
+                $path = Chinese::$option['sqliteDbPath'];
+            }
+            else
+            {
+                $path = \dirname(\dirname(__DIR__)) . '/data/chineseData.sqlite';
+            }
+            static::$pdo = new \PDO('sqlite:' . $path, '', '');
+        }
+    }
+
+    public static function getAllData()
+    {
+        $stmt = static::$pdo->query('select * from chars');
+        if (false === $stmt)
+        {
+            throw new \Exception(implode(' ', static::$pdo->errorInfo()));
+        }
+        $data = $stmt->fetchAll(\PDO::FETCH_ASSOC);
+        $s = \count($data);
+        for ($i = 0; $i < $s; ++$i)
+        {
+            static::parseResultData($data[$i]);
+        }
+
+        return $data;
+    }
+
+    public static function getData($char, $fields = '*')
+    {
+        $stmt = static::$pdo->prepare('select ' . $fields . ' from chars where char = :char limit 1');
+        if (false === $stmt)
+        {
+            throw new \Exception(implode(' ', static::$pdo->errorInfo()));
+        }
+        $stmt->bindValue('char', $char);
+        $result = $stmt->execute();
+        if (false === $result)
+        {
+            throw new \Exception(implode(' ', $stmt->errorInfo()));
+        }
+        $data = $stmt->fetch(\PDO::FETCH_ASSOC);
+        static::parseResultData($data);
+
+        return $data;
+    }
+
+    private static function parseResultData(&$data)
+    {
+        if (isset($data['pinyin']))
+        {
+            $data['pinyin'] = isset($data['pinyin'][0]) ? explode(',', $data['pinyin']) : [];
+        }
+        if (isset($data['pinyinSound']))
+        {
+            $data['pinyinSound'] = isset($data['pinyinSound'][0]) ? explode(',', $data['pinyinSound']) : [];
+        }
+        if (isset($data['pinyinSoundNumber']))
+        {
+            $data['pinyinSoundNumber'] = isset($data['pinyinSoundNumber'][0]) ? explode(',', $data['pinyinSoundNumber']) : [];
+        }
+        if (isset($data['sc']))
+        {
+            $data['sc'] = isset($data['sc'][0]) ? explode(',', $data['sc']) : [];
+        }
+        if (isset($data['tc']))
+        {
+            $data['tc'] = isset($data['tc'][0]) ? explode(',', $data['tc']) : [];
+        }
+        if (isset($data['isSC']))
+        {
+            $data['isSC'] = 1 == $data['isSC'];
+        }
+        if (isset($data['isTC']))
+        {
+            $data['isTC'] = 1 == $data['isTC'];
+        }
+    }
+}

+ 71 - 0
src/Library/Third/ChineseUtil/src/Chinese/SimplifiedAndTraditional.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace Yurun\Util\Chinese;
+
+use Yurun\Util\Chinese;
+
+class SimplifiedAndTraditional
+{
+    /**
+     * 处理器.
+     *
+     * @var \Yurun\Util\Chinese\Driver\SimplifiedTraditional\BaseInterface
+     */
+    public static $handler;
+
+    /**
+     * 处理器的模式.
+     *
+     * @var string
+     */
+    private static $handlerMode = 'Memory';
+
+    /**
+     * 繁体转简体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public static function toSimplified($string)
+    {
+        return static::getHandler()->toSimplified($string);
+    }
+
+    /**
+     * 简体转繁体.
+     *
+     * @param string $string
+     *
+     * @return array
+     */
+    public static function toTraditional($string)
+    {
+        return static::getHandler()->toTraditional($string);
+    }
+
+    /**
+     * 获取处理器.
+     *
+     * @return \Yurun\Util\Chinese\Driver\SimplifiedTraditional\BaseInterface
+     */
+    protected static function getHandler()
+    {
+        $mode = Chinese::getMode();
+        if (null === static::$handler || $mode !== static::$handlerMode)
+        {
+            if (null === $mode)
+            {
+                $mode = static::$handlerMode;
+            }
+            else
+            {
+                static::$handlerMode = $mode;
+            }
+            $className = '\Yurun\Util\Chinese\Driver\SimplifiedTraditional\\' . $mode;
+            static::$handler = new $className();
+        }
+
+        return static::$handler;
+    }
+}

+ 46 - 0
src/Library/Third/ChineseUtil/src/Chinese/Traits/JSONInit.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace Yurun\Util\Chinese\Traits;
+
+use Yurun\Util\Chinese;
+
+trait JSONInit
+{
+    protected function loadChars()
+    {
+        if (!isset(Chinese::$chineseData['chars']))
+        {
+            if (!empty(Chinese::$option['charsData']))
+            {
+                Chinese::$chineseData['chars'] = Chinese::$option['charsData'];
+            }
+            elseif (empty(Chinese::$option['charsDataPath']))
+            {
+                Chinese::$chineseData['chars'] = json_decode(file_get_contents(\dirname(\dirname(\dirname(__DIR__))) . '/data/charsData.json'), true);
+            }
+            else
+            {
+                Chinese::$chineseData['chars'] = json_decode(file_get_contents(Chinese::$option['charsDataPath']), true);
+            }
+        }
+    }
+
+    protected function loadPinyinSound()
+    {
+        if (!isset(Chinese::$chineseData['pinyinSound']))
+        {
+            if (!empty(Chinese::$option['pinyinSoundData']))
+            {
+                Chinese::$chineseData['pinyinSound'] = Chinese::$option['pinyinSoundData'];
+            }
+            elseif (empty(Chinese::$option['pinyinSoundDataPath']))
+            {
+                Chinese::$chineseData['pinyinSound'] = json_decode(file_get_contents(\dirname(\dirname(\dirname(__DIR__))) . '/data/pinyinData.json'), true)['sound'];
+            }
+            else
+            {
+                Chinese::$chineseData['pinyinSound'] = json_decode(file_get_contents(Chinese::$option['pinyinSoundDataPath']), true)['sound'];
+            }
+        }
+    }
+}

+ 37 - 0
src/Library/Third/ChineseUtil/src/Chinese/Traits/MemoryInit.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Yurun\Util\Chinese\Traits;
+
+use Yurun\Util\Chinese;
+use Yurun\Util\Chinese\SQLiteData;
+
+trait MemoryInit
+{
+    protected function initData()
+    {
+        SQLiteData::init();
+        if (!isset(Chinese::$chineseData['chars']))
+        {
+            $data = SQLiteData::getAllData();
+            $this->parseData($data);
+            Chinese::$chineseData['chars'] = $data;
+        }
+    }
+
+    /**
+     * 处理数据.
+     *
+     * @param array $array
+     */
+    protected function parseData(&$array)
+    {
+        $s = \count($array);
+        for ($i = 0; $i < $s; ++$i)
+        {
+            $char = $array[$i]['char'];
+            unset($array[$i]['char']);
+            $array[$char] = $array[$i];
+            unset($array[$i]);
+        }
+    }
+}

+ 16 - 0
src/Library/Third/ChineseUtil/src/Chinese/Util.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace Yurun\Util\Chinese;
+
+abstract class Util
+{
+    public static function mbLtrim($string, $trim_chars = '\s')
+    {
+        return preg_replace('/^[' . $trim_chars . ']*(.*?)$/u', '\\1', $string);
+    }
+
+    public static function mbRtrim($string, $trim_chars = '\s')
+    {
+        return preg_replace('/^(.*?)[' . $trim_chars . ']*$/u', '\\1', $string);
+    }
+}

+ 7 - 0
src/Library/Third/ChineseUtil/tests/bootstrap.php

@@ -0,0 +1,7 @@
+<?php
+
+ini_set('memory_limit', '-1');
+
+require dirname(__DIR__) . '/vendor/autoload.php';
+
+mb_internal_encoding('UTF-8');

+ 16 - 0
src/Library/Third/ChineseUtil/tests/phpunit.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         bootstrap="./bootstrap.php"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false">
+    <testsuites>
+        <testsuite name="ChineseUtil">
+            <directory>unit</directory>
+        </testsuite>
+    </testsuites>
+</phpunit>

+ 348 - 0
src/Library/Third/ChineseUtil/tests/unit/BaseTest.php

@@ -0,0 +1,348 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test;
+
+use PHPUnit\Framework\TestCase;
+use Yurun\Util\Chinese;
+use Yurun\Util\Chinese\Pinyin;
+
+abstract class BaseTest extends TestCase
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode;
+
+    protected function check()
+    {
+    }
+
+    public function testMode()
+    {
+        $this->check();
+        Chinese::setMode($this->mode);
+        $this->assertEquals($this->mode, Chinese::getMode());
+    }
+
+    /**
+     * @testdox pinyin-1
+     *
+     * @return void
+     */
+    public function testPinyin1()
+    {
+        $this->check();
+        $this->assertEquals([
+            'pinyin' => [
+                [
+                    'gong',
+                    'xi',
+                    'fa',
+                    'cai',
+                    '!',
+                    '1',
+                    '2',
+                    '3',
+                ],
+            ],
+            'pinyinSound' => [
+                [
+                    'gōng',
+                    'xǐ',
+                    'fā',
+                    'cái',
+                    '!',
+                    '1',
+                    '2',
+                    '3',
+                ],
+            ],
+            'pinyinSoundNumber' => [
+                [
+                    'gong1',
+                    'xi3',
+                    'fa1',
+                    'cai2',
+                    '!',
+                    '1',
+                    '2',
+                    '3',
+                ],
+            ],
+            'pinyinFirst' => [
+                [
+                    'g',
+                    'x',
+                    'f',
+                    'c',
+                    '!',
+                    '1',
+                    '2',
+                    '3',
+                ],
+            ],
+        ], Chinese::toPinyin('恭喜發財!123'));
+    }
+
+    /**
+     * @testdox pinyin-2
+     *
+     * @return void
+     */
+    public function testPinyin2()
+    {
+        $this->check();
+        $this->assertEquals([
+            'pinyin' => [
+                [
+                    'wo',
+                    'di',
+                ],
+                [
+                    'wo',
+                    'de',
+                ],
+            ],
+            'pinyinSound' => [
+                [
+                    'wǒ',
+                    'dí',
+                ],
+                [
+                    'wǒ',
+                    'dì',
+                ],
+                [
+                    'wǒ',
+                    'de',
+                ],
+            ],
+            'pinyinSoundNumber' => [
+                [
+                    'wo3',
+                    'di2',
+                ],
+                [
+                    'wo3',
+                    'di4',
+                ],
+                [
+                    'wo3',
+                    'de0',
+                ],
+            ],
+            'pinyinFirst' => [
+                [
+                    'w',
+                    'd',
+                ],
+            ],
+        ], Chinese::toPinyin('我的'));
+    }
+
+    /**
+     * @testdox 全拼
+     *
+     * @return void
+     */
+    public function testPinyinAll()
+    {
+        $this->check();
+        $this->assertEquals([
+            'pinyin' => [
+                [
+                    'gong',
+                    'xi',
+                    'fa',
+                    'cai',
+                    '!',
+                    '1',
+                    '2',
+                    '3',
+                ],
+            ], ], Chinese::toPinyin('恭喜發財!123', Pinyin::CONVERT_MODE_PINYIN));
+    }
+
+    /**
+     * @testdox 拼音首字母
+     *
+     * @return void
+     */
+    public function testPinyinFirst()
+    {
+        $this->check();
+        $this->assertEquals([
+            'pinyinFirst' => [
+                [
+                    'g',
+                    'x',
+                    'f',
+                    'c',
+                    '!',
+                    '1',
+                    '2',
+                    '3',
+                ],
+            ],
+        ], Chinese::toPinyin('恭喜發財!123', Pinyin::CONVERT_MODE_PINYIN_FIRST));
+    }
+
+    /**
+     * @testdox 读音
+     *
+     * @return void
+     */
+    public function testPinyinSound()
+    {
+        $this->check();
+        $this->assertEquals([
+            'pinyinSound' => [
+                [
+                    'gōng',
+                    'xǐ',
+                    'fā',
+                    'cái',
+                    '!',
+                    '1',
+                    '2',
+                    '3',
+                ],
+            ],
+        ], Chinese::toPinyin('恭喜發財!123', Pinyin::CONVERT_MODE_PINYIN_SOUND));
+    }
+
+    /**
+     * @testdox 读音数字
+     *
+     * @return void
+     */
+    public function testPinyinSoundNumber()
+    {
+        $this->check();
+        $this->assertEquals([
+            'pinyinSoundNumber' => [
+                [
+                    'gong1',
+                    'xi3',
+                    'fa1',
+                    'cai2',
+                    '!',
+                    '1',
+                    '2',
+                    '3',
+                ],
+            ],
+        ], Chinese::toPinyin('恭喜發財!123', Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER));
+    }
+
+    /**
+     * @testdox 自选返回格式 + 以文本格式返回 + 自定义分隔符
+     *
+     * @return void
+     */
+    public function testPinyinCustom()
+    {
+        $this->check();
+        $this->assertEquals([
+            'pinyin' => [
+                'gong xi fa cai ! 1 2 3',
+            ],
+            'pinyinSoundNumber' => [
+                'gong1 xi3 fa1 cai2 ! 1 2 3',
+            ], ], Chinese::toPinyin('恭喜發財!123', Pinyin::CONVERT_MODE_PINYIN | Pinyin::CONVERT_MODE_PINYIN_SOUND_NUMBER, ' '));
+    }
+
+    /**
+     * @testdox 分割无拼音字符
+     *
+     * @return void
+     */
+    public function testPinyinSplitNoPinyin()
+    {
+        $this->check();
+        $this->assertEquals([
+            'pinyin'  => [
+                'gong-xi-fa-cai-!-1-2-3',
+            ],
+        ], Chinese::toPinyin('恭喜發財!123', Pinyin::CONVERT_MODE_PINYIN, '-'));
+    }
+
+    /**
+     * @testdox 不分割无拼音字符
+     *
+     * @return void
+     */
+    public function testPinyinNotSplitNoPinyin()
+    {
+        $this->check();
+        $this->assertEquals([
+            'pinyin'  => [
+                'gong-xi-fa-cai-!123',
+            ],
+        ], Chinese::toPinyin('恭喜發財!123', Pinyin::CONVERT_MODE_PINYIN, '-', false));
+    }
+
+    /**
+     * @testdox 拼音分词
+     *
+     * @return void
+     */
+    public function testPinyinSplit()
+    {
+        $this->check();
+        $this->assertEquals([
+            ['xiang', 'gang'],
+            ['xi', 'ang', 'gang'],
+        ], Chinese::splitPinyinArray('xianggang'));
+
+        $this->assertEquals([
+            'xiang gang',
+            'xi ang gang',
+        ], Chinese::splitPinyin('xianggang'));
+
+        $this->assertEquals([
+            's b te lang pu s b',
+        ], Chinese::splitPinyin('sbtelangpusb'));
+
+        $this->assertEquals([
+            '啊 xian',
+            '啊 xi an',
+        ], Chinese::splitPinyin('啊xian'));
+
+        $this->assertEquals([
+            'xi 啊 an',
+        ], Chinese::splitPinyin('xi啊an'));
+
+        $this->assertEquals([
+            'xian 啊',
+            'xi an 啊',
+        ], Chinese::splitPinyin('xian啊'));
+
+        $this->assertEquals([
+            '一 xian 二',
+            '一 xi an 二',
+        ], Chinese::splitPinyin('一xian二'));
+
+        $this->assertEquals([
+            '一 xi 二 an 三',
+        ], Chinese::splitPinyin('一xi二an三'));
+
+        $this->assertEquals([], Chinese::splitPinyin(''));
+    }
+
+    /**
+     * @testdox 简繁互转
+     *
+     * @return void
+     */
+    public function testSimplifiedAndTraditional()
+    {
+        $this->check();
+        $simplified = '中华人民共和国!恭喜发财!';
+        $traditional = '中華人民共和國!恭喜發財!';
+        $this->assertEquals([$traditional, '中華人民共和國!恭喜髮財!'], Chinese::toTraditional($simplified));
+        $this->assertEquals([$simplified], Chinese::toSimplified($traditional));
+    }
+}

+ 32 - 0
src/Library/Third/ChineseUtil/tests/unit/FFIModeTest.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test;
+
+/**
+ * @testdox FFI Mode
+ */
+class FFIModeTest extends BaseTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'FFI';
+
+    protected function check()
+    {
+        if ('0' === getenv('CHINESE_UTIL_FFI'))
+        {
+            $this->markTestSkipped('Not test FFI');
+        }
+        if (version_compare(\PHP_VERSION, '7.4', '<'))
+        {
+            $this->markTestSkipped('PHP need >= 7.4');
+        }
+        if (!\extension_loaded('FFI'))
+        {
+            $this->markTestSkipped('You must enable FFI extension');
+        }
+    }
+}

+ 16 - 0
src/Library/Third/ChineseUtil/tests/unit/JSONModeTest.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test;
+
+/**
+ * @testdox JSON Mode
+ */
+class JSONModeTest extends BaseTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'JSON';
+}

+ 16 - 0
src/Library/Third/ChineseUtil/tests/unit/MemoryModeTest.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test;
+
+/**
+ * @testdox Memory Mode
+ */
+class MemoryModeTest extends BaseTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'Memory';
+}

+ 103 - 0
src/Library/Third/ChineseUtil/tests/unit/Money/BaseMoneyTest.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test\Money;
+
+use PHPUnit\Framework\TestCase;
+use Yurun\Util\Chinese;
+use Yurun\Util\Chinese\Money;
+
+/**
+ * @testdox 中文金额转换
+ */
+abstract class BaseMoneyTest extends TestCase
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode;
+
+    protected function check()
+    {
+    }
+
+    public function testMode()
+    {
+        $this->check();
+        Chinese::setMode($this->mode);
+        $this->assertEquals($this->mode, Chinese::getMode());
+    }
+
+    public function testToChinese()
+    {
+        $this->check();
+        // 数字
+        $this->assertEquals('伍圆', Money::toChinese(5));
+        $this->assertEquals('壹拾贰圆', Money::toChinese(12));
+        $this->assertEquals('壹佰贰拾叁圆', Money::toChinese(123));
+        $this->assertEquals('壹仟贰佰叁拾肆圆', Money::toChinese(1234));
+        $this->assertEquals('壹万贰仟叁佰肆拾伍圆', Money::toChinese(12345));
+
+        // 负数
+        $this->assertEquals('负伍圆', Money::toChinese(-5));
+
+        // 小数
+        $this->assertEquals('叁圆壹角肆分壹厘伍毫', Money::toChinese(3.1415));
+        $this->assertEquals('壹角肆分壹厘伍毫', Money::toChinese(0.1415));
+    }
+
+    public function testToNumber()
+    {
+        $this->check();
+        // 数字
+        $this->assertEquals(5, Money::toNumber('伍圆'));
+        $this->assertEquals(5, Money::toNumber('伍元'));
+        $this->assertEquals(12, Money::toNumber('壹拾贰圆'));
+        $this->assertEquals(12, Money::toNumber('壹拾贰元'));
+
+        // 负数
+        $this->assertEquals(-5, Money::toNumber('负伍圆'));
+        $this->assertEquals(-5, Money::toNumber('负伍元'));
+
+        // 小数
+        $this->assertEquals(3.1415, Money::toNumber('叁圆壹角肆分壹厘伍毫'));
+        $this->assertEquals(3.1415, Money::toNumber('叁元壹角肆分壹厘伍毫'));
+        $this->assertEquals(0.1415, Money::toNumber('壹角肆分壹厘伍毫'));
+    }
+
+    public function testIssue8()
+    {
+        $this->check();
+        $this->assertEquals('零圆', Money::toChinese(0));
+        $this->assertEquals('零圆', Money::toChinese('0'));
+        $this->assertEquals('零圆', Money::toChinese('0.0'));
+    }
+
+    public function testIssue9()
+    {
+        $this->check();
+        $this->assertEquals('壹拾贰圆', Money::toChinese('12.0'));
+        $this->assertEquals('壹拾贰圆', Money::toChinese('12.00'));
+    }
+
+    public function testIssue15()
+    {
+        $this->check();
+        $this->assertEquals('叁拾圆', Money::toChinese('30'));
+        $this->assertEquals('叁拾圆', Money::toChinese('30.0'));
+        $this->assertEquals('叁拾圆', Money::toChinese('30.00'));
+
+        $this->assertEquals('叁佰圆', Money::toChinese('300'));
+        $this->assertEquals('叁佰圆', Money::toChinese('300.0'));
+        $this->assertEquals('叁佰圆', Money::toChinese('300.00'));
+
+        $this->assertEquals('叁仟圆', Money::toChinese('3000'));
+        $this->assertEquals('叁仟圆', Money::toChinese('3000.0'));
+        $this->assertEquals('叁仟圆', Money::toChinese('3000.00'));
+
+        $this->assertEquals('叁万圆', Money::toChinese('30000'));
+        $this->assertEquals('叁万圆', Money::toChinese('30000.0'));
+        $this->assertEquals('叁万圆', Money::toChinese('30000.00'));
+    }
+}

+ 32 - 0
src/Library/Third/ChineseUtil/tests/unit/Money/FFIMoneyTest.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test\Money;
+
+/**
+ * @testdox FFI Mode 中文金额转换
+ */
+class FFIMoneyTest extends BaseMoneyTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'FFI';
+
+    protected function check()
+    {
+        if ('0' === getenv('CHINESE_UTIL_FFI'))
+        {
+            $this->markTestSkipped('Not test FFI');
+        }
+        if (version_compare(\PHP_VERSION, '7.4', '<'))
+        {
+            $this->markTestSkipped('PHP need >= 7.4');
+        }
+        if (!\extension_loaded('FFI'))
+        {
+            $this->markTestSkipped('You must enable FFI extension');
+        }
+    }
+}

+ 16 - 0
src/Library/Third/ChineseUtil/tests/unit/Money/MemoryNumberTest.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test\Money;
+
+/**
+ * @testdox Memory Mode 中文金额转换
+ */
+class MemoryNumberTest extends BaseMoneyTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'Memory';
+}

+ 36 - 0
src/Library/Third/ChineseUtil/tests/unit/Money/SwooleFFINumberTest.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test\Money;
+
+/**
+ * @testdox SwooleFFI Mode 中文金额转换
+ */
+class SwooleFFINumberTest extends BaseMoneyTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'SwooleFFI';
+
+    protected function check()
+    {
+        if ('0' === getenv('CHINESE_UTIL_FFI'))
+        {
+            $this->markTestSkipped('Not test FFI');
+        }
+        if (version_compare(\PHP_VERSION, '7.4', '<'))
+        {
+            $this->markTestSkipped('PHP need >= 7.4');
+        }
+        if (!\extension_loaded('FFI'))
+        {
+            $this->markTestSkipped('You must enable FFI extension');
+        }
+        if (!\extension_loaded('Swoole'))
+        {
+            $this->markTestSkipped('You must enable Swoole extension');
+        }
+    }
+}

+ 70 - 0
src/Library/Third/ChineseUtil/tests/unit/Number/BaseNumberTest.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test\Number;
+
+use PHPUnit\Framework\TestCase;
+use Yurun\Util\Chinese;
+use Yurun\Util\Chinese\Number;
+
+/**
+ * @testdox 中文数字转换
+ */
+abstract class BaseNumberTest extends TestCase
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode;
+
+    protected function check()
+    {
+    }
+
+    public function testMode()
+    {
+        $this->check();
+        Chinese::setMode($this->mode);
+        $this->assertEquals($this->mode, Chinese::getMode());
+    }
+
+    public function testToChinese()
+    {
+        $this->check();
+        // 数字
+        $this->assertEquals('五', Number::toChinese(5));
+        $this->assertEquals('一十二', Number::toChinese(12));
+        $this->assertEquals('三十', Number::toChinese(30));
+
+        // 过滤一十二的一
+        $this->assertEquals('五', Number::toChinese(5, [
+            'tenMin'    => true,
+        ]));
+        $this->assertEquals('十二', Number::toChinese(12, [
+            'tenMin'    => true,
+        ]));
+
+        // 负数
+        $this->assertEquals('负五', Number::toChinese(-5));
+
+        // 小数
+        $this->assertEquals('三点一四一五', Number::toChinese(3.1415));
+        $this->assertEquals('零点一四一五', Number::toChinese(0.1415));
+    }
+
+    public function testToNumber()
+    {
+        $this->check();
+        // 数字
+        $this->assertEquals(5, Number::toNumber('五'));
+        $this->assertEquals(12, Number::toNumber('一十二'));
+        $this->assertEquals(12, Number::toNumber('十二'));
+
+        // 负数
+        $this->assertEquals(-5, Number::toNumber('负五'));
+
+        // 小数
+        $this->assertEquals(3.1415, Number::toNumber('三点一四一五'));
+    }
+}

+ 32 - 0
src/Library/Third/ChineseUtil/tests/unit/Number/FFINumberTest.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test\Number;
+
+/**
+ * @testdox FFI Mode 中文数字转换
+ */
+class FFINumberTest extends BaseNumberTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'FFI';
+
+    protected function check()
+    {
+        if ('0' === getenv('CHINESE_UTIL_FFI'))
+        {
+            $this->markTestSkipped('Not test FFI');
+        }
+        if (version_compare(\PHP_VERSION, '7.4', '<'))
+        {
+            $this->markTestSkipped('PHP need >= 7.4');
+        }
+        if (!\extension_loaded('FFI'))
+        {
+            $this->markTestSkipped('You must enable FFI extension');
+        }
+    }
+}

+ 16 - 0
src/Library/Third/ChineseUtil/tests/unit/Number/MemoryNumberTest.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test\Number;
+
+/**
+ * @testdox Memory Mode 中文数字转换
+ */
+class MemoryNumberTest extends BaseNumberTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'Memory';
+}

+ 36 - 0
src/Library/Third/ChineseUtil/tests/unit/Number/SwooleFFINumberTest.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test\Number;
+
+/**
+ * @testdox SwooleFFI Mode 中文数字转换
+ */
+class SwooleFFINumberTest extends BaseNumberTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'SwooleFFI';
+
+    protected function check()
+    {
+        if ('0' === getenv('CHINESE_UTIL_FFI'))
+        {
+            $this->markTestSkipped('Not test FFI');
+        }
+        if (version_compare(\PHP_VERSION, '7.4', '<'))
+        {
+            $this->markTestSkipped('PHP need >= 7.4');
+        }
+        if (!\extension_loaded('FFI'))
+        {
+            $this->markTestSkipped('You must enable FFI extension');
+        }
+        if (!\extension_loaded('Swoole'))
+        {
+            $this->markTestSkipped('You must enable Swoole extension');
+        }
+    }
+}

+ 16 - 0
src/Library/Third/ChineseUtil/tests/unit/SQLiteModeTest.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test;
+
+/**
+ * @testdox SQLite Mode
+ */
+class SQLiteModeTest extends BaseTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'SQLite';
+}

+ 36 - 0
src/Library/Third/ChineseUtil/tests/unit/SwooleFFIModeTest.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Yurun\Util\ChineseUtil\Test;
+
+/**
+ * @testdox Swoole FFI Mode
+ */
+class SwooleFFIModeTest extends BaseTest
+{
+    /**
+     * 模式.
+     *
+     * @var string
+     */
+    protected $mode = 'SwooleFFI';
+
+    protected function check()
+    {
+        if ('0' === getenv('CHINESE_UTIL_FFI'))
+        {
+            $this->markTestSkipped('Not test FFI');
+        }
+        if (version_compare(\PHP_VERSION, '7.4', '<'))
+        {
+            $this->markTestSkipped('PHP need >= 7.4');
+        }
+        if (!\extension_loaded('FFI'))
+        {
+            $this->markTestSkipped('You must enable FFI extension');
+        }
+        if (!\extension_loaded('Swoole'))
+        {
+            $this->markTestSkipped('You must enable Swoole extension');
+        }
+    }
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff