如何用Open XML SDK 2.0与C#按指定文本检索Word章节及段落并入库?
解决C# Open XML SDK检索Word章节及段落并插入数据库的问题
原代码的核心问题分析
先帮你拆解下当前代码返回空值的原因:
- 正则表达式使用错误:你用
p.InnerText.Contains("Chapter [0-9]{1;} ")来匹配章节,但Contains是字面字符串匹配,根本不支持正则语法,而且正则里的量词应该是{1,}(匹配1个或多个数字),不是{1;},这两个问题导致完全匹配不到章节标题。 - 缺少内容关联逻辑:就算匹配到章节,你也只提取了标题,没把后续的子标题和段落内容关联起来,更没有处理数据库插入的逻辑。
完整实现方案
下面是修正后的代码,包含章节匹配、内容关联、数据库插入的完整流程,完全适配你的需求:
1. 修正后的核心代码
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using System.Text.RegularExpressions; using System.Data.SqlClient; // 需添加数据库访问引用 public partial class Default : Page { // 替换成你的数据库连接字符串 private string _dbConnString = "Data Source=你的服务器地址;Initial Catalog=你的数据库名;Integrated Security=True;"; protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string wordFilePath = @"C:\Users\file.docx"; // 提取指定章节的内容(这里指定"Chapter 1 - Events") var targetChapterData = ExtractTargetChapterData(wordFilePath, "Chapter 1 - Events"); if (targetChapterData != null && targetChapterData.SubheadingContents.Any()) { InsertChapterDataToDb(targetChapterData); Response.Write("指定章节内容已成功插入数据库!"); } else { Response.Write("未找到匹配的章节内容。"); } } } // 提取指定章节的子标题和对应段落 private ChapterData ExtractTargetChapterData(string filePath, string targetChapterTitle) { ChapterData targetChapter = null; ChapterData currentChapter = null; // 匹配章节标题:兼容短横线和长破折号,忽略大小写 var chapterRegex = new Regex(@"Chapter \d+ [–-] .+", RegexOptions.IgnoreCase); // 匹配子标题:以"- "开头的行 var subheadingRegex = new Regex(@"^- .+", RegexOptions.Multiline); // 只读打开文档,避免不必要的锁 using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(filePath, false)) { Body docBody = wordDoc.MainDocumentPart.Document.Body; foreach (var para in docBody.Descendants<Paragraph>()) { string paraText = para.InnerText.Trim(); if (string.IsNullOrEmpty(paraText)) continue; // 匹配到章节标题 if (chapterRegex.IsMatch(paraText)) { // 先把之前的章节收尾 if (currentChapter != null) { FinalizeCurrentSubheading(currentChapter); } // 判断是否是目标章节 if (paraText.Equals(targetChapterTitle, StringComparison.OrdinalIgnoreCase)) { currentChapter = new ChapterData { ChapterTitle = paraText }; targetChapter = currentChapter; } else { // 不是目标章节,跳过后续内容直到下一个章节 currentChapter = null; } } // 当前处于目标章节内,匹配子标题 else if (currentChapter != null && subheadingRegex.IsMatch(paraText)) { FinalizeCurrentSubheading(currentChapter); // 去除开头的"- ",提取子标题文本 currentChapter.CurrentSubheading = paraText.TrimStart('-', ' '); currentChapter.CurrentContent = ""; } // 当前处于目标章节的子标题下,收集段落内容 else if (currentChapter != null && !string.IsNullOrEmpty(currentChapter.CurrentSubheading)) { currentChapter.CurrentContent += paraText + " "; } } // 处理最后一个子标题的内容 if (currentChapter != null) { FinalizeCurrentSubheading(currentChapter); } } return targetChapter; } // 辅助方法:把当前子标题和内容加入章节列表 private void FinalizeCurrentSubheading(ChapterData chapter) { if (!string.IsNullOrEmpty(chapter.CurrentSubheading)) { chapter.SubheadingContents.Add(new SubheadingContent { Subheading = chapter.CurrentSubheading, Content = chapter.CurrentContent.Trim() }); chapter.CurrentSubheading = null; chapter.CurrentContent = ""; } } // 将章节数据插入数据库 private void InsertChapterDataToDb(ChapterData chapterData) { using (SqlConnection conn = new SqlConnection(_dbConnString)) { conn.Open(); // 参数化查询避免SQL注入 string insertSql = @"INSERT INTO chapters (chapter, subheading, contents) VALUES (@ChapterTitle, @Subheading, @Content)"; foreach (var subContent in chapterData.SubheadingContents) { using (SqlCommand cmd = new SqlCommand(insertSql, conn)) { cmd.Parameters.AddWithValue("@ChapterTitle", chapterData.ChapterTitle); cmd.Parameters.AddWithValue("@Subheading", subContent.Subheading); cmd.Parameters.AddWithValue("@Content", subContent.Content); cmd.ExecuteNonQuery(); } } } } // 辅助类:存储章节的结构化数据 private class ChapterData { public string ChapterTitle { get; set; } public string CurrentSubheading { get; set; } public string CurrentContent { get; set; } = ""; public List<SubheadingContent> SubheadingContents { get; set; } = new List<SubheadingContent>(); } // 辅助类:存储子标题和对应内容 private class SubheadingContent { public string Subheading { get; set; } public string Content { get; set; } } }
2. 关键改进说明
- 正则匹配修复:用
Regex.IsMatch替代Contains实现正则匹配,修正了量词错误,同时兼容文档中的短横线和长破折号。 - 结构化内容提取:通过跟踪当前章节、当前子标题,把段落内容正确关联到对应的层级下,确保数据结构和文档一致。
- 数据库安全插入:使用参数化查询避免SQL注入,严格按照你提供的
chapters表结构插入数据。 - 目标章节筛选:可以直接指定要检索的章节标题,只提取目标内容,不需要处理无关章节。
3. 注意事项
- 确保项目已安装
DocumentFormat.OpenXmlNuGet包(右键项目→管理NuGet程序包→搜索安装)。 - 替换代码中的数据库连接字符串为你自己的服务器和数据库信息。
- 如果文档中章节标题的格式有变化,可以调整
chapterRegex的正则表达式来适配。
测试效果
针对你提供的示例文档,运行代码后,Chapter 1 - Events的内容会被插入到数据库中,每条记录对应一个子标题和其段落内容,比如:
| chapter | subheading | contents |
|---|---|---|
| Chapter 1 - Events | alert or disservices | Lorem ipsum dolor sit amet, consectetur adipiscing elit …. …. |
| Chapter 1 - Events | significant activities | Phasellus dui nunc, rutrum vitae dictum eleifend, ullamcorper hendrerit sem …. …. |
内容的提问来源于stack exchange,提问作者Chevy Mark Sunderland




