从零开始:C# 解析docx提取文本-无需安装office软件且完美支持aot

在解析前,我们可以对docx格式有一个初步了解。

一、 docx格式了解

docx 文件是 Microsoft Office Word 使用的基于 XML 的文件格式,Open XML。Open XML 格式使用 zip 压缩技术来存储文档,从而节省潜在的成本。 在打开文件时,Office程序会自动对文件进行解压。 在保存文件时,会再次对文件自动进行压缩。 比如可以把任意docx,pptx文件后缀改为.zip,可以看到压缩包中有多个xml格式文件和图片素材。

微软官方提供了Open XML SDK 库来处理符合 Office Open XML 文件格式规范的文档。 Office Open XML 文件格式规范是一个开放的、国际的 ECMA-376、第 5 版 和 ISO/IEC 29500 标准。Open XML SDK 简化了操作 Open XML 包和包中基础 Open XML 架构元素的任务。 Open XML SDK 封装开发人员在 Open XML 包上执行的许多常见任务,因此只需几行代码即可执行复杂的操作。

因此我们首选使用Open XML SDK来解析docx文件,不需要安装任何office软件且完美支持aot编译。

二 文件解析与文本提取

首先需要在项目里去Nuget安装OpenXml, 以下示例中的版本是。DocumentFormat.OpenXml(3.3.0), Net9。

2.1 docx格式文本提取

docx格式文件对应的操作类是WordprocessingDocument, 需要用静态方法来实例化 using var doc = WordprocessingDocument.Open(filePath,isEditable: false),此时OpenXml已经帮我们把xml对象都转成具体类型了。

  • Document: 文档的根元素,包含了文档的主体内容
  • Body 元素**: 位于 Document 元素中,包含了文档的主体部分。
    如果是纯文本,Document.innerText 或 Body.innerText可以直接提取出所有字符串内容,但是其中表格文本是连在一起即没有任何分隔符的。因此最好还是考虑对内部元素进行遍历。

2.2 OpenXmlElement对象遍历

如果是提取文本,可以在文档的Body.Elements中遍历OpenXmlElement。在方法doc.body.Elements<T>()T表示继承OpenXmlElement的泛型。我们需要关注的类型主要有:

  • Paragraph: 段落,可直接获取文本;
  • Table、TableRow、TableCell: 表格、表行、单元格,需要再次遍历单元格的段落获取文本;
  • SdtElement: 控件中的显示文本(文本框控件、下拉菜单控件),需要遍历文本框的段落获取文本。

2.4 其他OpenXmlElement对象

其他OpenXmlElement对象我们可以通过检视对象的的ChildElements,找到包含感兴趣文本的类型,然后获取。

2.3 批注提取

批注内容通常存储在 CommentsPart 中,而不是直接存储在文档的主体部分。因此我们需要遍历doc.MainDocumentPart.WordprocessingCommentsPart.Comments容器。

csharp 复制代码
 foreach (var p in wordDocument?.MainDocumentPart.WordprocessingCommentsPart.Comments)
            {
                if (!string.IsNullOrWhiteSpace(p.InnerText))
                {
                    sb.AppendLine(p.InnerText);
                }
            }

2.4 最小实现的代码

具体代码如下:

csharp 复制代码
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System.Text;

var text = ExtractText(@"d:\reference.docx");
Console.Write(text);

static StringBuilder ExtractText(string filePath)
{
    var sb = new StringBuilder();
    using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(filePath, isEditable: false))
    {
        Body? body = wordDocument?.MainDocumentPart?.Document?.Body;

        if (body != null)
        {
            foreach (var p in body.Elements<Paragraph>())
            {
                if (!string.IsNullOrWhiteSpace(p.InnerText))
                {
                    sb.AppendLine(p.InnerText);
                }
            }

            foreach (var p in wordDocument?.MainDocumentPart.WordprocessingCommentsPart.Comments)
            {
                if (!string.IsNullOrWhiteSpace(p.InnerText))
                {
                    sb.AppendLine(p.InnerText);
                }
            }

            foreach (Table table in body.Elements<Table>())
            {
                foreach (TableRow row in table.Elements<TableRow>())
                {
                    foreach (TableCell cell in row.Elements<TableCell>())
                    {
                        foreach (Paragraph p in cell.Elements<Paragraph>())
                        {
                            if (!string.IsNullOrWhiteSpace(p.InnerText))
                            {
                                sb.AppendLine(p.InnerText);
                            }
                        }
                    }
                }
            }

            foreach (var sdt in body.Elements<SdtElement>())
            {
                foreach (Paragraph p in sdt.Descendants<Paragraph>())
                {
                    if (!string.IsNullOrWhiteSpace(p.InnerText))
                    {
                        sb.AppendLine(p.InnerText);
                    }
                }
            }
        }
        wordDocument.Dispose();
    }

    return sb;
}

以上项目AOT发布后得到的Exe也就19.5MB,启动速度很快。基于进程的调用与通信,可以方便为其他程序调用从而快速提供docx文本解析能力。

三 最后

本文分享了在docx格式文件中提取文本过程。对于如pptx、xlsx等offce格式提取文本的操作也是类似的,但会因文档结构会与docx有较大不同,后续再给大家分享。

如果你对本文建议或想法,欢迎随时交流。请关注我们的公众号萤火初芒,以后会和大家分享更多有趣内容,一起学习交流进步。