339 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ContentItem, LensType, SimpleCharacter, TagValueObject } from '../domain/valueObject';
// 定义角色属性接口
interface CharacterAttributes {
name: string;
// gender: string;
// age: string;
avatar: string;
}
// 定义高亮属性接口
interface HighlightAttributes {
text: string;
color: string;
}
// 定义文本节点接口
interface TextNode {
type: 'text';
text: string;
}
// 定义角色标记节点接口
interface CharacterTokenNode {
type: 'characterToken';
attrs: CharacterAttributes;
}
// 定义高亮节点接口
interface HighlightNode {
type: 'highlightText';
attrs: HighlightAttributes;
}
// 定义内容节点类型(文本或角色标记)
type ContentNode = TextNode | CharacterTokenNode | HighlightNode;
// 定义段落接口
interface Paragraph {
type: 'paragraph';
content: ContentNode[];
}
// 定义shot 接口
interface Shot {
name: string;
shotDescContent: Paragraph[];
shotDialogsContent: Paragraph[];
}
export class TextToShotAdapter {
/**
* 解析文本,识别角色并转换为节点数组
* @param text 要解析的文本
* @param roles 角色列表
* @returns ContentNode[] 节点数组
*/
public static parseText(text: string, roles: SimpleCharacter[]): ContentNode[] {
const nodes: ContentNode[] = [];
let currentText = text;
// 按角色名称长度降序排序,避免短名称匹配到长名称的一部分
// 既要兼容 每个单词 首字母大写 其余小写、还要兼容 全部大写
const sortedRoles = [...roles].sort((a, b) => b.name.length - a.name.length).map(role => ({
...role,
name: role.name.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ')
})).concat([...roles].map(role => ({
...role,
name: role.name.toUpperCase()
})));
while (currentText.length > 0) {
let matchFound = false;
// 尝试匹配角色
for (const role of sortedRoles) {
if (currentText.startsWith(role.name)) {
// 如果当前文本以角色名开头
if (currentText.length > role.name.length) {
// 添加角色标记节点
nodes.push({
type: 'characterToken',
attrs: {
name: role.name,
avatar: role.imageUrl
}
});
// 移除已处理的角色名
currentText = currentText.slice(role.name.length);
matchFound = true;
break;
}
}
}
if (!matchFound) {
// 如果没有找到角色匹配,处理普通文本
// 查找下一个可能的角色名位置
let nextRoleIndex = currentText.length;
for (const role of sortedRoles) {
const index = currentText.indexOf(role.name);
if (index !== -1 && index < nextRoleIndex) {
nextRoleIndex = index;
}
}
// 添加文本节点
const textContent = currentText.slice(0, nextRoleIndex);
if (textContent) {
nodes.push({
type: 'text',
text: textContent
});
}
// 移除已处理的文本
currentText = currentText.slice(nextRoleIndex);
}
}
return nodes;
}
/**
* 解析高亮文本识别tag并转换为节点数组
* @param text 要解析的文本
* @param tags 标签列表
* @returns ContentNode[] 节点数组
*/
public static parseHighlight(text: string, tags: TagValueObject[]): ContentNode[] {
const nodes: ContentNode[] = [];
let currentText = text;
// 按内容长度降序排序,避免短名称匹配到长名称的一部分
const sortedTags = [...tags].sort((a, b) => String(b.content).length - String(a.content).length);
while (currentText.length > 0) {
let matchFound = false;
// 尝试匹配
for (const tag of sortedTags) {
if (currentText.startsWith(String(tag.content))) {
// 如果当前文本以tag内容开头
if (currentText.length > String(tag.content).length) {
// 添加标记节点
nodes.push({
type: 'highlightText',
attrs: {
text: String(tag.content),
color: tag?.color || 'yellow'
}
});
// 移除已处理的tag内容
currentText = currentText.slice(String(tag.content).length);
matchFound = true;
break;
}
}
}
if (!matchFound) {
// 如果没有找到tag匹配处理普通文本
// 查找下一个可能的tag内容位置
let nextTagIndex = currentText.length;
for (const tag of sortedTags) {
const index = currentText.indexOf(String(tag.content));
if (index !== -1 && index < nextTagIndex) {
nextTagIndex = index;
}
}
// 添加文本节点
const textContent = currentText.slice(0, nextTagIndex);
if (textContent) {
nodes.push({
type: 'text',
text: textContent
});
}
// 移除已处理的文本
currentText = currentText.slice(nextTagIndex);
}
}
return nodes;
}
private readonly ShotData: Shot;
constructor(shotData: Shot) {
this.ShotData = shotData;
}
toShot() {
return this.ShotData;
}
/**
* 将 LensType 转换为 Paragraph 格式
* @param lensType LensType 实例
* @returns Paragraph 格式的数据
*/
public static fromLensType(lensType: LensType, roles: SimpleCharacter[]): Shot {
const shotDescContent: Paragraph[] = [];
const shotDialogsContent: Paragraph[] = [];
// 处理镜头描述 通过roles name 匹配镜头描述中出现的角色 并添加到shotDescContent
if (lensType.script) {
const descNodes = TextToShotAdapter.parseText(lensType.script, roles);
shotDescContent.push({
type: 'paragraph',
content: descNodes
});
}
// 处理对话内容 通过roles name 匹配对话内容中出现的角色 并添加到shotDialogsContent
lensType.content.forEach(item => {
const dialogNodes = TextToShotAdapter.parseText(item.content, roles);
// 确保对话内容以角色标记开始
const roleMatch = roles.find(role => role.name === item.roleName);
if (roleMatch) {
const dialogContent: Paragraph = {
type: 'paragraph',
content: [{
type: 'characterToken',
attrs: {
name: roleMatch.name,
avatar: roleMatch.imageUrl
}},
...dialogNodes
]
};
shotDialogsContent.push(dialogContent);
}
});
return {
name: lensType.name,
shotDescContent,
shotDialogsContent
};
}
/**
* 将 Paragraph 转换为 LensType 格式
* @param paragraphData Paragraph 格式的数据
* @returns LensType 实例
*/
/**
* 将 Shot 格式转换为 LensType 格式
* @param shotData Shot 格式的数据
* @returns LensType 实例
*/
public static toLensType(shotData: Shot): LensType {
const content: ContentItem[] = [];
let currentScript = '';
// 处理镜头描述
if (shotData.shotDescContent.length > 0) {
// 合并所有描述段落的文本内容
shotData.shotDescContent.forEach(paragraph => {
if (paragraph.content) {
paragraph.content.forEach(node => {
if (node.type === 'text') {
currentScript += node.text;
}
if (node.type === 'characterToken') {
currentScript += node.attrs.name;
}
});
}
});
}
// 处理对话内容
shotData.shotDialogsContent.forEach(paragraph => {
let dialogRoleName = '';
let dialogContent = '';
let firstFindRole = false;
// 遍历段落内容
if (paragraph.content) {
paragraph.content.forEach((node, index) => {
if (node.type === 'characterToken') {
// 记录说话角色的名称
if (!firstFindRole) {
dialogRoleName = node.attrs.name;
firstFindRole = true;
} else {
dialogContent += node.attrs.name;
}
} else if (node.type === 'text') {
// 累积对话内容
dialogContent += node.text;
}
});
}
// 如果有角色名和对话内容,添加到结果中
if (dialogRoleName && dialogContent) {
content.push({
roleName: dialogRoleName,
content: dialogContent.trim()
});
}
});
return new LensType(
shotData.name, // 使用 Shot 中的 name
currentScript.trim(),
content
);
}
public static fromTextToRole(description: string, tags: TagValueObject[]): Paragraph[] {
const paragraph: Paragraph = {
type: 'paragraph',
content: []
};
const highlightNodes = TextToShotAdapter.parseHighlight(description, tags);
paragraph.content.push(...highlightNodes);
return [paragraph];
}
public static fromRoleToText(paragraphs: Paragraph[]): string {
let text = '';
paragraphs.forEach(paragraph => {
if (paragraph?.content) {
paragraph.content.forEach(node => {
if (node.type === 'highlightText') {
text += node.attrs.text;
} else if (node.type === 'text') {
text += node.text;
} else if (node.type === 'characterToken') {
text += node.attrs.name;
}
});
}
});
return text;
}
}