C# 使用openxml解析PPTX中的文本内容
阅读原文时间:2023年08月09日阅读:3

本文讨论的仅针对微软Office 2007以后的(OOXML定义)PowerPoint文档,Office 2007以前的用二进制格式定义的(ppt格式)文档不在本文讨论范围。

本文需要依赖两个免费的第三方类库:DocumentFormat.OpenXml和FreeSpire.Doc。

DocumentFormat.OpenXml用于加载解析pptx文档,FreeSpire.Doc用于解析pptx中嵌入的doc文档内容,详见解析嵌入的doc的文本。

1.引入库

通过Nuget引入类库

<packages>
  <package id="DocumentFormat.OpenXml" version="2.13.0" targetFramework="net452" />
  <package id="FreeSpire.Doc" version="7.11.0" targetFramework="net452" />
</packages>

2.读取数据

PPTX中的文本内容主要以三种形式存储。1、直接保存在slide*.xml文件的节点数据;2、以oleObject对象的形式存储在word文档中;3、以oleObject对象的形式存储在bin文件中。接下来针对这三种情况分别分析如何解析获取内容。

首先需要读取pptx文件,解析每一个页面的slide对象

using (var presentationDocument = PresentationDocument.Open(filePath, false))
{
    var presentationPart = presentationDocument.PresentationPart;
    var presentation = presentationPart.Presentation;
    // 先获取页面
    var slideIdList = presentation.SlideIdList;
    foreach (var slideId in slideIdList.ChildElements.OfType<SlideId>())
    {
        //TODO:解析页面的内容
    }

2.1 直接保存在slide*.xml文件的节点数据

直接保存在slide*.xml文件的文本数据只需遍历页面中的每一个paragraph对象即可,需要注意的是此处的用到的是DocumentFormat.OpenXml.Drawing.Paragraph。

 foreach (var paragraph in
     slidePart.Slide.Descendants<DocumentFormat.OpenXml.Drawing.Paragraph>())
{
    contentText.Length = 0;
    foreach (var text in paragraph.Descendants<DocumentFormat.OpenXml.Drawing.Text>())
    {
        contentText.Append(text.InnerText);
    }
}

2.2 以oleObject对象的形式存储在word文档中

oleObject对象在slide*.xml文件中记录形式如下图:

progId的值为“Word.Document.8”表示嵌入的对象是Office 2007以前的数据格式,值为“Word.Document.12”表示嵌入的对象是Office 2007以后的OOXML定义的数据格式。通过r:id的值获取嵌入的文件对象及其ContentType。值为 "application/vnd.openxmlformats-officedocument.wordprocessingml.document"表示嵌入的对象是word文档,值为"application/vnd.openxmlformats-officedocument.oleObject"标识嵌入的是bin文件。

Office 2007以后的OOXML定义的数据格式直接通过DocumentFormat.OpenXml解析,需要注意的是在解析word中的段落需要用DocumentFormat.OpenXml.Wordprocessing.Paragraph。

foreach (var choice in
    slidePart.Slide.Descendants<DocumentFormat.OpenXml.AlternateContentChoice>())
{
    foreach (var oleobject in
        choice.Descendants<DocumentFormat.OpenXml.Presentation.OleObject>())
    {
        if (oleobject.ProgId.Value == "Word.Document.12")
        {
            var part = slidePart.GetPartById(oleobject.Id);
            if (part.ContentType == "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
            {
                 using (var stream = part.GetStream(FileMode.Open, FileAccess.Read))
                 {
                     using (var wordDocument = WordprocessingDocument.Open(stream, false))
                     {
                         var mainDocumentPart = wordDocument.MainDocumentPart;
                         var body = mainDocumentPart.Document.Body;
                         foreach (DocumentFormat.OpenXml.Wordprocessing.Paragraph paragraph in body.Descendants<DocumentFormat.OpenXml.Wordprocessing.Paragraph>())
                         {
                             contentText.Length = 0;
                             foreach (var text in
                     paragraph.Descendants<DocumentFormat.OpenXml.Wordprocessing.Text>())
                             {
                                  contentText.Append(text.InnerText);
                              }
                          }
                       }
                   }
               }
          }
      }
}

Office 2007以前的数据格式借助FreeSpire.Doc解析

else if (oleobject.ProgId.Value == "Word.Document.8")
{
    var part = slidePart.GetPartById(oleobject.Id);
    var stream = part.GetStream(FileMode.Open, FileAccess.Read);
    Spire.Doc.Document doc = new Spire.Doc.Document(stream);
    foreach (Spire.Doc.DocumentObject child in doc.Sections[0].Body.ChildObjects)
    {
        if (child is Spire.Doc.Documents.Paragraph)
            paraList.Add((child as Spire.Doc.Documents.Paragraph).Text);
        else if (child is Spire.Doc.Table)
        {
            Spire.Doc.Table table = child as Spire.Doc.Table;
            foreach (Spire.Doc.TableRow row in table.Rows)
            {
                foreach (Spire.Doc.TableCell cell in row.Cells)
                {
                    foreach (Spire.Doc.Documents.Paragraph paragraph in cell.Paragraphs)
                    {
                        paraList.Add(paragraph.Text);
                    }
                }
            }
        }
    }
    stream.Dispose();
}

2.3 以oleObject对象的形式存储在bin文件中

这种情况需要通过StgOpenStorage解析oleObject对象提取word数据的文件流:

[DllImport("ole32.dll")]
        private static extern int StgIsStorageFile(
            [MarshalAs(UnmanagedType.LPWStr)] string pwcsName);

        [DllImport("ole32.dll")]
        static extern int StgOpenStorage(
            [MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
            IStorage pstgPriority,
            STGM grfMode,
            IntPtr snbExclude,
            uint reserved,
            out IStorage ppstgOpen);

public MemoryStream parseOleObject(string fileName)
        {
            MemoryStream outBuffer=default(MemoryStream);
            if (StgIsStorageFile(fileName) == 0)
            {
                IStorage storage = null;
                if (StgOpenStorage(
                    fileName,
                    null,
                    STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE,
                    IntPtr.Zero,
                    0,
                    out storage) == 0)
                {
                    System.Runtime.InteropServices.ComTypes.STATSTG statstg;
                    storage.Stat(out statstg, (uint)STATFLAG.STATFLAG_DEFAULT);
                    IEnumSTATSTG pIEnumStatStg = null;
                    storage.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStg);
                    System.Runtime.InteropServices.ComTypes.STATSTG[] regelt = { statstg };
                    uint fetched = 0;
                    uint res = pIEnumStatStg.Next(1, regelt, out fetched);
                    if (res == 0)
                    {
                        while (res != 1)
                        {
                            string strNode = statstg.pwcsName;
                            if (strNode == "Package")
                            {
                                switch (statstg.type)
                                {
                                    case (int)STGTY.STGTY_STORAGE:
                                        {
                                            IStorage pIChildStorage;
                                            storage.OpenStorage(statstg.pwcsName,
                                               null,
                                               (uint)(STGM.READ | STGM.SHARE_EXCLUSIVE),
                                               IntPtr.Zero,
                                               0,
                                               out pIChildStorage);
                                        }
                                        break;
                                    case (int)STGTY.STGTY_STREAM:
                                        {
                                            IStream pIStream;
                                            storage.OpenStream(statstg.pwcsName,
                                               IntPtr.Zero,
                                               (uint)(STGM.READ | STGM.SHARE_EXCLUSIVE),
                                               0,
                                               out pIStream);
                                            outBuffer = pIStream.ReadToMemoryStream();
                                            Marshal.FinalReleaseComObject(pIStream);
                                            Marshal.FinalReleaseComObject(pIEnumStatStg);
                                            Marshal.FinalReleaseComObject(storage);
                                            return outBuffer;
                                        }
                                        break;
                                }
                            }
                            if ((res = pIEnumStatStg.Next(1, regelt, out fetched)) != 1)
                            {
                                statstg = regelt[0];
                            }
                        }
                    }
                }
            }
            return outBuffer;
        }

解析oleObject对象提取word数据的文件流后按照解析word对象的方式解析数据即可。


Office OpenXml SDK 使用 Fallback 图片显示 Ole 元素

reading-compound-documents-in-c-sharp