/**
|
* 富文本解析工具
|
* @author sonve
|
* @version 1.0.0
|
* @date 2024-12-04
|
*/
|
|
import config from './config.js'
|
|
/**
|
* 将含有封面占位图形式的视频富文本转换成正常视频的富文本
|
* @param {String} richText 要进行处理的富文本字符串
|
* @returns {String} 返回处理结果
|
*/
|
export function parseHtmlWithVideo(richText) {
|
// 正则表达式匹配<img>标签及其属性
|
const imgRegex = /<img\s+([^>]+)>/gi;
|
// 正则表达式匹配data-custom属性中的url值
|
const customUrlRegex = /\bdata-custom="[^"]*url=([^&"]+)/i;
|
|
return richText.replace(imgRegex, (match, attrs) => {
|
// 查找data-custom属性中的url值
|
const urlMatch = attrs.match(customUrlRegex);
|
if (urlMatch) {
|
// 获取data-custom中的url
|
const videoUrl = urlMatch[1];
|
|
// 解析出所有属性
|
const attrArray = attrs.split(/\s+/).filter(attr => attr.trim() !== '');
|
|
// 过滤掉src属性和data-custom属性
|
const newAttrs = attrArray.filter(attr => !attr.startsWith('src=') && !attr.startsWith('data-custom='))
|
.join(' ');
|
|
// 构建新的video标签,保留原有的其他属性,但去除src和data-custom
|
return `<video controls ${newAttrs}><source src="${videoUrl}" /></video>`;
|
}
|
// 如果没有匹配到data-custom中的url,则保持原样
|
return match;
|
});
|
}
|
|
/**
|
* 带有视频的富文本逆向转换
|
* @description 可自定义处理封面
|
* @param {Promise} richText 要转换的富文本
|
* @param {Function<Promise>} customCallback 自定义处理封面回调,需要return封面图片资源,自带参数为视频地址
|
* @returns {Promise} 转换后的富文本 注意异步处理
|
*/
|
export async function replaceVideoWithImageRender(richText, customCallback) {
|
|
// 正则表达式用于匹配 <video> 标签以及其内部的 <source> 标签
|
const videoRegex = /<video\s+([^>]+)>(.*?)<\/video>/gi;
|
|
// 找到所有的 <video> 标签
|
const matches = [];
|
let match;
|
while ((match = videoRegex.exec(richText)) !== null) {
|
matches.push(match);
|
}
|
|
// 并行处理每个 <video> 标签,生成对应的缩略图
|
const replacements = await Promise.all(
|
matches.map(async (match) => {
|
const [fullMatch, attributes, content] = match;
|
|
// 匹配 <source> 标签中的 src 属性
|
const sourceRegex = /<source\s+[^>]*src="([^">]+)"/i;
|
const matchSource = content.match(sourceRegex);
|
|
let videoUrl = '';
|
if (matchSource && matchSource.length > 1) {
|
videoUrl = matchSource[1];
|
}
|
|
// 生成视频封面图
|
let thumbnailRes
|
if (customCallback) thumbnailRes = await customCallback(videoUrl) // 自定义封面处理
|
if (!thumbnailRes) thumbnailRes = config.video_thumbnail // 无效值则默认封面处理
|
|
// 过滤掉不需要的属性,例如 controls
|
const filteredAttributes = attributes
|
.split(/\s+/)
|
.filter(attr => !attr.startsWith('controls'))
|
.join(' ');
|
|
// 构建新的 img 标签,继承 video 的属性(除了 controls)并添加 data-custom 属性
|
const imgTag = `<img ${filteredAttributes} src="${thumbnailRes}" data-custom="url=${videoUrl}" />`;
|
|
return { fullMatch, imgTag };
|
}));
|
|
// 使用 replacements 替换原始的 <video> 标签
|
let result = richText;
|
for (const { fullMatch, imgTag } of replacements) {
|
result = result.replace(fullMatch, imgTag);
|
}
|
|
return result;
|
}
|
|
/**
|
* 解析出富文本中的图片和视频
|
* @param {String} richText 要解析的富文本
|
* @returns {Array} 图片和视频数组
|
*/
|
export function parseImagesAndVideos(richText) {
|
// 创建一个空数组用于存储图片和视频信息
|
const result = [];
|
|
// 正则表达式匹配 <img> 标签及其属性
|
const imgRegex = /<img\s+[^>]*>/gi;
|
// 匹配属性名和值的正则表达式,改进后的版本可以处理属性名中包含连字符的情况
|
const attrRegex = /(\w+(-\w+)*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>]+))/gi;
|
|
// 找到所有的 <img> 标签
|
const matches = richText.match(imgRegex);
|
// 如果没有找到任何 <img> 标签,返回空数组
|
if (!matches) return [];
|
|
// 遍历所有的 <img> 标签
|
matches.forEach(match => {
|
// 创建一个对象用于存储单个图片或视频的信息
|
const ivInfo = {};
|
// 使用正则表达式匹配每个 <img> 标签的属性
|
let attrsMatch;
|
while ((attrsMatch = attrRegex.exec(match)) !== null) {
|
// 属性名
|
const name = attrsMatch[1].toLowerCase();
|
// 属性值可能存在于第三、第四或第五个捕获组中
|
let value = attrsMatch[3] || attrsMatch[4] || attrsMatch[5] || '';
|
|
// 去除属性值两端可能存在的引号
|
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
value = value.substring(1, value.length - 1);
|
}
|
|
// 将属性名和值添加到 ivInfo 对象中
|
ivInfo[name] = value;
|
}
|
// 将单个图片或视频信息添加到数组中
|
result.push(ivInfo);
|
});
|
|
// 返回包含所有图片和视频信息的数组
|
return result;
|
}
|
|
/**
|
* 解析出富文本中的图片
|
* @param {String} richText 要解析的富文本
|
* @returns {Array} 图片数组
|
*/
|
export function parseImages(richText) {
|
let result = []
|
const ivList = parseImagesAndVideos(richText)
|
ivList.forEach(item => {
|
if (!item['data-custom'] || !item['data-custom'].startsWith('url')) {
|
result.push(item)
|
}
|
})
|
return result
|
}
|
|
/**
|
* 解析出富文本中的视频
|
* @param {String} richText 要解析的富文本
|
* @returns {Array} 视频数组
|
*/
|
export function parseVideos(richText) {
|
let result = []
|
const ivList = parseImagesAndVideos(richText)
|
ivList.forEach(item => {
|
if (item['data-custom'] && item['data-custom'].startsWith('url')) {
|
result.push(item)
|
}
|
})
|
return result
|
}
|