#c# #powerpoint #openxml
#c# #powerpoint #openxml
Вопрос:
У меня есть код, который копирует содержимое одного слайда PowerPoint в другой. Ниже приведен пример обработки изображений.
foreach (OpenXmlElement element in sourceSlide.CommonSlideData.ShapeTree.ChildElements.ToList())
{
string elementType = element.GetType().ToString();
if (elementType.EndsWith(".Picture"))
{
// Deep clone the element.
elementClone = element.CloneNode(true);
var picture = (Picture)elementClone;
// Get the picture's original rId
var blip = picture.BlipFill.Blip;
string rId = blip.Embed.Value;
// Retrieve the ImagePart from the original slide by rId
ImagePart sourceImagePart = (ImagePart)sourceSlide.SlidePart.GetPartById(rId);
// Add the image part to the new slide, letting OpenXml generate the new rId
ImagePart targetImagePart = targetSlidePart.AddImagePart(sourceImagePart.ContentType);
// And copy the image data.
targetImagePart.FeedData(sourceImagePart.GetStream());
// Retrieve the new ID from the target image part,
string id = targetSlidePart.GetIdOfPart(targetImagePart);
// and assign it to the picture.
blip.Embed.Value = id;
// Get the shape tree that we're adding the clone to and append to it.
ShapeTree shapeTree = targetSlide.CommonSlideData.ShapeTree;
shapeTree.Append(elementClone);
}
Этот код работает нормально. Для других сценариев, таких как графические фреймы, это выглядит немного иначе, потому что каждый графический фрейм может содержать несколько объектов изображения.
// Go thru all the Picture objects in this GraphicFrame.
foreach (var sourcePicture in element.Descendants<Picture>())
{
string rId = sourcePicture.BlipFill.Blip.Embed.Value;
ImagePart sourceImagePart = (ImagePart)sourceSlide.SlidePart.GetPartById(rId);
var contentType = sourceImagePart.ContentType;
var targetPicture = elementClone.Descendants<Picture>().First(x => x.BlipFill.Blip.Embed.Value == rId);
var targetBlip = targetPicture.BlipFill.Blip;
ImagePart targetImagePart = targetSlidePart.AddImagePart(contentType);
targetImagePart.FeedData(sourceImagePart.GetStream());
string id = targetSlidePart.GetIdOfPart(targetImagePart);
targetBlip.Embed.Value = id;
}
Теперь мне нужно сделать то же самое с OLE-объектами.
// Go thru all the embedded objects in this GraphicFrame.
foreach (var oleObject in element.Descendants<OleObject>())
{
// Get the rId of the embedded OLE object.
string rId = oleObject.Id;
// Get the EmbeddedPart from the source slide.
var embeddedOleObj = sourceSlide.SlidePart.GetPartById(rId);
// Get the content type.
var contentType = embeddedOleObj.ContentType;
// Create the Target Part. Let OpenXML assign an rId.
var targetObjectPart = targetSlide.SlidePart.AddNewPart<EmbeddedObjectPart>(contentType, null);
// Get the embedded OLE object data from the original object.
var objectStream = embeddedOleObj.GetStream();
// And give it to the ObjectPart.
targetObjectPart.FeedData(objectStream);
// Get the new rId and assign it to the OLE Object.
string id = targetSlidePart.GetIdOfPart(targetObjectPart);
oleObject.Id = id;
}
Но это не сработало. Результирующий PowerPoint поврежден.
Что я делаю не так?
ПРИМЕЧАНИЕ: Весь код работает, за исключением rId
обработки в OLE-объекте. Я знаю, что это работает, потому что, если я просто передам оригинал rId
из исходного объекта в часть целевого объекта, вот так:
var targetObjectPart = targetSlide.SlidePart
.AddNewPart<EmbeddedObjectPart>(contentType, rId);
он будет функционировать должным образом, пока это rId
еще не существует на целевом слайде, что, очевидно, не будет работать каждый раз, когда мне это нужно.
Исходный слайд и целевой слайд поступают из разных файлов PPTX. Мы используем OpenXML, а не Office Interop.
Комментарии:
1. Просто дикое предположение, но я вижу вызовы GetStream() в вашем коде без каких-либо связанных вызовов Close / Dispose. Возможно, неявное, но я бы рассмотрел это.
Ответ №1:
Поскольку вы не предоставили полный код, трудно сказать, что не так.
Я предполагаю, что вы не изменяете правильный объект.
В вашем примере кода для изображений вы создаете и изменяете elementClone
.
В вашем примере кода для ole-объектов вы работаете с и модифицируете oleObject
(который является потомком element
), и из контекста не совсем ясно, является ли это частью исходного документа или целевого документа.
Вы можете попробовать этот минимальный пример:
- используйте новый pptx с одним встроенным ole-объектом для
c:testdatainput.pptx
- используйте новый pptx (пустой) для
c:testdataoutput.pptx
После запуска кода я смог открыть встроенный ole-объект в выходном документе.
using DocumentFormat.OpenXml.Presentation;
using DocumentFormat.OpenXml.Packaging;
using System.Linq;
namespace ooxml
{
class Program
{
static void Main(string[] args)
{
CopyOle("c:\testdata\input.pptx", "c:\testdata\output.pptx");
}
private static void CopyOle(string inputFile, string outputFile)
{
using (PresentationDocument sourceDocument = PresentationDocument.Open(inputFile, true))
{
using (PresentationDocument targetDocument = PresentationDocument.Open(outputFile, true))
{
var sourceSlidePart = sourceDocument.PresentationPart.SlideParts.First();
var targetSlidePart = targetDocument.PresentationPart.SlideParts.First();
foreach (var element in sourceSlidePart.Slide.CommonSlideData.ShapeTree.ChildElements)
{
//clones an element, does not copy the actual relationship target (e.g. pptembeddingsoleObject1.bin)
var elementClone = element.CloneNode(true);
//for each cloned OleObject, fix its relationship
foreach(var clonedOleObject in elementClone.Descendants<OleObject>())
{
//find the original EmbeddedObjectPart in the source document
//(we can use the id from the clonedOleObject to do that, since it contained the same id
// as the source ole object)
var sourceObjectPart = sourceSlidePart.GetPartById(clonedOleObject.Id);
//create a new EmbeddedObjectPart in the target document and copy the data from the original EmbeddedObjectPart
var targetObjectPart = targetSlidePart.AddEmbeddedObjectPart(sourceObjectPart.ContentType);
targetObjectPart.FeedData(sourceObjectPart.GetStream());
//update the relationship target on the clonedOleObject to point to the newly created EmbeddedObjectPath
clonedOleObject.Id = targetSlidePart.GetIdOfPart(targetObjectPart);
}
//add cloned element to the document
targetSlidePart.Slide.CommonSlideData.ShapeTree.Append(elementClone);
}
targetDocument.PresentationPart.Presentation.Save();
}
}
}
}
}
Что касается устранения неполадок, расширение Chrome для OOXML Tools оказалось полезным.
Это позволяет сравнивать структуру двух документов, поэтому намного проще проанализировать, что пошло не так.
Примеры:
- если бы вы клонировали только все элементы, вы могли бы увидеть, что / ppt/ embeddings /* и / ppt / media /* будут отсутствовать
- или вы можете проверить правильность связей (например, во входном документе используется «rId1» для ссылки на встроенные данные, а в выходном документе используется «R3a2fa0c37eaa42b5»)