import { ContentItem, LensType, SimpleCharacter } from '../domain/valueObject'; // 定义角色属性接口 interface CharacterAttributes { name: string; // gender: string; // age: string; avatar: string; } // 定义文本节点接口 interface TextNode { type: 'text'; text: string; } // 定义角色标记节点接口 interface CharacterTokenNode { type: 'characterToken'; attrs: CharacterAttributes; } // 定义内容节点类型(文本或角色标记) type ContentNode = TextNode | CharacterTokenNode; // 定义段落接口 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); 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; } 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 => { 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 = ''; // 遍历段落内容 paragraph.content.forEach((node, index) => { if (node.type === 'characterToken') { // 记录说话角色的名称 index === 0 && (dialogRoleName = node.attrs.name); index !== 0 && (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 ); } }