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