【问题标题】:C# OpenXML SDK - Inserting a new slide from slide mastersC# OpenXML SDK - 从幻灯片母版插入新幻灯片
【发布时间】:2023-03-18 07:30:01
【问题描述】:

我正在尝试实施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 实际提供的任何内容。

这似乎应该是一个相当常见的用例,也许我的期望是错误的,但似乎给定一个 已经存在的幻灯片布局,你应该能够(相对轻松)创建一个新的幻灯片基于该布局,它将包含所有相同的占位符形状等。

【问题讨论】:

    标签: c# powerpoint openxml openxml-sdk


    【解决方案1】:

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

        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));
        }
    

    【讨论】:

      【解决方案2】:

      我注意到幻灯片的 .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 文件。
      • 幻灯片缺少返回幻灯片布局的 rel 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); 
      }
      

      实施了这些更改后,我可以成功地从母版生成“一个内容”幻灯片,并且看起来 大多数 其他布局也正确输出,但如果我尝试创建每个幻灯片布局的一个实例,仍然存在我需要隔离的“修复”问题。

      更新

      【讨论】:

      • 我正在尝试做同样的事情。我想我最好让您知道您的代码无法编译。
      • 祝你好运。我一直无法弄清楚并放弃了 C# 方法,转而使用 python-pptx 库。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-21
      相关资源
      最近更新 更多