/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { StringUtil } from '../../common/utils/StringUtil';
import i18n from '@ohos.i18n';
import { ANONYMOUS_SORT, ANONYMOUS_SORT_FIRST_LETTER } from '../../serivce/ContactsDataService';
const TAG = 'ChineseCharacterUtils';
// 双框架无名氏首字母,与单框架不同,双框架三个点为一个字符
export const ANONYMOUS_SORT_FIRST_LETTER_DOUBLE = '…';
const PINYIN_MAP: Map<string, string> = new Map<string, string>([
['ā', 'a'],
['á', 'a'],
['ǎ', 'a'],
['à', 'a'],
['ō', 'o'],
['ó', 'o'],
['ǒ', 'o'],
['ò', 'o'],
['ē', 'e'],
['é', 'e'],
['ě', 'e'],
['è', 'e'],
['ī', 'i'],
['í', 'i'],
['ǐ', 'i'],
['ì', 'i'],
['ū', 'u'],
['ú', 'u'],
['ǔ', 'u'],
['ù', 'u'],
['ǖ', 'v'],
['ǘ', 'v'],
['ǚ', 'v'],
['ǜ', 'v']
]);
const MULTI_PINYIN: Map<string, string> = new Map<string, string>([
['\u8983', 'qin'], // qin tan
['\u6c88', 'shen'], // SHEN yang
['\u66fe', 'zeng'], // ZENG zu fu
['\u8d3e', 'jia'], // JIA
['\u4fde', 'yu'],
['\u513F', 'er'],
['\u5475', 'he'],
['\u957f', 'chang'],
['\u7565', 'lue'],
['\u63a0', 'lue'],
['\u4e7e', 'qian'], // qian
['\u79d8', 'bi'],
['\u8584', 'bo'],
['\u79cd', 'chong'],
['\u891a', 'chu'],
['\u555c', 'chuai'],
['\u53e5', 'gou'],
['\u839e', 'guan'],
['\u7094', 'gui'],
['\u85c9', 'ji'],
['\u5708', 'juan'],
['\u89d2', 'jue'],
['\u961a', 'kan'],
['\u9646', 'lu'],
['\u7f2a', 'miao'],
['\u4f74', 'nai'],
['\u5152', 'ni'],
['\u4e5c', 'nie'],
['\u533a', 'ou'],
['\u6734', 'piao'],
['\u7e41', 'po'],
['\u4ec7', 'qiu'],
['\u5355', 'shan'],
['\u76db', 'sheng'],
['\u6298', 'she'],
['\u5bbf', 'su'],
['\u6d17', 'xian'],
['\u89e3', 'xie'],
['\u5458', 'yun'],
['\u7b2e', 'ze'],
['\u7fdf', 'zhai'],
['\u796d', 'zhai'],
['\u963f', 'a'],
['\u5b93', 'fu'],
['\u90a3', 'na'],
['\u5c09', 'yu'],
['\u86fe', 'yi'],
['\u67e5', 'zha'],
['\u5200', 'dao']
]);
const LETTER_UP_A_CODE = 65;
const LETTER_UP_Z_CODE = 90;
const LETTER_LOW_A_CODE = 97;
const LETTER_LOW_Z_CODE = 122;
const MIN_CHINESE_CODE = 19968;
const MAX_CHINESE_CODE = 40869;
const MIN_CHINESE_CODE_16 = 0x4E00;
const MAX_CHINESE_CODE_16 = 0x9FCF;
const RANGE_1_MAX_LATIN_CODE_16 = 0x250;
const RANGE_2_MIN_LATIN_CODE_16 = 0x1e00;
const RANGE_2_MAX_LATIN_CODE_16 = 0x1eff;
const LATIN = 1;
const UNKNOWN = 3;
const ASCII_MAX_NUM = 128;
const SPACE = ' ';
const DEFAULT_SORT_LETTER = '#';
export default class ChineseCharacterUtils {
private static getTransHanToLatinLocal(): i18n.Transliterator {
if (ChineseCharacterUtils.transHanToLatinLocal == null) {
ChineseCharacterUtils.transHanToLatinLocal = i18n.Transliterator.getInstance('Han-Latin/Names');
}
return ChineseCharacterUtils.transHanToLatinLocal;
}
private static getTransLatinToAsciiLocal(): i18n.Transliterator {
if (ChineseCharacterUtils.transLatinToAsciiLocal == null) {
ChineseCharacterUtils.transLatinToAsciiLocal = i18n.Transliterator.getInstance('Latin-Ascii');
}
return ChineseCharacterUtils.transLatinToAsciiLocal;
}
/**
* i18n.Transliterator 对象,用完需要置空
*/
public static destoryTransliterator() {
ChineseCharacterUtils.transHanToLatinLocal == null;
ChineseCharacterUtils.transLatinToAsciiLocal == null;
}
private static transHanToLatinLocal: i18n.Transliterator | null = null;
private static transLatinToAsciiLocal: i18n.Transliterator | null = null;
public static getPhotoFirstName(name: string): string {
if (StringUtil.isEmpty(name)) {
return '';
}
for (let index = 0; index < name.length; index++) {
const letter = name[index];
let code = letter.charCodeAt(0);
if (code >= MIN_CHINESE_CODE && code <= MAX_CHINESE_CODE) {
return letter;
}
}
const charCode = name.charCodeAt(0);
if ((charCode >= LETTER_UP_A_CODE && charCode <= LETTER_UP_Z_CODE) ||
(charCode >= LETTER_LOW_A_CODE && charCode <= LETTER_LOW_Z_CODE)) {
return name.charAt(0);
}
return '';
}
/**
* 根据sortKey 获取sort信息
* @param name
* @returns
*/
public static getSort(name: string): string {
// 如果名称为空,说明是无名氏,应该返回99
if (StringUtil.isEmpty(name)) {
return ANONYMOUS_SORT;
}
const code = name.charCodeAt(0);
if ((code >= LETTER_UP_A_CODE && code <= LETTER_UP_Z_CODE) ||
(code >= LETTER_LOW_A_CODE && code <= LETTER_LOW_Z_CODE)) {
return code.toString();
}
return '-1';
}
// 根据排序首字母,获取sort信息
public static getSortBySortFirstLetter(name: string): string {
if (StringUtil.isEmpty(name)) {
return '-1';
}
// 排序首字母为三个点,返回99
if (name === ANONYMOUS_SORT_FIRST_LETTER_DOUBLE || name === ANONYMOUS_SORT_FIRST_LETTER) {
return ANONYMOUS_SORT;
}
const code = name.charCodeAt(0);
if ((code >= LETTER_UP_A_CODE && code <= LETTER_UP_Z_CODE) ||
(code >= LETTER_LOW_A_CODE && code <= LETTER_LOW_Z_CODE)) {
return code.toString();
}
return '-1';
}
public static getSearchInfo(name: string): string {
if (StringUtil.isEmpty(name)) {
return '';
}
let pinyin = '';
let initials = '';
for (let index = 0; index < name.length; index++) {
const letter = name[index];
let code = letter.charCodeAt(0);
if (code < MIN_CHINESE_CODE || code > MAX_CHINESE_CODE) {
initials += letter;
pinyin += letter;
continue;
}
let pyLetter: string | undefined = undefined;
if (index === 0 && MULTI_PINYIN.has(letter)) {
pyLetter = MULTI_PINYIN.get(letter);
}
if (!pyLetter) {
pyLetter = ChineseCharacterUtils.removePinyinTones(
ChineseCharacterUtils.getTransLatinToAsciiLocal().transform(
ChineseCharacterUtils.getTransHanToLatinLocal().transform(letter)));
}
initials += pyLetter.charAt(0);
pinyin += pyLetter;
}
return `${name}||${pinyin}||${initials}`;
}
public static removePinyinTones(pinyin: string): string {
if (StringUtil.isEmpty(pinyin)) {
return '';
}
let result = '';
for (let index = 0; index < pinyin.length; index++) {
let letter = pinyin[index];
let temp = PINYIN_MAP.get(letter);
result += temp ? temp : letter;
}
return result;
}
private static appendSortKey(sortKey: string, pinYin: string, hanZi: string) {
if (sortKey.length > 0) {
sortKey = (sortKey + SPACE);
}
sortKey = (sortKey + pinYin.toUpperCase() + SPACE + hanZi);
return sortKey;
}
private static appendSameTypeStr(sortKey: string, sameTypeStr: string) {
if (sameTypeStr.length > 0) {
if (sortKey.length > 0) {
sortKey = (sortKey + SPACE);
}
sortKey = (sortKey + sameTypeStr.toUpperCase());
return sortKey;
}
return sortKey;
}
public static getSortKey(name: string) {
let sortKey = '';
if (StringUtil.isEmpty(name)) {
return sortKey;
}
try {
const trimName = name.trimStart();
if (trimName.length === 0) {
return sortKey;
}
// 相同类型的字符串,如 “aaa啊”格式,“aaa”为相同类型字符串,需要转为“aaa a 啊”形式
let curHandleSameTypeStr = '';
let curHandleType = LATIN;
let index: number = -1;
for (const letter of trimName) {
// 处理的字符的编码值
let code = letter.charCodeAt(0);
index += 1;
// 中文处理
if (code >= MIN_CHINESE_CODE_16 && code <= MAX_CHINESE_CODE_16) {
// 中文处理前的同类型字符串加入到sortkey
sortKey = ChineseCharacterUtils.appendSameTypeStr(sortKey, curHandleSameTypeStr);
curHandleSameTypeStr = '';
let pinYin = '';
if (index == 0) {
// 处理姓氏多音字
pinYin = ChineseCharacterUtils.getPinYinByMulti(letter);
} else {
pinYin = ChineseCharacterUtils.getPinYinByHanZi(letter);
}
sortKey = ChineseCharacterUtils.appendSortKey(sortKey, pinYin, letter);
continue;
}
// 如果是ASCII,转换后字符和原始相同;
if (code < ASCII_MAX_NUM) {
// 不是LATIN类型的加入转换
if (curHandleType != LATIN) {
sortKey = ChineseCharacterUtils.appendSameTypeStr(sortKey, curHandleSameTypeStr);
curHandleSameTypeStr = '';
}
// sameTypeStr 当前处理为为LATIN类型字符串,字符加入到字符串
curHandleType = LATIN;
curHandleSameTypeStr = (curHandleSameTypeStr + letter);
continue;
}
// 如果是latin,也作为ascii,转换为对应的ascii
// Latin-Ascii
if (code < RANGE_1_MAX_LATIN_CODE_16 ||
(RANGE_2_MIN_LATIN_CODE_16 <= code && code < RANGE_2_MAX_LATIN_CODE_16)) {
// 不是LATIN类型的加入转换
if (curHandleType != LATIN) {
sortKey = ChineseCharacterUtils.appendSameTypeStr(sortKey, curHandleSameTypeStr);
curHandleSameTypeStr = '';
}
// sameTypeStr 为LATIN类型字符串
// Extended Latin. Transcode these to ASCII equivalents.
curHandleType = LATIN;
curHandleSameTypeStr = (curHandleSameTypeStr +
ChineseCharacterUtils.getTransLatinToAsciiLocal().transform(letter));
continue;
}
// 可能是中文字符,中文字符作为中文处理的,转为 targetstr+“ ”+sourcestr
// 。,“ ? 等等
let asciiStr = ChineseCharacterUtils.getTransLatinToAsciiLocal().transform(letter);
// 字符串不相同,作为中文处理
if (letter != asciiStr) {
sortKey = ChineseCharacterUtils.appendSameTypeStr(sortKey, curHandleSameTypeStr);
curHandleSameTypeStr = '';
sortKey = ChineseCharacterUtils.appendSortKey(sortKey, asciiStr, letter);
continue;
}
// 其他类型
if (curHandleType != UNKNOWN) {
sortKey = ChineseCharacterUtils.appendSameTypeStr(sortKey, curHandleSameTypeStr);
curHandleSameTypeStr = '';
}
curHandleType = UNKNOWN;
curHandleSameTypeStr = (curHandleSameTypeStr + letter);
}
sortKey = ChineseCharacterUtils.appendSameTypeStr(sortKey, curHandleSameTypeStr);
} catch (e) {
console.error(`${TAG} getSortKey err,name:${name},msg:${e.message}`)
}
return sortKey;
}
private static getPinYinByHanZi(text: string) {
let pinYin = PINYIN_MAP.get(text);
if (pinYin) {
return pinYin;
}
return ChineseCharacterUtils.getTransLatinToAsciiLocal().transform(
ChineseCharacterUtils.getTransHanToLatinLocal().transform(text));
}
private static getPinYinByMulti(letter: string): string {
if (MULTI_PINYIN.has(letter)) {
const pinyin = MULTI_PINYIN.get(letter);
if (pinyin !== undefined) {
return pinyin;
}
}
return ChineseCharacterUtils.getPinYinByHanZi(letter);
}
public static getSortKeyOld(name: string): string {
if (StringUtil.isEmpty(name)) {
return '';
}
let currentStr = '';
let sortKey = '';
try {
const trimName = name.trimStart();
if (trimName.length === 0) {
return '';
}
for (let index = 0; index < trimName.length; index++) {
const letter = trimName[index];
let code = letter.charCodeAt(0);
//非中文不需要转拼音
if (code < MIN_CHINESE_CODE || code > MAX_CHINESE_CODE) {
currentStr += letter.toUpperCase();
continue;
}
if (currentStr) {
sortKey += currentStr + SPACE;
currentStr = '';
}
let pyLetter: string | undefined = undefined;
if (index === 0 && MULTI_PINYIN.has(letter)) {
pyLetter = MULTI_PINYIN.get(letter);
}
if (!pyLetter) {
pyLetter = ChineseCharacterUtils.removePinyinTones(
ChineseCharacterUtils.getTransLatinToAsciiLocal().transform(
ChineseCharacterUtils.getTransHanToLatinLocal().transform(letter)));
}
sortKey += pyLetter.toUpperCase() + SPACE + letter + SPACE;
}
if (currentStr && currentStr.length) {
sortKey += currentStr;
}
} catch (e) {
console.error(`${TAG} getSortKey err,name:${name},msg:${e.message}`)
}
return sortKey;
}
public static getSortFirstLetter(name: string): string {
if (StringUtil.isEmpty(name)) {
return DEFAULT_SORT_LETTER;
}
if (name[0] >= 'a' && name[0] <= 'z') {
return name[0].toUpperCase();
}
if (name[0] >= 'A' && name[0] <= 'Z') {
return name[0];
}
return DEFAULT_SORT_LETTER;
}
}