Explorar o código

fix(book-analysis): 修复章节识别带缩进导致导入失败

章节标题前带半角/全角空格或制表符时无法识别章节,导入静默失败作品不显示。
正则改为允许行首空白,保留锚定行首与防幻影章节能力。
Mochocyang hai 3 semanas
pai
achega
0e354478c8

+ 21 - 0
src/lib/novel/book-analysis/analysis-engine.spec.ts

@@ -63,6 +63,27 @@ describe("parseNovelChapters 章节识别(锚定行首,防幻影章节)",
     ])
   })
 
+  it("允许章节标题前带缩进(半角/全角空格、制表符)且标题不含缩进", () => {
+    const text = [
+      "  第一章 穿越",
+      "正文一。",
+      "  第二章 觉醒",
+      "正文二。",
+      "\t第三章 终局",
+      "正文三。",
+    ].join("\n")
+    const chapters = parseNovelChapters(text)
+    expect(chapters).toHaveLength(3)
+    expect(chapters.map((chapter) => chapter.title)).toEqual([
+      "第一章 穿越",
+      "第二章 觉醒",
+      "第三章 终局",
+    ])
+    // 正文从章标题开始截取,缩进不会混入正文
+    expect(chapters[0].content).toContain("第一章 穿越")
+    expect(chapters[0].content).not.toMatch(/^[ \t\u3000]+第一章/)
+  })
+
   it("没有章节标记时抛出明确错误", () => {
     expect(() => parseNovelChapters("这是一篇没有章节标题的小说正文。")).toThrow(
       "未能识别到章节标记",

+ 2 - 1
src/lib/novel/book-analysis/analysis-engine.ts

@@ -37,8 +37,9 @@ export function parseNovelChapters(content: string): ParsedNovelChapter[] {
   // 锚定行首(m 多行模式),只把独立成行的“第X章”标题当作章节;
   // 兼容“第X卷卷名+第X章标题”同行的格式(如“第五卷名震一方第六百四十八章”),
   // 卷前缀仅作为行首锚定条件,章标题用捕获组1单独提取(避免标题混入卷名)。
+  // 允许行首存在空白(半角/全角空格、制表符),很多 TXT 的章节标题前带缩进。
   // 避免把正文里“第X章提到过”这类跨章引用误拆成幻影章节。
-  const chapterRegex = /^(?:第[零〇一二三四五六七八九十百千万两0-9]+卷[^。!?!?\n]{0,32}?)?(第[零〇一二三四五六七八九十百千万两0-9]+章[^\n]*)/gim
+  const chapterRegex = /^[ \t\u3000]*(?:第[零〇一二三四五六七八九十百千万两0-9]+卷[^。!?!?\n]{0,32}?)?(第[零〇一二三四五六七八九十百千万两0-9]+章[^\n]*)/gim
   const matches = Array.from(content.matchAll(chapterRegex))
 
   if (matches.length === 0) {