将主题应用于演示文稿

本主题演示如何使用 Open XML SDK for Office 中的类以编程方式将主题从一个演示文稿应用到另一个演示文稿。


获取 PresentationDocument 对象

在 Open XML SDK 中,PresentationDocument 类表示演示文稿文档包。 若要使用演示文稿文档,首先创建 PresentationDocument 类的实例,然后使用该实例。 若要从调用 Open (String 的文档创建类实例 ,布尔值) 方法,该方法使用文件路径,并将布尔值用作指定文档是否可编辑的第二个参数。 若要打开文档进行只读访问,请为此参数指定值 false。 若要打开文档进行读/写访问,请为此参数指定值 true。 在下面的 using 语句中,打开两个演示文稿文件,即要应用主题的目标演示文稿和已应用该主题的源演示文稿。 打开源演示文稿文件进行只读访问,打开目标演示文稿文件进行读/写访问。 在该代码中, themePresentation 参数是表示源演示文稿文档路径的字符串, presentationFile 参数是表示目标演示文稿文档路径的字符串。

using (PresentationDocument themeDocument = PresentationDocument.Open(themePresentation, false))
using (PresentationDocument presentationDocument = PresentationDocument.Open(presentationFile, true))
{

using 语句提供典型 .Open, .Save, .Close 序列的建议备选序列。 它确保在遇到右大括号时会自动调用 Dispose 方法(Open XML SDK 用来清理资源的内部方法)。 using 语句后面的块为 using 语句中创建或指定的对象设定范围,在此示例中这个范围就是 themeDocumentpresentationDocument


基本演示文稿文档结构

PresentationML 文档的基本文档结构包含大量部件,在这些部件中,主部件是包含演示文稿定义的部件。 ISO/IEC 29500(该链接可能指向英文页面) 规范中的以下文本介绍了 PresentationML 包的整体形式。

PresentationML 包的主部件以演示文稿根元素开头。 该元素包含演示文稿,演示文稿又引用幻灯片 列表、幻灯片母版 列表、备注母版 列表和讲义母版 列表。 幻灯片列表指的是演示文稿中的所有幻灯片;幻灯片母版列表指的是演示文稿中使用的全部幻灯片母版;备注母版包含有关备注页格式的信息;讲义母版描述讲义的外观。

讲义 是可提供给访问群体 的一组打印的幻灯片。

除了文本和图形,每个幻灯片还可以包含注释备注,可以具有布局,并且可以是一个或多个自定义演示文稿 的组成部分。 注释是供维护演示文稿幻灯片平台的人员参考的批注。 备注是供演示者或访问群体参考的提醒信息或一段文字。

PresentationML 文档可以包括以下其他功能:幻灯片之间的动画音频视频切换效果。

PresentationML 文档不会存储为单个部件中的一个大型正文。 而实现某些功能组合的元素会存储在各个部件中。 例如,文档中的所有注释都存储在一个注释部件中,而每个幻灯片都有自己的部件。

© ISO/IEC29500: 2008.

以下 XML 代码示例代表包含用 ID 267 和 256 表示的两个幻灯片的演示文稿。

    <p:presentation xmlns:p="…" … > 
       <p:sldMasterIdLst>
          <p:sldMasterId
             xmlns:rel="https://…/relationships" rel:id="rId1"/>
       </p:sldMasterIdLst>
       <p:notesMasterIdLst>
          <p:notesMasterId
             xmlns:rel="https://…/relationships" rel:id="rId4"/>
       </p:notesMasterIdLst>
       <p:handoutMasterIdLst>
          <p:handoutMasterId
             xmlns:rel="https://…/relationships" rel:id="rId5"/>
       </p:handoutMasterIdLst>
       <p:sldIdLst>
          <p:sldId id="267"
             xmlns:rel="https://…/relationships" rel:id="rId2"/>
          <p:sldId id="256"
             xmlns:rel="https://…/relationships" rel:id="rId3"/>
       </p:sldIdLst>
           <p:sldSz cx="9144000" cy="6858000"/>
       <p:notesSz cx="6858000" cy="9144000"/>
    </p:presentation>

使用 Open XML SDK,可以使用对应于 PresentationML 元素的强类型类创建文档结构和内容。 可以在 DocumentFormat.OpenXml.Presentation 命名空间中找到这些类。 下表列出了 sldsldLayoutsldMasternotesMaster 元素所对应类的类名称。

PresentationML 元素 Open XML SDK 类 说明
Sld Slide 演示文稿幻灯片。 它是 SlidePart 的根元素。
sldLayout SlideLayout 幻灯片版式。 它是 SlideLayoutPart 的根元素。
sldMaster SlideMaster 幻灯片母版。 它是 SlideMasterPart 的根元素。
notesMaster NotesMaster 备注母版(或讲义母版)。 它是 NotesMasterPart 的根元素。

主题元素的结构

ISO/IEC 29500(该链接可能指向英文页面) 规范中的以下信息在处理此元素时会很有用。

此元素定义与共享样式表(或主题)关联的根级别复杂类型。 此元素保留通过主题提供给文档的所有不同的格式选项并定义在文档中使用主题对象时文档的总体外观。 [示例:请考虑以下图像示例中应用于演示文稿的不同主题。 在此示例中,可以查看主题如何影响演示文稿中不同对象的字体、颜色、背景、填充和效果。 示例结束]

主题示例

在此示例中,我们查看主题如何影响演示文稿中不同对象的字体、颜色、背景、填充和效果。 示例结束]

© ISO/IEC29500: 2008.

下表列出 Theme 类可能的子类型。

PresentationML 元素 Open XML SDK 类 说明
custClrLst CustomColorList 自定义颜色列表
extLst ExtensionList 扩展名列表
extraClrSchemeLst ExtraColorSchemeList 额外配色方案列表
objectDefaults ObjectDefaults 对象默认值
themeElements ThemeElements 主题元素

下面的 XML 架构段定义主题元素的四个部件。 themeElements 元素是保留主题中定义的主要格式的部件。 其他部件提供 themeElements 中所包含信息的重载、默认值和附加内容。 定义主题 CT_OfficeStyleSheet 的复杂类型通过以下方式定义。

    <complexType name="CT_OfficeStyleSheet">
       <sequence>
           <element name="themeElements" type="CT_BaseStyles" minOccurs="1" maxOccurs="1"/>
           <element name="objectDefaults" type="CT_ObjectStyleDefaults" minOccurs="0" maxOccurs="1"/>
           <element name="extraClrSchemeLst" type="CT_ColorSchemeList" minOccurs="0" maxOccurs="1"/>
           <element name="custClrLst" type="CT_CustomColorList" minOccurs="0" maxOccurs="1"/>
           <element name="extLst" type="CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
       </sequence>
       <attribute name="name" type="xsd:string" use="optional" default=""/>
    </complexType>

此复杂类型还保留一个 CT_OfficeArtExtensionList,它用于将来对此复杂类型进行扩展。


示例代码的工作方式

该示例代码包含 ApplyThemeToPresentation 方法和 GetSlideLayoutType 方法的两个重载。 以下代码段显示第一个重载方法,其中打开两个演示文稿文件 themePresentationpresentationFile 并将其作为参数传递给第二个重载方法。

// Apply a new theme to the presentation. 
static void ApplyThemeToPresentation(string presentationFile, string themePresentation)
{
    using (PresentationDocument themeDocument = PresentationDocument.Open(themePresentation, false))
    using (PresentationDocument presentationDocument = PresentationDocument.Open(presentationFile, true))
    {
        ApplyThemeToPresentationDocument(presentationDocument, themeDocument);
    }
}

在第二个重载方法中,代码首先检查是否有任何演示文稿文件为空,如果为空,则引发异常。 然后,代码通过声明 PresentationPart 对象并将其设置为等于传入的目标 PresentationDocument 对象的演示文稿部件来获取演示文稿文档的演示文稿部件。 接下来,代码从传入的两个对象的演示文稿部件中获取幻灯片母版部件,并获取目标演示文稿的幻灯片母版部件的关系 ID。

// Apply a new theme to the presentation. 
static void ApplyThemeToPresentationDocument(PresentationDocument presentationDocument, PresentationDocument themeDocument)
{
    // Get the presentation part of the presentation document.
    PresentationPart presentationPart = presentationDocument.PresentationPart ?? presentationDocument.AddPresentationPart();

    // Get the existing slide master part.
    SlideMasterPart slideMasterPart = presentationPart.SlideMasterParts.ElementAt(0);


    string relationshipId = presentationPart.GetIdOfPart(slideMasterPart);

    // Get the new slide master part.
    PresentationPart themeDocPresentationPart = themeDocument.PresentationPart ?? themeDocument.AddPresentationPart();
    SlideMasterPart newSlideMasterPart = themeDocPresentationPart.SlideMasterParts.ElementAt(0);

然后,该代码从目标演示文稿中移除现有主题部件和幻灯片母版部件。 通过重用旧的关系 ID,代码将新幻灯片母版部件从源演示文稿添加到目标演示文稿中。 它还向目标演示文稿中添加主题部件。

if (presentationPart.ThemePart is not null)
{
    // Remove the existing theme part.
    presentationPart.DeletePart(presentationPart.ThemePart);
}

// Remove the old slide master part.
presentationPart.DeletePart(slideMasterPart);

// Import the new slide master part, and reuse the old relationship ID.
newSlideMasterPart = presentationPart.AddPart(newSlideMasterPart, relationshipId);

if (newSlideMasterPart.ThemePart is not null)
{
    // Change to the new theme part.
    presentationPart.AddPart(newSlideMasterPart.ThemePart);
}

该代码循环访问幻灯片母版部件中的所有幻灯片版式部件并将其添加到新幻灯片版式的列表中。 它指定默认布局类型。 对于此示例,默认布局类型的代码为"标题和内容"。

Dictionary<string, SlideLayoutPart> newSlideLayouts = new Dictionary<string, SlideLayoutPart>();

foreach (var slideLayoutPart in newSlideMasterPart.SlideLayoutParts)
{
    string? newLayoutType = GetSlideLayoutType(slideLayoutPart);

    if (newLayoutType is not null)
    {
        newSlideLayouts.Add(newLayoutType, slideLayoutPart);
    }
}

string? layoutType = null;
SlideLayoutPart? newLayoutPart = null;

// Insert the code for the layout for this example.
string defaultLayoutType = "Title and Content";

代码循环访问目标演示文稿中的所有幻灯片部件并移除所有幻灯片中的幻灯片版式关系。 它使用 GetSlideLayoutType 方法查找幻灯片版式部件的布局类型。 对于具有现有幻灯片版式部件的任何幻灯片,代码会添加幻灯片之前具有的相同类型的新幻灯片版式部件。 对于没有现有幻灯片版式部件的任何幻灯片,代码会添加默认类型的新幻灯片版式部件。

// Remove the slide layout relationship on all slides. 
foreach (var slidePart in presentationPart.SlideParts)
{
    layoutType = null;

    if (slidePart.SlideLayoutPart is not null)
    {
        // Determine the slide layout type for each slide.
        layoutType = GetSlideLayoutType(slidePart.SlideLayoutPart);

        // Delete the old layout part.
        slidePart.DeletePart(slidePart.SlideLayoutPart);
    }

    if (layoutType is not null && newSlideLayouts.TryGetValue(layoutType, out newLayoutPart))
    {
        // Apply the new layout part.
        slidePart.AddPart(newLayoutPart);
    }
    else
    {
        newLayoutPart = newSlideLayouts[defaultLayoutType];

        // Apply the new default layout part.
        slidePart.AddPart(newLayoutPart);
    }
}

为了获取幻灯片版式类型,代码使用 GetSlideLayoutType 方法,该方法采用幻灯片版式部件作为参数,并向第二个重载的 ApplyThemeToPresentation 方法返回一个表示幻灯片版式类型名称的字符串。

// Get the slide layout type.
static string? GetSlideLayoutType(SlideLayoutPart slideLayoutPart)
{
    CommonSlideData? slideData = slideLayoutPart.SlideLayout?.CommonSlideData;

    return slideData?.Name;
}

示例代码

以下是从一个演示文稿向另一个演示文稿复制主题的完整示例代码。 若要使用此程序,必须创建两个演示文稿,一个是具有要复制的主题的源演示文稿,如 Myppt9-theme.pptx,另一个是目标演示文稿,如 Myppt9.pptx。 在您的程序中可以使用以下调用来执行复制。

ApplyThemeToPresentation(args[0], args[1]);

在执行该调用后,您可以检查文件 Myppt2.pptx,您将看到文件 Myppt9-theme.pptx 具有相同主题。

using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Presentation;
using System.Collections.Generic;
using System.Linq;
ApplyThemeToPresentation(args[0], args[1]);
// Apply a new theme to the presentation. 
static void ApplyThemeToPresentation(string presentationFile, string themePresentation)
{
    using (PresentationDocument themeDocument = PresentationDocument.Open(themePresentation, false))
    using (PresentationDocument presentationDocument = PresentationDocument.Open(presentationFile, true))
    {
        ApplyThemeToPresentationDocument(presentationDocument, themeDocument);
    }
}
// Apply a new theme to the presentation. 
static void ApplyThemeToPresentationDocument(PresentationDocument presentationDocument, PresentationDocument themeDocument)
{
    // Get the presentation part of the presentation document.
    PresentationPart presentationPart = presentationDocument.PresentationPart ?? presentationDocument.AddPresentationPart();

    // Get the existing slide master part.
    SlideMasterPart slideMasterPart = presentationPart.SlideMasterParts.ElementAt(0);


    string relationshipId = presentationPart.GetIdOfPart(slideMasterPart);

    // Get the new slide master part.
    PresentationPart themeDocPresentationPart = themeDocument.PresentationPart ?? themeDocument.AddPresentationPart();
    SlideMasterPart newSlideMasterPart = themeDocPresentationPart.SlideMasterParts.ElementAt(0);
    if (presentationPart.ThemePart is not null)
    {
        // Remove the existing theme part.
        presentationPart.DeletePart(presentationPart.ThemePart);
    }

    // Remove the old slide master part.
    presentationPart.DeletePart(slideMasterPart);

    // Import the new slide master part, and reuse the old relationship ID.
    newSlideMasterPart = presentationPart.AddPart(newSlideMasterPart, relationshipId);

    if (newSlideMasterPart.ThemePart is not null)
    {
        // Change to the new theme part.
        presentationPart.AddPart(newSlideMasterPart.ThemePart);
    }
    Dictionary<string, SlideLayoutPart> newSlideLayouts = new Dictionary<string, SlideLayoutPart>();

    foreach (var slideLayoutPart in newSlideMasterPart.SlideLayoutParts)
    {
        string? newLayoutType = GetSlideLayoutType(slideLayoutPart);

        if (newLayoutType is not null)
        {
            newSlideLayouts.Add(newLayoutType, slideLayoutPart);
        }
    }

    string? layoutType = null;
    SlideLayoutPart? newLayoutPart = null;

    // Insert the code for the layout for this example.
    string defaultLayoutType = "Title and Content";
    // Remove the slide layout relationship on all slides. 
    foreach (var slidePart in presentationPart.SlideParts)
    {
        layoutType = null;

        if (slidePart.SlideLayoutPart is not null)
        {
            // Determine the slide layout type for each slide.
            layoutType = GetSlideLayoutType(slidePart.SlideLayoutPart);

            // Delete the old layout part.
            slidePart.DeletePart(slidePart.SlideLayoutPart);
        }

        if (layoutType is not null && newSlideLayouts.TryGetValue(layoutType, out newLayoutPart))
        {
            // Apply the new layout part.
            slidePart.AddPart(newLayoutPart);
        }
        else
        {
            newLayoutPart = newSlideLayouts[defaultLayoutType];

            // Apply the new default layout part.
            slidePart.AddPart(newLayoutPart);
        }
    }
}
// Get the slide layout type.
static string? GetSlideLayoutType(SlideLayoutPart slideLayoutPart)
{
    CommonSlideData? slideData = slideLayoutPart.SlideLayout?.CommonSlideData;

    return slideData?.Name;
}

另请参阅