欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-26 09:11:12
本文告诉大家如何读取 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 解析文档博客目录