dotnet OpenXML 读取 PPT 主序列进入退出强调动画

  • A+
所属分类:.NET技术
摘要

本文告诉大家如何读取 PPT 文件里面,放在主动画序列 MainSequence 的进入和退出和强调的动画,和在 OpenXML 里面的存放方式

本文告诉大家如何读取 PPT 文件里面,放在主动画序列 MainSequence 的进入和退出和强调的动画,和在 OpenXML 里面的存放方式

如以下的课件内容,给一个元素添加了进入强调退出的动画,动画之间没有相关影响,通过点击触发动画,如下图

dotnet OpenXML 读取 PPT 主序列进入退出强调动画

大概的动画内容如下

  <p:timing>     <p:tnLst>       <p:par>         <p:cTn id="1" dur="indefinite" restart="never" nodeType="tmRoot">           <p:childTnLst>             <p:seq concurrent="1" nextAc="seek">               <p:cTn id="2" dur="indefinite" nodeType="mainSeq">                 <p:childTnLst>                   <p:par>                     <p:cTn id="3" fill="hold">                       <p:stCondLst>                         <p:cond delay="indefinite" />                       </p:stCondLst>                       <p:childTnLst>                         <p:par>                           <p:cTn id="4" fill="hold">                             <p:stCondLst>                               <p:cond delay="0" />                             </p:stCondLst>                             <p:childTnLst>                               <p:par>                                 <p:cTn id="5" presetID="1" presetClass="entr" presetSubtype="0" fill="hold" grpId="0" nodeType="clickEffect">                                   <p:stCondLst>                                     <p:cond delay="0" />                                   </p:stCondLst>                                   <p:childTnLst>                                     <!-- 忽略代码-->                                   </p:childTnLst>                                 </p:cTn>                               </p:par>                             </p:childTnLst>                           </p:cTn>                         </p:par>                       </p:childTnLst>                     </p:cTn>                   </p:par>                   <p:par>                     <p:cTn id="7" fill="hold">                       <p:stCondLst>                         <p:cond delay="indefinite" />                       </p:stCondLst>                       <p:childTnLst>                         <p:par>                           <p:cTn id="8" fill="hold">                             <p:stCondLst>                               <p:cond delay="0" />                             </p:stCondLst>                             <p:childTnLst>                               <p:par>                                 <p:cTn id="9" presetID="25" presetClass="emph" presetSubtype="0" fill="hold" grpId="2" nodeType="clickEffect">                                   <p:stCondLst>                                     <p:cond delay="0" />                                   </p:stCondLst>                                   <p:childTnLst>                                      <!-- 忽略代码-->                                   </p:childTnLst>                                 </p:cTn>                               </p:par>                             </p:childTnLst>                           </p:cTn>                         </p:par>                       </p:childTnLst>                     </p:cTn>                   </p:par>                   <p:par>                     <p:cTn id="14" fill="hold">                       <p:stCondLst>                         <p:cond delay="indefinite" />                       </p:stCondLst>                       <p:childTnLst>                         <p:par>                           <p:cTn id="15" fill="hold">                             <p:stCondLst>                               <p:cond delay="0" />                             </p:stCondLst>                             <p:childTnLst>                               <p:par>                                 <p:cTn id="16" presetID="10" presetClass="exit" presetSubtype="0" fill="hold" grpId="1" nodeType="clickEffect">                                     <!-- 忽略代码-->                                 </p:cTn>                               </p:par>                             </p:childTnLst>                           </p:cTn>                         </p:par>                       </p:childTnLst>                     </p:cTn>                   </p:par>                 </p:childTnLst>               </p:cTn>               <!-- 忽略代码-->             </p:seq>           </p:childTnLst>         </p:cTn>       </p:par>     </p:tnLst>     <!-- 忽略代码-->   </p:timing> 

根据 cTn 也就是 OpenXML sdk 定义的 CommonTimeNode 类型的 PresetClass 属性可以了解到,动画 id 是 5 的是进入动画,动画 id 是 9 的是强调动画,动画 id 是 10 的是退出动画

可以看到在 PPT 里面,多个不同的动画,这些动画没有关联,也就是没有在上一个播放完成后,而是通过点击触发的,放在主序列的动画的内容大概如下

  <p:timing>     <p:tnLst>       <p:par>         <p:cTn id="1" dur="indefinite" restart="never" nodeType="tmRoot">           <p:childTnLst>             <p:seq concurrent="1" nextAc="seek">               <p:cTn id="2" dur="indefinite" nodeType="mainSeq">                 <p:childTnLst>                   <p:par>                     <!-- 进入动画-->                   </p:par>                   <p:par>                     <!-- 强调动画-->                   </p:par>                   <p:par>                     <!-- 退出动画-->                   </p:par>                 </p:childTnLst>               </p:cTn>               <!-- 忽略代码-->             </p:seq>           </p:childTnLst>         </p:cTn>       </p:par>     </p:tnLst>     <!-- 忽略代码-->   </p:timing> 

如上面的内容,大概可以理解存放的方式了,只是在 PPT 里面,有多个 ParallelTimeNode 和 CommonTimeNode 的嵌套。从 mainSeq 也就是 MainSequence 主动画序列以下,获取到的实际的进入动画,是经过了如下路径才能获取

cTn (mainSeq) -> childTnLst -> par -> cTn (id="3") -> childTnLst -> par -> cTn (id="4") -> childTnLst -> par -> cTn (id="5" presetClass="entr") 

可以使用以下代码读取

        static void Main(string[] args)         {             using var presentationDocument =                 DocumentFormat.OpenXml.Packaging.PresentationDocument.Open("Test.pptx", false);             var presentationPart = presentationDocument.PresentationPart;             var slidePart = presentationPart!.SlideParts.First();             var slide = slidePart.Slide;             var timing = slide.Timing;             // 第一级里面默认只有一项             var commonTimeNode = timing?.TimeNodeList?.ParallelTimeNode?.CommonTimeNode;              if (commonTimeNode?.NodeType?.Value == TimeNodeValues.TmingRoot)             {                 // 这是符合约定                 // nodeType="tmRoot"             }              if (commonTimeNode?.ChildTimeNodeList == null) return;             // 理论上只有一项,而且一定是 SequenceTimeNode 类型             var sequenceTimeNode = commonTimeNode.ChildTimeNodeList.GetFirstChild<SequenceTimeNode>();              var mainSequenceTimeNode = sequenceTimeNode.CommonTimeNode;             if (mainSequenceTimeNode?.NodeType?.Value == TimeNodeValues.MainSequence)             {                 // [TimeLine 对象 (PowerPoint) | Microsoft Docs](https://docs.microsoft.com/zh-cn/office/vba/api/PowerPoint.TimeLine )                 //  MainSequence 主动画序列                 var mainParallelTimeNode = mainSequenceTimeNode.ChildTimeNodeList;                  foreach (var openXmlElement in mainParallelTimeNode)                 {                     // 并行关系的                     if (openXmlElement is ParallelTimeNode parallelTimeNode)                     {                         var timeNode = parallelTimeNode.CommonTimeNode.ChildTimeNodeList                             .GetFirstChild<ParallelTimeNode>().CommonTimeNode.ChildTimeNodeList                             .GetFirstChild<ParallelTimeNode>().CommonTimeNode;                          switch (timeNode.PresetClass.Value)                         {                             case TimeNodePresetClassValues.Entrance:                                 // 进入动画                                 break;                             case TimeNodePresetClassValues.Exit:                                 // 退出动画                                 break;                             case TimeNodePresetClassValues.Emphasis:                                 // 强调动画                                 break;                             case TimeNodePresetClassValues.Path:                                 // 路由动画                                 break;                             case TimeNodePresetClassValues.Verb:                                 break;                             case TimeNodePresetClassValues.MediaCall:                                 // 播放动画                                 break;                             default:                                 throw new ArgumentOutOfRangeException();                         }                     }                 }             }              // 文档规定,必须存在一个AttributeNameList列表,一定存在AttributeName元素,如果有多个只取第一个元素。             // 见"[MS-OI 29500].PDF 第2.1.1137章节(g选项)"         } 

本文上面代码放在 githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init git remote add origin https://gitee.com/lindexi/lindexi_gd.git git pull origin 2c06ddf74e45c31ad7842dd06dc09bcc67b6142e 

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

git remote remove origin git remote add origin https://github.com/lindexi/lindexi_gd.git 

获取代码之后,进入 PptxDemo 文件夹

以上的测试使用的 PPT 课件也放在此文件夹

本文的属性是依靠 dotnet OpenXML 解压缩文档为文件夹工具 工具协助测试的,这个工具是开源免费的工具,欢迎使用

更多请看 Office 使用 OpenXML SDK 解析文档博客目录