我有一个.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实际提供的内容。
这似乎应该是一个相当普遍的用例,也许我的期望是错误的,但是在给定已经存在的幻灯片布局的情况下,您应该能够(相对轻松地)根据该布局创建一个新幻灯片 ,其中将包含所有相同的占位符形状,等等。
答案 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文件。_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));
}