188 lines
6.2 KiB
JavaScript
188 lines
6.2 KiB
JavaScript
/**
|
||
* 富文本解析工具
|
||
* @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>`;
|
||
return `<video controls ${newAttrs} src="${videoUrl}" width="100%" ></video>`;
|
||
}
|
||
// 如果没有匹配到data-custom中的url,则保持原样
|
||
return match;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 带有视频的富文本逆向转换
|
||
* @description 可自定义处理封面
|
||
* @param {Promise} richText 要转换的富文本
|
||
* @param {Function<Promise>} customCallback 自定义处理封面回调,需要return封面图片资源,自带参数为视频地址
|
||
* @returns {Promise} 转换后的富文本 注意异步处理
|
||
*/
|
||
export async function replaceVideoWithImageRender(richText, customCallback) {
|
||
console.log(1);
|
||
// 正则表达式用于匹配 <video> 标签以及其内部的 <source> 标签
|
||
const videoRegex = /<video\s+([^>]+)>(.*?)<\/video>/gi;
|
||
console.log(2);
|
||
// 找到所有的 <video> 标签
|
||
const matches = [];
|
||
let match;
|
||
while ((match = videoRegex.exec(richText)) !== null) {
|
||
matches.push(match);
|
||
}
|
||
console.log(3);
|
||
// 并行处理每个 <video> 标签,生成对应的缩略图
|
||
const replacements = await Promise.all(
|
||
|
||
matches.map(async (match) => {
|
||
console.log(5);
|
||
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];
|
||
}
|
||
|
||
// 生成视频封面图
|
||
console.log('5-1')
|
||
let thumbnailRes
|
||
//if (customCallback) thumbnailRes = await customCallback(videoUrl)
|
||
console.log('5-2')
|
||
// 自定义封面处理
|
||
//if (!thumbnailRes) thumbnailRes = config.video_thumbnail // 无效值则默认封面处理
|
||
console.log('5-3')
|
||
thumbnailRes="https://caseplatform.oss-cn-beijing.aliyuncs.com/prod/static/shipinfengmian.jpg"
|
||
// 过滤掉不需要的属性,例如 controls
|
||
const filteredAttributes = attributes
|
||
.split(/\s+/)
|
||
.filter(attr => !attr.startsWith('controls'))
|
||
.join(' ').replace('src=','').replace("").replaceAll('width="100%"','').replaceAll('"','').replace(/\s+/g, "");;
|
||
console.log('5-4')
|
||
// 构建新的 img 标签,继承 video 的属性(除了 controls)并添加 data-custom 属性
|
||
const imgTag = `<img src="${thumbnailRes}" data-custom="url=${filteredAttributes}">`;
|
||
console.log(6);
|
||
return { fullMatch, imgTag };
|
||
}));
|
||
console.log(7);
|
||
// 使用 replacements 替换原始的 <video> 标签
|
||
let result = richText;
|
||
for (const { fullMatch, imgTag } of replacements) {
|
||
console.log(8);
|
||
result = result.replace(fullMatch, imgTag);
|
||
}
|
||
console.log("打印结果2")
|
||
console.log(result)
|
||
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
|
||
} |