1+ /**
2+ * 智能断章工具
3+ * 用于将txt内容解析为章节列表
4+ */
5+
6+ /**
7+ * 章节标题的正则表达式模式
8+ */
9+ const CHAPTER_PATTERNS = [
10+ // 中文章节模式
11+ / ^ 第 [ 零 一 二 三 四 五 六 七 八 九 十 百 千 万 \d ] + [ 章 回 节 部 分 ] / ,
12+ / ^ 第 [ 零 一 二 三 四 五 六 七 八 九 十 百 千 万 \d ] + [ 章 回 节 部 分 ] [ \s \S ] * $ / ,
13+ / ^ [ 第 ] ? [ 零 一 二 三 四 五 六 七 八 九 十 百 千 万 \d ] + [ 、 \s ] * [ 章 回 节 部 分 ] / ,
14+
15+ // 数字章节模式
16+ / ^ 第 \d + 章 / ,
17+ / ^ 第 \d + 回 / ,
18+ / ^ 第 \d + 节 / ,
19+ / ^ 第 \d + 部 分 / ,
20+ / ^ \d + [ 、 \. \s ] * [ 章 回 节 部 分 ] / ,
21+ / ^ C h a p t e r \s * \d + / i,
22+ / ^ C h \. \s * \d + / i,
23+
24+ // 特殊格式
25+ / ^ 序 章 / ,
26+ / ^ 楔 子 / ,
27+ / ^ 引 子 / ,
28+ / ^ 前 言 / ,
29+ / ^ 后 记 / ,
30+ / ^ 尾 声 / ,
31+ / ^ 终 章 / ,
32+ / ^ 番 外 / ,
33+ / ^ 外 传 / ,
34+ / ^ 附 录 / ,
35+
36+ // 英文章节模式
37+ / ^ C h a p t e r \s + [ I V X ] + / i,
38+ / ^ P a r t \s + \d + / i,
39+ / ^ S e c t i o n \s + \d + / i,
40+
41+ // 其他常见模式
42+ / ^ 【 .* 】 $ / ,
43+ / ^ 〖 .* 〗 $ / ,
44+ / ^ 《 .* 》 $ / ,
45+ / ^ 「 .* 」 $ / ,
46+ / ^ 『 .* 』 $ /
47+ ]
48+
49+ /**
50+ * 检查一行文本是否可能是章节标题
51+ * @param {string } line - 要检查的文本行
52+ * @returns {boolean } 是否是章节标题
53+ */
54+ function isChapterTitle ( line ) {
55+ const trimmedLine = line . trim ( )
56+
57+ // 空行不是章节标题
58+ if ( ! trimmedLine ) return false
59+
60+ // 太长的行通常不是章节标题(超过50个字符)
61+ if ( trimmedLine . length > 50 ) return false
62+
63+ // 检查是否匹配章节模式
64+ for ( const pattern of CHAPTER_PATTERNS ) {
65+ if ( pattern . test ( trimmedLine ) ) {
66+ return true
67+ }
68+ }
69+
70+ // 检查是否是纯数字标题
71+ if ( / ^ \d + $ / . test ( trimmedLine ) && trimmedLine . length <= 3 ) {
72+ return true
73+ }
74+
75+ // 检查是否是短标题(可能是章节名)
76+ if ( trimmedLine . length <= 20 && ! trimmedLine . includes ( '。' ) && ! trimmedLine . includes ( ',' ) ) {
77+ // 如果包含常见的章节关键词
78+ const chapterKeywords = [ '章' , '回' , '节' , '部' , '篇' , 'Chapter' , 'Part' ]
79+ for ( const keyword of chapterKeywords ) {
80+ if ( trimmedLine . includes ( keyword ) ) {
81+ return true
82+ }
83+ }
84+ }
85+
86+ return false
87+ }
88+
89+ /**
90+ * 解析txt内容为章节列表
91+ * @param {string } content - txt文件内容
92+ * @param {Object } options - 解析选项
93+ * @returns {Array } 章节列表
94+ */
95+ export function parseChapters ( content , options = { } ) {
96+ const {
97+ minChapterLength = 500 , // 最小章节长度
98+ maxChapters = 1000 , // 最大章节数
99+ autoDetect = true // 是否自动检测章节
100+ } = options
101+
102+ if ( ! content || typeof content !== 'string' ) {
103+ return [ ]
104+ }
105+
106+ const lines = content . split ( / \r ? \n / )
107+ const chapters = [ ]
108+ let currentChapter = null
109+ let chapterIndex = 0
110+
111+ // 如果没有检测到章节标题,创建一个默认章节
112+ let hasChapterTitles = false
113+
114+ for ( let i = 0 ; i < lines . length ; i ++ ) {
115+ const line = lines [ i ] . trim ( )
116+
117+ if ( autoDetect && isChapterTitle ( line ) ) {
118+ hasChapterTitles = true
119+
120+ // 保存上一章节
121+ if ( currentChapter && currentChapter . content . trim ( ) . length >= minChapterLength ) {
122+ chapters . push ( currentChapter )
123+ chapterIndex ++
124+ }
125+
126+ // 创建新章节
127+ currentChapter = {
128+ id : chapterIndex ,
129+ title : line || `第${ chapterIndex + 1 } 章` ,
130+ content : '' ,
131+ startLine : i ,
132+ endLine : i
133+ }
134+ } else if ( line ) {
135+ // 添加内容到当前章节
136+ if ( ! currentChapter ) {
137+ // 如果还没有章节,创建第一个章节
138+ currentChapter = {
139+ id : chapterIndex ,
140+ title : `第${ chapterIndex + 1 } 章` ,
141+ content : '' ,
142+ startLine : i ,
143+ endLine : i
144+ }
145+ }
146+
147+ if ( currentChapter . content ) {
148+ currentChapter . content += '\n'
149+ }
150+ currentChapter . content += line
151+ currentChapter . endLine = i
152+ }
153+
154+ // 限制章节数量
155+ if ( chapters . length >= maxChapters ) {
156+ break
157+ }
158+ }
159+
160+ // 添加最后一个章节
161+ if ( currentChapter && currentChapter . content . trim ( ) . length >= minChapterLength ) {
162+ chapters . push ( currentChapter )
163+ }
164+
165+ // 如果没有检测到章节标题,按长度自动分章
166+ if ( ! hasChapterTitles && content . length > minChapterLength ) {
167+ return autoSplitChapters ( content , options )
168+ }
169+
170+ // 如果章节太少,尝试更宽松的检测
171+ if ( chapters . length < 2 && content . length > minChapterLength * 2 ) {
172+ return autoSplitChapters ( content , options )
173+ }
174+
175+ return chapters
176+ }
177+
178+ /**
179+ * 自动按长度分章
180+ * @param {string } content - 文本内容
181+ * @param {Object } options - 选项
182+ * @returns {Array } 章节列表
183+ */
184+ function autoSplitChapters ( content , options = { } ) {
185+ const {
186+ chapterLength = 3000 , // 每章大约长度
187+ minChapterLength = 500 // 最小章节长度
188+ } = options
189+
190+ const chapters = [ ]
191+ const paragraphs = content . split ( / \n \s * \n / ) . filter ( p => p . trim ( ) )
192+
193+ let currentChapter = {
194+ id : 0 ,
195+ title : '第1章' ,
196+ content : '' ,
197+ startLine : 0 ,
198+ endLine : 0
199+ }
200+
201+ let chapterIndex = 0
202+
203+ for ( let i = 0 ; i < paragraphs . length ; i ++ ) {
204+ const paragraph = paragraphs [ i ] . trim ( )
205+
206+ if ( currentChapter . content . length + paragraph . length > chapterLength &&
207+ currentChapter . content . length >= minChapterLength ) {
208+ // 当前章节已经足够长,开始新章节
209+ chapters . push ( currentChapter )
210+ chapterIndex ++
211+
212+ currentChapter = {
213+ id : chapterIndex ,
214+ title : `第${ chapterIndex + 1 } 章` ,
215+ content : paragraph ,
216+ startLine : i ,
217+ endLine : i
218+ }
219+ } else {
220+ // 添加到当前章节
221+ if ( currentChapter . content ) {
222+ currentChapter . content += '\n\n'
223+ }
224+ currentChapter . content += paragraph
225+ currentChapter . endLine = i
226+ }
227+ }
228+
229+ // 添加最后一个章节
230+ if ( currentChapter . content . trim ( ) ) {
231+ chapters . push ( currentChapter )
232+ }
233+
234+ return chapters
235+ }
236+
237+ /**
238+ * 获取章节摘要
239+ * @param {string } content - 章节内容
240+ * @param {number } maxLength - 最大长度
241+ * @returns {string } 摘要
242+ */
243+ export function getChapterSummary ( content , maxLength = 100 ) {
244+ if ( ! content ) return ''
245+
246+ const cleanContent = content . replace ( / \s + / g, ' ' ) . trim ( )
247+ if ( cleanContent . length <= maxLength ) {
248+ return cleanContent
249+ }
250+
251+ return cleanContent . substring ( 0 , maxLength ) + '...'
252+ }
253+
254+ /**
255+ * 验证章节数据
256+ * @param {Array } chapters - 章节列表
257+ * @returns {boolean } 是否有效
258+ */
259+ export function validateChapters ( chapters ) {
260+ if ( ! Array . isArray ( chapters ) || chapters . length === 0 ) {
261+ return false
262+ }
263+
264+ for ( const chapter of chapters ) {
265+ if ( ! chapter . title || ! chapter . content || typeof chapter . id !== 'number' ) {
266+ return false
267+ }
268+ }
269+
270+ return true
271+ }
272+
273+ export default {
274+ parseChapters,
275+ getChapterSummary,
276+ validateChapters,
277+ isChapterTitle
278+ }
0 commit comments