解析格式

  1. 带链接的font标签格式:<font color="#FFBB56"><a href="verb 运动">exercise</a></font>
  2. 解析普通font标签格式:<font color="#FFBB56">冠词</font>
  3. 高亮文本
// 解析结果项
export interface ParsedTextItem {
  text: string; // 文本内容
  isHighlighted: boolean; // 是否高亮
  color?: string; // 高亮颜色
  meaning?: string; // 词义(如果有)
  language?: 'zh' | 'en'; // 语言标识
  isKeyword: boolean; // 是否为关键词
}

// 解析配置选项
export interface ParseOptions {
  supportChinese?: boolean; // 是否支持中文解析
  supportEnglish?: boolean; // 是否支持英文解析
  detectLanguage?: boolean; // 是否自动检测语言
  removeHtmlTags?: boolean; // 是否移除HTML标签
  preserveWhitespace?: boolean; // 是否保留空白字符
}

// 默认解析配置
const DEFAULT_OPTIONS: ParseOptions = {
  supportChinese: true,
  supportEnglish: true,
  detectLanguage: true,
  removeHtmlTags: true,
  preserveWhitespace: false
};

export class RichTextParser {
  /**
   * 解析富文本内容
   * @param richText 富文本字符串
   * @param options 解析配置选项
   * @returns 解析后的文本项数组
   */
  static parse(richText: string, options: ParseOptions = {}): ParsedTextItem[] {
    if (!richText) {
      return [];
    }

    // 修复对象展开语法问题
    const config: ParseOptions = {
      supportChinese: options.supportChinese ?? DEFAULT_OPTIONS.supportChinese,
      supportEnglish: options.supportEnglish ?? DEFAULT_OPTIONS.supportEnglish,
      detectLanguage: options.detectLanguage ?? DEFAULT_OPTIONS.detectLanguage,
      removeHtmlTags: options.removeHtmlTags ?? DEFAULT_OPTIONS.removeHtmlTags,
      preserveWhitespace: options.preserveWhitespace ?? DEFAULT_OPTIONS.preserveWhitespace
    };

    const result: ParsedTextItem[] = [];

    // 首先尝试解析带链接的font标签格式
    // 格式:<font color="#FFBB56"><a href="verb 锻炼">exercise</a></font>
    const linkedFontRegex = /<font\s+color="([^"]+)"><a\s+href="([^"]+)">([^<]+)<\/a><\/font>/g;
    let lastIndex = 0;
    let match: RegExpExecArray | null;

    while ((match = linkedFontRegex.exec(richText)) !== null) {
      // 添加关键词前的普通文本
      if (match.index > lastIndex) {
        const beforeText = richText.substring(lastIndex, match.index);
        const cleanText = RichTextParser.cleanHtmlTags(beforeText, config);
        if (cleanText.trim()) {
          result.push({
            text: cleanText,
            isHighlighted: false,
            isKeyword: false,
            language: RichTextParser.detectLanguage(cleanText, config)
          });
        }
      }

      // 添加关键词
      const color = match[1];
      const meaning = match[2];
      const keyword = match[3];

      result.push({
        text: keyword,
        isHighlighted: true,
        color: color,
        meaning: meaning,
        isKeyword: true,
        language: RichTextParser.detectLanguage(keyword, config)
      });

      lastIndex = linkedFontRegex.lastIndex;
    }

    // 如果没有找到带链接的格式,尝试解析普通font标签格式
    // 格式:<font color="#FFBB56">冠词</font>
    if (result.length === 0) {
      const fontRegex = /<font\s+color="([^"]+)">([^<]+)<\/font>/g;
      lastIndex = 0;

      while ((match = fontRegex.exec(richText)) !== null) {
        // 添加高亮前的文本
        if (match.index > lastIndex) {
          const beforeText = richText.substring(lastIndex, match.index);
          const cleanText = RichTextParser.cleanHtmlTags(beforeText, config);
          if (cleanText.trim()) {
            result.push({
              text: cleanText,
              isHighlighted: false,
              isKeyword: false,
              language: RichTextParser.detectLanguage(cleanText, config)
            });
          }
        }

        // 添加高亮文本
        result.push({
          text: match[2],
          isHighlighted: true,
          color: match[1],
          isKeyword: true,
          language: RichTextParser.detectLanguage(match[2], config)
        });

        lastIndex = match.index + match[0].length;
      }
    }

    // 添加剩余的普通文本
    if (lastIndex < richText.length) {
      const remainingText = richText.substring(lastIndex);
      const cleanText = RichTextParser.cleanHtmlTags(remainingText, config);
      if (cleanText.trim()) {
        result.push({
          text: cleanText,
          isHighlighted: false,
          isKeyword: false,
          language: RichTextParser.detectLanguage(cleanText, config)
        });
      }
    }

    return result;
  }

  /**
   * 清理HTML标签
   * @param text 原始文本
   * @param config 配置选项
   * @returns 清理后的文本
   */
  private static cleanHtmlTags(text: string, config: ParseOptions): string {
    if (!config.removeHtmlTags) {
      return text;
    }

    // 移除所有HTML标签
    let cleanText = text.replace(/<[^>]*>/g, '');

    // 处理HTML实体
    cleanText = cleanText
      .replace(/&amp;/g, '&')
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')
      .replace(/&quot;/g, '"')
      .replace(/&#39;/g, "'")
      .replace(/&nbsp;/g, ' ');

    // 处理空白字符
    if (!config.preserveWhitespace) {
      cleanText = cleanText.replace(/\s+/g, ' ').trim();
    }

    return cleanText;
  }

  /**
   * 检测文本语言
   * @param text 文本内容
   * @param config 配置选项
   * @returns 语言标识
   */
  private static detectLanguage(text: string, config: ParseOptions): 'zh' | 'en' | undefined {
    if (!config.detectLanguage) {
      return undefined;
    }

    // 简单的语言检测逻辑
    const chineseRegex = /[\u4e00-\u9fff]/;
    const englishRegex = /[a-zA-Z]/;

    const hasChinese = chineseRegex.test(text);
    const hasEnglish = englishRegex.test(text);

    if (hasChinese && !hasEnglish) {
      return 'zh';
    } else if (hasEnglish && !hasChinese) {
      return 'en';
    } else if (hasChinese && hasEnglish) {
      // 混合语言,根据主要语言判断
      const chineseCount = (text.match(chineseRegex) || []).length;
      const englishCount = (text.match(/[a-zA-Z]/g) || []).length;
      return chineseCount > englishCount ? 'zh' : 'en';
    }

    return undefined;
  }

  /**
   * 解析纯文本为关键词高亮格式
   * @param text 纯文本
   * @param keywords 关键词列表
   * @param highlightColor 高亮颜色
   * @returns 解析后的文本项数组
   */
  static parseWithKeywords(text: string, keywords: string[], highlightColor: string = '#FFBB56'): ParsedTextItem[] {
    if (!text || !keywords || keywords.length === 0) {
      return [{
        text: text,
        isHighlighted: false,
        isKeyword: false,
        language: RichTextParser.detectLanguage(text, { detectLanguage: true })
      }];
    }

    const result: ParsedTextItem[] = [];
    let lastIndex = 0;

    // 按关键词长度排序,优先匹配长关键词
    const sortedKeywords = [...keywords].sort((a, b) => b.length - a.length);

    // 构建正则表达式
    const keywordRegex =
      new RegExp(`(${sortedKeywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})`, 'gi');

    let match: RegExpExecArray | null;
    while ((match = keywordRegex.exec(text)) !== null) {
      // 添加关键词前的文本
      if (match.index > lastIndex) {
        const beforeText = text.substring(lastIndex, match.index);
        if (beforeText.trim()) {
          result.push({
            text: beforeText,
            isHighlighted: false,
            isKeyword: false,
            language: RichTextParser.detectLanguage(beforeText, { detectLanguage: true })
          });
        }
      }

      // 添加关键词
      result.push({
        text: match[1],
        isHighlighted: true,
        color: highlightColor,
        isKeyword: true,
        language: RichTextParser.detectLanguage(match[1], { detectLanguage: true })
      });

      lastIndex = match.index + match[1].length;
    }

    // 添加剩余文本
    if (lastIndex < text.length) {
      const remainingText = text.substring(lastIndex);
      if (remainingText.trim()) {
        result.push({
          text: remainingText,
          isHighlighted: false,
          isKeyword: false,
          language: RichTextParser.detectLanguage(remainingText, { detectLanguage: true })
        });
      }
    }

    return result;
  }

  /**
   * 将解析结果转换为纯文本
   * @param items 解析后的文本项数组
   * @returns 纯文本字符串
   */
  static toPlainText(items: ParsedTextItem[]): string {
    return items.map(item => item.text).join('');
  }

  /**
   * 获取所有关键词
   * @param items 解析后的文本项数组
   * @returns 关键词数组
   */
  static getKeywords(items: ParsedTextItem[]): string[] {
    return items
      .filter(item => item.isKeyword)
      .map(item => item.text);
  }

  /**
   * 获取指定语言的文本项
   * @param items 解析后的文本项数组
   * @param language 语言标识
   * @returns 指定语言的文本项数组
   */
  static getItemsByLanguage(items: ParsedTextItem[], language: 'zh' | 'en'): ParsedTextItem[] {
    return items.filter(item => item.language === language);
  }
} 
页面使用方法
  // 根据片段索引获取富文本
  private getRichTextByClipIndex(clipIndex: number): string {
    if (!this.showSubtitle || clipIndex < 0 || clipIndex >= this.clips.length) {
      return '';
    }
    const clip = this.clips[clipIndex];
    return clip.richText || clip.text || clip.spokenText || '';
  }

  // 解析richText,提取关键词信息
  private parseRichText(richText: string): TextPart[] {
    if (!richText) {
      return [];
    }
    
    // 使用新的 RichTextParser 工具类
    const parsedItems = RichTextParser.parse(richText, {
      detectLanguage: true,
      removeHtmlTags: true,
      preserveWhitespace: false
    });
    
    // 转换为 TextPart 格式以保持兼容性
    return parsedItems.map((item: ParsedTextItem): TextPart => ({
      text: item.text,
      isKeyword: item.isKeyword,
      color: item.color,
      meaning: item.meaning,
      language: item.language,
      isHighlighted: item.isHighlighted
    }));
  }

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐