代码之家  ›  专栏  ›  技术社区  ›  David Zemens

c openxml sdk-从幻灯片母版插入新幻灯片

  •  0
  • David Zemens  · 技术社区  · 6 年前

    我正在尝试实现给出的解决方案 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结构,我所能收集到的只是它在某种程度上默认为 第一 master中的布局(“title”),因为它不知道如何处理通过openxml实际给出的内容。

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

    1 回复  |  直到 6 年前
        1
  •  1
  •   Matt Fitzmaurice    5 年前

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

        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
  •  0
  •   David Zemens    6 年前

    我注意到幻灯片上的一些差异 .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 身份证件 回到幻灯片布局,似乎很容易。我向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); 
    }
    

    实现了这些更改后,我可以从主控形状成功地制作“一个内容”幻灯片,它看起来像 其他布局的输出也都是正确的,但是如果我尝试为每个幻灯片布局创建一个实例,仍然有一个“修复”问题需要隔离。

    更新 以下内容: