如何使用 VSTO (C#) 检测 PowerPoint 中的嵌套组?

琳和 丁 0 信誉分
2025-11-05T01:50:23.2366667+00:00

背景

我正在开发一个使用 VSTO 分析 PowerPoint 演示文稿并将其转换为 JSON 格式的 C# 应用程序。大多数元素(文本框、自定义形状等)都能正常工作,但我在检测嵌套组时遇到了问题。

问题描述

VSTO 无法捕获 PowerPoint 幻灯片中的嵌套组。例如以下结构:


Group1:

  ├── Group2:

  │   ├── Rectangle1

  │   ├── Rectangle2

  │   └── Line (connecting Rectangle1 and Rectangle2)

  └── Group3:

      ├── Rectangle3

      └── Rectangle4


ppt 演示如下:

用户的图像

ppt 对应 slide.xml 如下

<!--
Source - https://stackoverflow.com/questions/79809551/how-to-detect-nested-groups-in-powerpoint-using-vsto-c
Posted by D.Galaxy
Retrieved 2025/11/5, License - CC-BY-SA 4.0
-->

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
  <p:cSld>
    <p:spTree>
      <p:nvGrpSpPr>
        <p:cNvPr id="1" name="" />
        <p:cNvGrpSpPr />
        <p:nvPr />
      </p:nvGrpSpPr>
      <p:grpSpPr>
        <a:xfrm>
          <a:off x="0" y="0" />
          <a:ext cx="0" cy="0" />
          <a:chOff x="0" y="0" />
          <a:chExt cx="0" cy="0" />
        </a:xfrm>
      </p:grpSpPr>
      <p:grpSp>
        <p:nvGrpSpPr>
          <p:cNvPr id="14" name="group 13" />
          <p:cNvGrpSpPr />
          <p:nvPr />
        </p:nvGrpSpPr>
        <p:grpSpPr>
          <a:xfrm>
            <a:off x="1515291" y="1828800" />
            <a:ext cx="5486400" cy="3422469" />
            <a:chOff x="1515291" y="1828800" />
            <a:chExt cx="5486400" cy="3422469" />
          </a:xfrm>
        </p:grpSpPr>
        <p:grpSp>
          <p:nvGrpSpPr>
            <p:cNvPr id="12" name="group 11" />
            <p:cNvGrpSpPr />
            <p:nvPr />
          </p:nvGrpSpPr>
          <p:grpSpPr>
            <a:xfrm>
              <a:off x="1515291" y="1828800" />
              <a:ext cx="5486400" cy="1410789" />
              <a:chOff x="1515291" y="1828800" />
              <a:chExt cx="5486400" cy="1410789" />
            </a:xfrm>
          </p:grpSpPr>
          <p:sp>
            <p:nvSpPr>
              <p:cNvPr id="4" name="rectangle 3" />
              <p:cNvSpPr />
              <p:nvPr />
            </p:nvSpPr>
            <p:spPr>
              <a:xfrm>
                <a:off x="1515291" y="1828800" />
                <a:ext cx="2011680" cy="1410789" />
              </a:xfrm>
              <a:prstGeom prst="rect">
                <a:avLst />
              </a:prstGeom>
            </p:spPr>
            <p:style>
              <a:lnRef idx="2">
                <a:schemeClr val="accent1">
                  <a:shade val="50000" />
                </a:schemeClr>
              </a:lnRef>
              <a:fillRef idx="1">
                <a:schemeClr val="accent1" />
              </a:fillRef>
              <a:effectRef idx="0">
                <a:schemeClr val="accent1" />
              </a:effectRef>
              <a:fontRef idx="minor">
                <a:schemeClr val="lt1" />
              </a:fontRef>
            </p:style>
            <p:txBody>
              <a:bodyPr rtlCol="0" anchor="ctr" />
              <a:lstStyle />
              <a:p>
                <a:pPr algn="ctr" />
                <a:endParaRPr lang="zh-CN" altLang="en-US" />
              </a:p>
            </p:txBody>
          </p:sp>
          <p:sp>
            <p:nvSpPr>
              <p:cNvPr id="5" name="rectangle 4" />
              <p:cNvSpPr />
              <p:nvPr />
            </p:nvSpPr>
            <p:spPr>
              <a:xfrm>
                <a:off x="4990011" y="1828800" />
                <a:ext cx="2011680" cy="1410789" />
              </a:xfrm>
              <a:prstGeom prst="rect">
                <a:avLst />
              </a:prstGeom>
            </p:spPr>
            <p:style>
              <a:lnRef idx="2">
                <a:schemeClr val="accent1">
                  <a:shade val="50000" />
                </a:schemeClr>
              </a:lnRef>
              <a:fillRef idx="1">
                <a:schemeClr val="accent1" />
              </a:fillRef>
              <a:effectRef idx="0">
                <a:schemeClr val="accent1" />
              </a:effectRef>
              <a:fontRef idx="minor">
                <a:schemeClr val="lt1" />
              </a:fontRef>
            </p:style>
            <p:txBody>
              <a:bodyPr rtlCol="0" anchor="ctr" />
              <a:lstStyle />
              <a:p>
                <a:pPr algn="ctr" />
                <a:endParaRPr lang="zh-CN" altLang="en-US" />
              </a:p>
            </p:txBody>
          </p:sp>
          <p:cxnSp>
            <p:nvCxnSpPr>
              <p:cNvPr id="8" name="Direct arrow connector 7" />
              <p:cNvCxnSpPr>
                <a:stCxn id="4" idx="3" />
                <a:endCxn id="5" idx="1" />
              </p:cNvCxnSpPr>
              <p:nvPr />
            </p:nvCxnSpPr>
            <p:spPr>
              <a:xfrm>
                <a:off x="3526971" y="2534195" />
                <a:ext cx="1463040" cy="0" />
              </a:xfrm>
              <a:prstGeom prst="straightConnector1">
                <a:avLst />
              </a:prstGeom>
              <a:ln>
                <a:tailEnd type="triangle" />
              </a:ln>
            </p:spPr>
            <p:style>
              <a:lnRef idx="1">
                <a:schemeClr val="accent1" />
              </a:lnRef>
              <a:fillRef idx="0">
                <a:schemeClr val="accent1" />
              </a:fillRef>
              <a:effectRef idx="0">
                <a:schemeClr val="accent1" />
              </a:effectRef>
              <a:fontRef idx="minor">
                <a:schemeClr val="tx1" />
              </a:fontRef>
            </p:style>
          </p:cxnSp>
        </p:grpSp>
        <p:grpSp>
          <p:nvGrpSpPr>
            <p:cNvPr id="13" name="group 12" />
            <p:cNvGrpSpPr />
            <p:nvPr />
          </p:nvGrpSpPr>
          <p:grpSpPr>
            <a:xfrm>
              <a:off x="1515291" y="3840480" />
              <a:ext cx="4754880" cy="1410789" />
              <a:chOff x="1515291" y="3840480" />
              <a:chExt cx="4754880" cy="1410789" />
            </a:xfrm>
          </p:grpSpPr>
          <p:sp>
            <p:nvSpPr>
              <p:cNvPr id="10" name="rectangle 9" />
              <p:cNvSpPr />
              <p:nvPr />
            </p:nvSpPr>
            <p:spPr>
              <a:xfrm>
                <a:off x="1515291" y="3840480" />
                <a:ext cx="2011680" cy="1410789" />
              </a:xfrm>
              <a:prstGeom prst="rect">
                <a:avLst />
              </a:prstGeom>
            </p:spPr>
            <p:style>
              <a:lnRef idx="2">
                <a:schemeClr val="accent1">
                  <a:shade val="50000" />
                </a:schemeClr>
              </a:lnRef>
              <a:fillRef idx="1">
                <a:schemeClr val="accent1" />
              </a:fillRef>
              <a:effectRef idx="0">
                <a:schemeClr val="accent1" />
              </a:effectRef>
              <a:fontRef idx="minor">
                <a:schemeClr val="lt1" />
              </a:fontRef>
            </p:style>
            <p:txBody>
              <a:bodyPr rtlCol="0" anchor="ctr" />
              <a:lstStyle />
              <a:p>
                <a:pPr algn="ctr" />
                <a:endParaRPr lang="zh-CN" altLang="en-US" />
              </a:p>
            </p:txBody>
          </p:sp>
          <p:sp>
            <p:nvSpPr>
              <p:cNvPr id="11" name="rectangle 10" />
              <p:cNvSpPr />
              <p:nvPr />
            </p:nvSpPr>
            <p:spPr>
              <a:xfrm>
                <a:off x="4258491" y="3840480" />
                <a:ext cx="2011680" cy="1410789" />
              </a:xfrm>
              <a:prstGeom prst="rect">
                <a:avLst />
              </a:prstGeom>
            </p:spPr>
            <p:style>
              <a:lnRef idx="2">
                <a:schemeClr val="accent1">
                  <a:shade val="50000" />
                </a:schemeClr>
              </a:lnRef>
              <a:fillRef idx="1">
                <a:schemeClr val="accent1" />
              </a:fillRef>
              <a:effectRef idx="0">
                <a:schemeClr val="accent1" />
              </a:effectRef>
              <a:fontRef idx="minor">
                <a:schemeClr val="lt1" />
              </a:fontRef>
            </p:style>
            <p:txBody>
              <a:bodyPr rtlCol="0" anchor="ctr" />
              <a:lstStyle />
              <a:p>
                <a:pPr algn="ctr" />
                <a:endParaRPr lang="zh-CN" altLang="en-US" />
              </a:p>
            </p:txBody>
          </p:sp>
        </p:grpSp>
      </p:grpSp>
    </p:spTree>
    <p:extLst>
      <p:ext uri="{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}">
        <p14:creationId xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="1958624764" />
      </p:ext>
    </p:extLst>
  </p:cSld>
  <p:clrMapOvr>
    <a:masterClrMapping />
  </p:clrMapOvr>
</p:sld>

当前结果:

  • VSTO 只能找到 Group1 中的所有元素,但无法检测到 Group2 和 Group3 作为嵌套组存在
  • 生成的 JSON 结构被扁平化,丢失了层级关系:
    
      {
    
        "group1": {
    
          "child_elements": [
    
            "rectangle1",
    
            "rectangle2",
    
            "line1",
    
            "rectangle3",
    
            "rectangle4"
    
          ]
    
        }
    
      }
    
    

预期结果:

  • 应该能够检测到 Group2 和 Group3 作为 Group1 中的嵌套组
  • JSON 应该保留层级结构

注意: 我已验证 python-pptx 可以成功检测并解析这些嵌套的组关系,因此数据存在于文件结构中。

我尝试过的方法

  1. 在 Visual Studio 中调试: 我搜索了可用的 VSTO API,但找不到访问嵌套组的方法。
  2. ID 匹配方法: 我尝试将形状组 ID 与父组 ID 进行匹配,但失败了。

可能的解决方案

一种潜在的方法是直接解析 PowerPoint XML 文件(类似于 python-pptx 的工作方式)以提取组关系。但是,这需要:

  • 理解 PPTX XML 结构
  • 实现自定义 XML 解析逻辑
  • 保持与不同 PowerPoint 版本的兼容性

问题

是否存在我遗漏的用于检测 PowerPoint 中嵌套组的 VSTO API 或方法?或者直接解析 XML 是唯一可行的解​​决方案?

代码示例(如有需要):

开发人员技术 | C#
开发人员技术 | C#

一种面向对象的类型安全的编程语言,它起源于 C 语言系列,包括对面向组件的编程的支持。

0 个注释 无注释

你的答案

提问者可以将答案标记为“已接受”,审查方可以将答案标记为“已推荐”,这有助于用户了解答案是否解决了提问者的问题。