using CloudBuilder.AI.Entity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace CloudBuilder.AI.Utility
{
///
/// 智能句子分割器
///
public class SentenceSplitter
{
// 成对引号配置数组(可自由扩展,格式:[左引号, 右引号])
private readonly List<(string LeftQuote, string RightQuote)> _quotePairs;
// 动态生成的正则表达式(匹配所有配置的引号类型)
private Regex _dynamicQuotePattern;
// 引号内必须包含的结尾标点集合
private readonly string _endPunctuations = "?!。,;:、……~·";
///
/// 默认构造函数:初始化常用成对引号(中文、英文、直角引号)
///
public SentenceSplitter()
{
_quotePairs = new List<(string, string)>
{
("“", "”"), // 中文弯引号
("\"", "\""), // 英文双引号(需转义)
("〝", "〞"), // 中文直角引号
("「", "」") // 中文方头引号(可选扩展)
};
InitDynamicRegex();
}
///
/// 自定义构造函数:允许外部传入任意成对引号配置
///
/// 自定义成对引号列表
public SentenceSplitter(List<(string LeftQuote, string RightQuote)> customQuotePairs)
{
_quotePairs = customQuotePairs ?? new List<(string, string)>();
InitDynamicRegex();
}
///
/// 初始化动态正则:根据配置的成对引号生成匹配规则
///
//private void InitDynamicRegex()
//{
// if (_quotePairs.Count == 0)
// {
// // 空配置时生成空正则,避免匹配任何内容
// _dynamicQuotePattern = new Regex(@"^$", RegexOptions.Compiled);
// return;
// }
// // 步骤1:转义所有引号(避免正则特殊字符冲突),拼接左/右引号匹配组
// string escapedLeftQuotes = string.Join("|", _quotePairs.Select(p => Regex.Escape(p.LeftQuote)));
// string escapedRightQuotes = string.Join("|", _quotePairs.Select(p => Regex.Escape(p.RightQuote)));
// // 步骤2:转义标点集合,避免正则解析错误
// string escapedPunctuations = Regex.Escape(_endPunctuations);
// // 步骤3:构建动态正则表达式
// // 分组说明:
// // ?:匹配配置的任意左引号
// // ?:匹配引号内的内容(必须以指定标点结尾)
// // ?:匹配配置的任意右引号
// string regexPattern = $@"(?{escapedLeftQuotes})(?.+?[{escapedPunctuations}])(?{escapedRightQuotes})";
// _dynamicQuotePattern = new Regex(regexPattern, RegexOptions.Compiled | RegexOptions.Singleline);
//}
private void InitDynamicRegex()
{
if (_quotePairs.Count == 0)
{
_dynamicQuotePattern = new Regex(@"^$", RegexOptions.Compiled);
return;
}
string escapedLeftQuotes = string.Join("|", _quotePairs.Select(p => Regex.Escape(p.LeftQuote)));
string escapedRightQuotes = string.Join("|", _quotePairs.Select(p => Regex.Escape(p.RightQuote)));
// ✅ 修复:移除必须以标点结尾的限制,匹配任意引号内容
string regexPattern = $@"(?{escapedLeftQuotes})(?.+?)(?{escapedRightQuotes})";
_dynamicQuotePattern = new Regex(regexPattern, RegexOptions.Compiled | RegexOptions.Singleline);
}
///
/// 核心分割方法:按配置的成对引号分割文本,结果无引号字符
///
/// 待分割的文本(如:“其他标点符号,如?”紫茜的秀目 或 "其他标点符号,如?"紫茜的秀目)
/// 分割后的纯文本列表
public List SplitByConfigurableQuotes(AiSentenceEntity aiParagraphEntity)
{
AiSentenceEntity aiSentence;
int sentenceIndex = 1;
// 空值/空白校验
if (string.IsNullOrWhiteSpace(aiParagraphEntity.Content))
{
return new List();
}
List result = new List();
string remainingText = aiParagraphEntity.Content.Trim();
int lastMatchEnd = 0;
// 遍历所有匹配的「引号+内容+标点+引号」文本块
foreach (Match match in _dynamicQuotePattern.Matches(remainingText))
{
// 1. 提取匹配的左/右引号,校验是否为成对配置
string matchedLeft = match.Groups["left"].Value;
string matchedRight = match.Groups["right"].Value;
bool isPairMatched = _quotePairs.Any(p => p.LeftQuote == matchedLeft && p.RightQuote == matchedRight);
if (!isPairMatched)
{
continue; // 非成对引号,跳过该匹配(避免“xxx"这类混合匹配)
}
// 2. 提取引号前的文本(过滤空值)
string prefixText = remainingText.Substring(lastMatchEnd, match.Index - lastMatchEnd).Trim();
if (!string.IsNullOrEmpty(prefixText))
{
aiSentence = new AiSentenceEntity();
aiSentence.BookId = aiParagraphEntity.BookId;
aiSentence.ChapterId = aiParagraphEntity.ChapterId;
aiSentence.Content = prefixText;
aiSentence.DialogueIndc = YesNoPolicy.NO;
aiSentence.SentenceIndex = sentenceIndex++;
aiSentence.ParagraphId = aiParagraphEntity.ParagraphId;
aiSentence.Status = "G";
//D-【待确认对话】p-【待确认人物】A-【等待分配角色】G-【生成中】C-【完成】
result.Add(aiSentence);
}
// 3. 提取引号内的纯内容(无引号字符)
string quotedContent = match.Groups["content"].Value.Trim();
if (!string.IsNullOrEmpty(quotedContent))
{
aiSentence = new AiSentenceEntity();
aiSentence.BookId = aiParagraphEntity.BookId;
aiSentence.ChapterId = aiParagraphEntity.ChapterId;
aiSentence.Content = quotedContent;
aiSentence.DialogueIndc = YesNoPolicy.YES;
aiSentence.SentenceIndex = sentenceIndex++;
aiSentence.ParagraphId = aiParagraphEntity.ParagraphId;
aiSentence.Status = "P";
//D-【待确认对话】p-【待确认人物】A-【等待分配角色】G-【生成中】C-【完成】
result.Add(aiSentence);
}
// 更新最后匹配位置
lastMatchEnd = match.Index + match.Length;
}
// 4. 提取最后一个匹配项后的剩余文本
string suffixText = remainingText.Substring(lastMatchEnd).Trim();
if (!string.IsNullOrEmpty(suffixText))
{
aiSentence = new AiSentenceEntity();
aiSentence.BookId = aiParagraphEntity.BookId;
aiSentence.ChapterId = aiParagraphEntity.ChapterId;
aiSentence.Content = suffixText;
aiSentence.DialogueIndc = YesNoPolicy.NO;
aiSentence.SentenceIndex = sentenceIndex++;
aiSentence.ParagraphId = aiParagraphEntity.ParagraphId;
aiSentence.Status = "G";
//D-【待确认对话】p-【待确认人物】A-【等待分配角色】G-【生成中】C-【完成】
result.Add(aiSentence);
}
return result;
}
///
/// 新增:判断文本是否包含对话内容
///
/// 待判断的文本(句子/段落)
/// true=包含对话,false=纯旁白/空文本
public bool IsDialogue(string text)
{
// 1. 处理空文本/NULL,直接返回false
if (string.IsNullOrWhiteSpace(text))
return false;
// 2. 文本预处理:去空格、统一全角/半角引号
string cleanedText = text.Trim();
// 替换全角空格为半角,避免干扰判断
cleanedText = Regex.Replace(cleanedText, @" ", " ");
// 统一引号格式(中文弯引号、直引号 → 标准双引号)
cleanedText = cleanedText.Replace("“", "\"")
.Replace("”", "\"")
.Replace("〝", "\"")
.Replace("〞", "\"")
.Replace("「", "\"")
.Replace("」", "\"")
.Replace("‘", "'")
.Replace("’", "'");
// 3. 核心判断规则:
var hasQuote = Regex.IsMatch(cleanedText, @""".+?""") ||
Regex.IsMatch(cleanedText, @"[::,,]\s*"""); // 冒号/逗号+引号(说话标记)
var hasSingleQuote = Regex.IsMatch(cleanedText, @"'[^']+'");
// 4. 排除仅含引号符号的无效情况(修复:只匹配替换后的符号)
var onlyQuote = Regex.IsMatch(cleanedText, @"^\s*[""']{1,2}\s*$");
// 最终判断
return (hasQuote || hasSingleQuote) && !onlyQuote;
}
}
}