鸿蒙HTML富文本解析
·
解析格式
-
带链接的font标签格式:<font color="#FFBB56"><a href="verb 运动">exercise</a></font>
-
解析普通font标签格式:<font color="#FFBB56">冠词</font>
-
高亮文本
// 解析结果项
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(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/ /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
}));
}
更多推荐



所有评论(0)