Skip to content

txt文本文件解析bookParser.js

js
/**
 * 解析TXT书籍
 * @param {string} text - TXT文件内容
 * @param {string} fileName - 文件名(用于默认标题)
 * @param {function} progressCallback - 进度回调函数 (progress: number, message: string)
 * @returns {Promise<object>} 解析后的书籍对象 { title, info, author, chapters }
 */
export async function parseTxtBook(text, fileName, progressCallback) {
  try {
    // 初始进度更新
    progressCallback(0.1, '正在分析文本内容...');
    // 1. 去除 BOM
    if (text.charCodeAt(0) === 0xFEFF) {
      text = text.slice(1)
    }

    // 2. 按行分割
    const lines = text.split(/\r?\n/).map(line => line.trim())
    progressCallback(0.2, '正在识别章节结构...');

    // 3. 🔍 阶段1:使用你原来的“标准正则”(精准)
    const standardRegex = /^[零一二三四五六七八九十百千0-9]+[章节回集卷]/i
    let chapterStartIndex = lines.findIndex(line => standardRegex.test(line))

    let chapterRegex = standardRegex

    // 4. 如果没找到标准章节,进入“宽松模式”
    if (chapterStartIndex === -1) {
      progressCallback(0.25, '章节结构识别失败,进入宽松模式... 后续还可手动调整正则识别...');

      // 宽松正则:只找常见关键词
      const looseRegex = /(?:第[零一二三四五六七八九十百千0-9]+[章节回集卷]|序章|终章|外传|番外|楔子|[0-9]+|Chapter|Vol\.)/i

      // 找第一个像章节的行
      chapterStartIndex = lines.findIndex(line => looseRegex.test(line))

      // 如果还是没找到,从第一个非空行开始
      if (chapterStartIndex === -1) {
        chapterStartIndex = lines.findIndex(line => line.length > 0)
      }

      // 使用宽松正则进行后续解析
      chapterRegex = looseRegex
    }

    // 5. 如果还是没找到,从第0行开始
    if (chapterStartIndex === -1) {
      chapterStartIndex = 0
    }

    // 6. 提取 info:第一章前的内容,最多 500 字
    const rawInfo = lines.slice(0, chapterStartIndex).join('\n')
    const info = rawInfo.length > 500
      ? rawInfo.substring(0, 500) + '...'
      : rawInfo

    // 7. 提取作者
    let author = '佚名'
    const authorMatch = info.match(/(?:作者|著者)[::]\s*([^\n]+)/i)
    if (authorMatch) {
      author = authorMatch[1].trim()
    }

    // 8. 文件名作为标题
    const title = fileName.replace(/\.\w+$/, '').trim() || '未知小说'

    // 9. 解析章节
    const chapters = []
    let currentChapter = null
    const contentBuffer = []

    progressCallback(0.3, '正在分割章节...');
    // ✅ 计算有效行数
    const totalContentLines = Math.max(1, lines.length - chapterStartIndex); // 防止除0

    for (let i = chapterStartIndex; i < lines.length; i++) {
      const line = lines[i]

      if (chapterRegex.test(line)) {
        // 保存上一章
        if (currentChapter) {
          currentChapter.content = contentBuffer.join('\n').trim()
          chapters.push(currentChapter)
          // ✅ 基于有效行数计算进度
          const processedLines = i - chapterStartIndex;
          const progress = 0.3 + 0.6 * (processedLines / totalContentLines);
          progressCallback(Math.min(progress, 0.95), `已识别 ${chapters.length} 个章节`);
        }
        // 新章节
        currentChapter = { title: line, content: '' }
        contentBuffer.length = 0
      } else if (currentChapter) {
        contentBuffer.push(line)
      }

    }

    // 保存最后一章
    if (currentChapter) {
      currentChapter.content = contentBuffer.join('\n').trim()
      chapters.push(currentChapter)
    }

    // 如果没有识别到任何章节,将整个内容作为一个章节
    if (chapters.length === 0) {
      progressCallback(0.9, '未识别到章节结构,创建“全文”章节...');
      chapters.push({
        title: '全文',  // 或 '正文' / '单章内容' / '原始文本'
        content: text.trim() // 使用原始文本(已去BOM)
      })
    }

    // 清理内容
    chapters.forEach(ch => {
      ch.content = ch.content.replace(/\n{3,}/g, '\n\n').trim()
    })
    progressCallback(1.0, '解析完成');
    return {
      title,
      info,
      author,
      chapters
    }
  } catch (error) {
    console.error('TXT解析错误:', error);
    throw new Error('解析TXT文件失败: ' + error.message);
  }
}