Skip to content

特殊Unicode字符处理

作者:guo-zi-xin
更新于:几秒前
字数统计:809 字
阅读时长:3 分钟
typescript
/**
 * 删除文本中的特殊Unicode字符配置选项
 */
export interface RemoveSpecialCharsOptions {
    /** 是否删除零宽字符,默认 true */
    removeZeroWidth?: boolean;
    /** 是否删除BOM字符,默认 true */
    removeBOM?: boolean;
    /** 是否删除其他不可见字符,默认 true */
    removeInvisible?: boolean;
    /** 是否删除私人使用区字符,默认 true */
    removePrivateUse?: boolean;
    /** 是否删除方向格式化字符,默认 true */
    removeDirectionalFormatting?: boolean;
    /** 是否处理转义字符序列(如 \n, \t 等),默认不启用,后续看需求决定是否启用 */
    processEscapeSequences?: boolean;
    /** 是否删除特殊空白符,默认 true */
    removeSpecialSpaces?: boolean;
    /** 自定义要删除的额外字符正则表达式 */
    customPatterns?: RegExp[];
}

/**
 * 删除文本中的特殊Unicode字符
 * @param text 原始文本
 * @param options 配置选项
 * @returns 清理后的文本
 */
export function removeSpecialUnicodeChars(text: string, options: RemoveSpecialCharsOptions = {}): string {
    if (typeof text !== 'string') {
        return text;
    }

    if (!text) {
        return text;
    }

    // 合并默认选项
    const config: Required<Omit<RemoveSpecialCharsOptions, 'customPatterns'>> & { customPatterns: RegExp[] } = {
        removeZeroWidth: true,
        removeBOM: true,
        removeInvisible: true,
        removePrivateUse: true,
        removeDirectionalFormatting: true,
        // TODO:转义字符的处理,默认不启用,后续看需求决定是否启用
        processEscapeSequences: false,
        removeSpecialSpaces: true,
        customPatterns: [],
        ...options
    };

    let result = text;

    // 第一步:处理转义字符序列(如果启用)
    if (config.processEscapeSequences) {
        const escapeSequences: { [key: string]: string } = {
            '\\\\n': '\n',   // 换行符
            '\\\\t': '\t',   // 制表符
            '\\\\r': '\r',   // 回车符
            '\\\\b': '\b',   // 退格符
            '\\\\f': '\f',   // 换页符
            '\\\\v': '\v',   // 垂直制表符
            '\\\\0': '\0',   // 空字符
            '\\\\\'': '\'',  // 单引号
            '\\\\"': '"',    // 双引号
            '\\\\\\\\': '\\' // 反斜杠
        };

        for (const [escapeSeq, actualChar] of Object.entries(escapeSequences)) {
            const regex = new RegExp(escapeSeq, 'g');
            result = result.replace(regex, actualChar);
        }
    }

    // 定义特殊字符的正则表达式部分
    const regexParts: string[] = [];

    if (config.removeZeroWidth) {
        // 零宽字符
        regexParts.push(
            '\u200b-\u200d' +  // U+200B~U+200D: 零宽空格、非连接符、连接符
            '\u2060' +         // U+2060: 词连接符
            '\u2061-\u2064'    // U+2061~U+2064: 数学函数应用符等
        );
    }

    if (config.removeBOM) {
        // BOM字符
        regexParts.push('\ufeff'); // U+FEFF: 字节顺序标记
    }

    if (config.removeDirectionalFormatting) {
        // 方向格式化字符
        regexParts.push(
            '\u061c' +         // U+061C: 阿拉伯文字母标记
            '\u200e\u200f' +   // U+200E~U+200F: 左右标记
            '\u202a-\u202e' +  // U+202A~U+202E: 方向嵌入和覆盖
            '\u2066-\u2069'    // U+2066~U+2069: 双向文本隔离符
        );
    }

    if (config.removeSpecialSpaces) {
        // 特殊空白符
        regexParts.push(
            '\u00a0' +         // U+00A0: 不换行空格
            '\u2007' +         // U+2007: 数字空格
            '\u2009' +         // U+2009: 窄空格
            '\u202f' +         // U+202F: 窄不换行空格
            '\u3000'           // U+3000: 表意文字空格(全角空格)
        );
    }

    if (config.removeInvisible) {
        // 其他不可见字符
        regexParts.push(
            '\u0000-\u001f' +  // U+0000~U+001F: C0控制字符
            '\u007f-\u009f' +  // U+007F~U+009F: 删除键和C1控制字符
            '\u00ad' +         // U+00AD: 软连字符
            '\u034f' +         // U+034F: 组合 grapheme joiner
            '\u115f-\u1160' +  // U+115F~U+1160: 韩文字母填充符
            '\u17b4-\u17b5' +  // U+17B4~U+17B5: 高棉语元音符号
            '\u180b-\u180e' +  // U+180B~U+180E: 蒙古文格式控制
            '\u2060-\u2064' +  // U+2060~U+2064: 词连接符等(已在其他类别中)
            '\u2066-\u206f' +  // U+2066~U+206F: 格式控制符(已在其他类别中)
            '\u3164' +         // U+3164: 韩文字母填充符
            '\ufe00-\ufe0f' +  // U+FE00~U+FE0F: 变体选择符
            '\ufff0-\ufff8' +  // U+FFF0~U+FFF8: 特殊用途
            '\ufff9-\ufffb' +  // U+FFF9~U+FFFB: 间注格式控制
            '\ufffc-\ufffd'    // U+FFFC~U+FFFD: 对象替换字符
        );
    }

    if (config.removePrivateUse) {
        // 私人使用区字符
        regexParts.push('\ue000-\uf8ff');  // U+E000~U+F8FF: 基本多文种平面PUA
    }

    // 处理Unicode特殊字符
    if (regexParts.length > 0) {
        try {
            const regex = new RegExp(`[${regexParts.join('')}]`, 'g');
            result = result.replace(regex, '');
        } catch (error) {
            console.warn('正则表达式构建失败:', error);
        }
    }

    // 处理自定义模式
    if (config.customPatterns.length > 0) {
        for (const pattern of config.customPatterns) {
            result = result.replace(pattern, '');
        }
    }

    return result;
}

上次更新:

人生没有捷径,就像到二仙桥必须要走成华大道。