前言

      本文讨论的仅针对微软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