C#OpenXML SDK-从幻灯片母版插入新幻灯片

时间:2018-07-10 04:21:11

标签: c# powerpoint openxml openxml-sdk

我正在尝试实施给定here和/或here的解决方案。

我有一个.pptx文件,最初包含零张幻灯片。布局之一称为“一个内容”。现在,我只想基于此布局用一张幻灯片生成一个新的PPTX文件。应该是微不足道的,不是吗?不,显然不是。

在文件 OpenXmlUtils.cs 中,我使用以下方法从“模板”文件中创建新的PPTX:

public static void CopyTemplate(string template, string target)
{
    string targetPath = Path.GetFullPath(target);
    string targetFolder = Path.GetDirectoryName(targetPath);
    if (!System.IO.Directory.Exists(targetFolder))
    {
        System.IO.Directory.CreateDirectory(targetFolder);
    }
    System.IO.File.Copy(template, targetPath, true);
}

我的 PPTWriter.cs 分解为MCVE:

public PPTOpenXMLWriter(string templatePath, string presSaveAsPath)
{
    if (File.Exists(presSaveAsPath)) { File.Delete(presSaveAsPath); }

    OpenXmlUtils.CopyTemplate(templatePath, presSaveAsPath);

    _createPresentation(presSaveAsPath);

}

private void _createPresentation(string presSaveAsPath)
{
    using (PresentationDocument presentationDocument = PresentationDocument.Open(presSaveAsPath, true))
    {

        string layoutName = "One content";

        _insertNewSlide(presentationDocument.PresentationPart, layoutName);

        presentationDocument.Save();
    }
}    

private void _insertNewSlide(PresentationPart presentationPart, string layoutName)
{
    Slide slide = new Slide(new CommonSlideData(new ShapeTree()));
    SlidePart slidePart = presentationPart.AddNewPart<SlidePart>();
    slide.Save(slidePart);
    SlideMasterPart slideMasterPart = presentationPart.SlideMasterParts.FirstOrDefault();
    SlideLayoutPart slideLayoutPart = slideMasterPart.SlideLayoutParts.SingleOrDefault
            (sl => sl.SlideLayout.CommonSlideData.Name.Value.Equals(layoutName, StringComparison.OrdinalIgnoreCase));
    slidePart.AddPart<SlideLayoutPart>(slideLayoutPart);
    slidePart.Slide.CommonSlideData = (CommonSlideData)slideLayoutPart.SlideLayout.CommonSlideData.Clone();

    SlideIdList slideIdList = null;
    if ( presentationPart.Presentation.SlideIdList is null)
    {
        presentationPart.Presentation.SlideIdList = new SlideIdList();
    }
    slideIdList = presentationPart.Presentation.SlideIdList;
    // find the highest id
    uint maxSlideId = 0;
    if (slideIdList.ChildElements.Count() > 0)
        maxSlideId = slideIdList.ChildElements
            .Cast<SlideId>()
            .Max(x => x.Id.Value);

    // Insert the new slide into the slide list after the previous slide.
    SlideId newSlideId = new SlideId();
    slideIdList.Append(newSlideId);
    newSlideId.Id = maxSlideId;
    newSlideId.RelationshipId = presentationPart.GetIdOfPart(slidePart);

    // Save the modified presentation.
    presentationPart.Presentation.Save();
}

生成的文件已损坏,需要PowerPoint进行“修复”,然后修复过程中,幻灯片布局不是是指定的布局。实际上,这是一个完全不同的布局,具有完全不同的XML结构,我可以收集到的是它以某种方式默认返回到主版中通常的 first 布局(“标题”),因为它没有知道如何处理通过OpenXML实际提供的内容。

这似乎应该是一个相当普遍的用例,也许我的期望是错误的,但是在给定已经存在的幻灯片布局的情况下,您应该能够(相对轻松地)根据该布局创建一个新幻灯片 ,其中将包含所有相同的占位符形状,等等。

2 个答案:

答案 0 :(得分:0)

我注意到幻灯片的.rels与正确的手动制作的幻灯片存在一些差异:

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
    <Relationship Target="../slideLayouts/slideLayout8.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Id="rId1"/>
</Relationships>

不正确的样子:

<?xml version="1.0" encoding="UTF-8"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="R522c7c9989a04964" Target="/ppt/slideLayouts/slideLayout8.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"/>
<Relationship Id="rId5" Target="/ppt/media/image2.bin" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"/>
</Relationships>

两个差异,我相信如下:

  • 我相信image2.bin可以追溯到几个幻灯片母版上存在的1x1像素自动整形“对象”。我从存在的每个幻灯片母版中手动删除了该文件,并重新保存了我的模板pptx文件。
  • 幻灯片丢失了相对于幻灯片布局的 ID ,似乎很容易。我向OpenXmlUtils类添加了一些扩展方法,并如下修改了_insertNewSlide方法:

private void _insertNewSlide(PresentationPart presentationPart, string layoutName)
{
    Slide slide = new Slide();
    SlidePart slidePart = presentationPart.AddNewPart<SlidePart>();

    slide.Save(slidePart);
    SlideMasterPart slideMasterPart = presentationPart.SlideMasterParts.FirstOrDefault();
    SlideLayoutPart slideLayoutPart = slideMasterPart.GetSlideLayoutPartByLayoutName(layoutName); // extension method

    /* ensure we added the rel ID to this part */
    slidePart.AddPart<SlideLayoutPart>(slideLayoutPart, slideMasterPart.GetIdOfPart(slideLayoutPart));

    slidePart.Slide.CommonSlideData = (CommonSlideData)slideLayoutPart.SlideLayout.CommonSlideData.Clone();

    slidePart.CloneSlideLayout(slideLayoutPart); // extension method

    presentationPart.AppendSlide(slidePart); // extension method

}

我在 OpenXmlUtils.cs 中添加了以下扩展方法:

public static void CloneSlideLayout(this SlidePart newSlidePart, SlideLayoutPart slPart, string id)
{
    // creates a Slide from a SlideLayout

    /* ensure we added the rel ID to this part */
    newSlidePart.AddPart(slPart, id);
    using (Stream stream = slPart.GetStream()) { newSlidePart.SlideLayoutPart.FeedData(stream); }

    newSlidePart.Slide.CommonSlideData = (CommonSlideData)slPart.SlideLayout.CommonSlideData.Clone();

    foreach (ImagePart iPart in slPart.ImageParts)
    {
        newSlidePart.AddPart<ImagePart>(iPart, slPart.GetIdOfPart(iPart));
    }

}

public static uint GetNextSlideId(this SlideIdList slideIdList)
{
    uint nextId;
    uint maxId = GetMaxSlideId(slideIdList);
    if (maxId == 0)
    {
        // Slide Id must be >= 256
        nextId = 256;
    }
    else
    {
        nextId = maxId++;
    }
    return nextId;
}
public static uint GetMaxSlideId(this SlideIdList slideIdList)
{

    // find the highest id
    uint maxSlideId = 0;
    if (slideIdList.ChildElements.Count() > 0)
        maxSlideId = slideIdList.ChildElements
            .Cast<SlideId>()
            .Max(x => x.Id.Value);
    return maxSlideId;
}
public static SlideLayoutPart GetSlideLayoutPartByLayoutName(this SlideMasterPart slideMasterPart, string layoutName)
{
    return slideMasterPart.SlideLayoutParts.SingleOrDefault
            (sl => sl.SlideLayout.CommonSlideData.Name.Value.Equals(layoutName, StringComparison.OrdinalIgnoreCase));
}

public static void AppendSlide(this PresentationPart presentationPart, SlidePart newSlidePart)
{
        SlideMasterPart slideMasterPart = presentationPart.SlideMasterParts.FirstOrDefault();
        SlideLayoutPart slideLayoutPart = slideMasterPart.GetSlideLayoutPartByLayoutName(layoutName);

        Slide slide = new Slide(  );
        SlidePart slidePart = presentationPart.AddNewPart<SlidePart>();
        slide.Save(slidePart);

        string id = slideMasterPart.GetIdOfPart(slideLayoutPart);
        slidePart.CloneSlideLayout(slideLayoutPart, id);

        presentationPart.AppendSlide(slidePart); 
}

实施了这些更改后,我可以成功制作出母版的“一个内容”幻灯片,看起来其他布局的 most 也可以正确输出,但是如果我尝试创建每个幻灯片版式的一个实例,仍然需要解决一个“修复”问题。

更新

答案 1 :(得分:0)

知道了。以下内容适用于我的测试场景(感谢您的代码的帮助):

    presentationPart.InsertNewSlide("CV Full page");
    presentationPart.InsertNewSlide("CV Half page");
    presentationPart.InsertNewSlide("Credential full page");
    presentationPart.InsertNewSlide("CV or Credential 5 to a page", 3);

    public static void InsertNewSlide(this PresentationPart presentationPart, string layoutName, int? position = null)
    {
        Slide slide = new Slide();
        SlidePart slidePart = presentationPart.AddNewPart<SlidePart>();
        slide.Save(slidePart);

        SlideMasterPart slideMasterPart = presentationPart.SlideMasterParts.FirstOrDefault();
        SlideLayoutPart slideLayoutPart = slideMasterPart.GetSlideLayoutPartByLayoutName(layoutName);

        slidePart.AddPart(slideLayoutPart, slideMasterPart.GetIdOfPart(slideLayoutPart));
        slidePart.Slide.CommonSlideData = (CommonSlideData)slideLayoutPart.SlideLayout.CommonSlideData.Clone();

        string id = slideMasterPart.GetIdOfPart(slideLayoutPart);
        slidePart.CloneSlideLayout(slideLayoutPart, id);

        slideMasterPart.AddPart(slideLayoutPart, id);
        presentationPart.SetSlideID(slidePart, position);
    }

    public static void SetSlideID(this PresentationPart presentationPart, SlidePart slidePart, int? position = null)
    {
        SlideIdList slideIdList = presentationPart.Presentation.SlideIdList;
        if (slideIdList == null)
        {
            slideIdList = new SlideIdList();
            presentationPart.Presentation.SlideIdList = slideIdList;
        }

        if (position != null && position > slideIdList.Count())
            throw new InvalidOperationException($"Unable to set slide to position '{position}'. There are only '{slideIdList.Count()}' slides.");

        uint newId = slideIdList.ChildElements.Count() == 0 ? 256 : slideIdList.GetMaxSlideId() + 1;
        if (position == null)
        {
            var newSlideId = slideIdList.AppendChild(new SlideId());
            newSlideId.Id = newId;
            newSlideId.RelationshipId = presentationPart.GetIdOfPart(slidePart);
        }
        else
        {
            SlideId nextSlideId = (SlideId)slideIdList.ChildElements[position.Value - 1];
            var newSlideId = slideIdList.InsertBefore(new SlideId(), nextSlideId);
            newSlideId.Id = newId;
            newSlideId.RelationshipId = presentationPart.GetIdOfPart(slidePart);
        }
    }

    public static uint GetMaxSlideId(this SlideIdList slideIdList)
    {
        uint maxSlideId = 0;
        if (slideIdList.ChildElements.Count() > 0)
            maxSlideId = slideIdList.ChildElements
                .Cast<SlideId>()
                .Max(x => x.Id.Value);
        return maxSlideId;
    }

    public static SlideLayoutPart GetSlideLayoutPartByLayoutName(this SlideMasterPart slideMasterPart, string layoutName)
    {
        return slideMasterPart.SlideLayoutParts.SingleOrDefault
                (sl => sl.SlideLayout.CommonSlideData.Name.Value.Equals(layoutName, StringComparison.OrdinalIgnoreCase));
    }

    public static void CloneSlideLayout(this SlidePart newSlidePart, SlideLayoutPart slPart, string id)
    {
        /* ensure we added the rel ID to this part */
        newSlidePart.AddPart(slPart, id);
        using (Stream stream = slPart.GetStream()) { newSlidePart.SlideLayoutPart.FeedData(stream); }

        newSlidePart.Slide.CommonSlideData = (CommonSlideData)slPart.SlideLayout.CommonSlideData.Clone();

        foreach (ImagePart iPart in slPart.ImageParts)
            newSlidePart.AddPart(iPart, slPart.GetIdOfPart(iPart));
    }