I'm an intern at a large company who got tasked with working on a previous intern's project that apparently worked at some point, but now it's broken. What the program does, is it takes a bunch of text and images out of a document and inserts them into a template document. The problem is, about half of the images aren't forming relationships and I'm getting the red X "Image cannot be displayed" empty box. I've been doing some digging with the productivity tool, and I found out that there are a couple duplicate IDs, as well as quite a few non-existent relationships, although looking at his code I'm not sure what might be causing that. Here are his 2 methods for copying images:
internal static void CopyImages(OpenXmlElement oldTable, OpenXmlElement newTable,
WordprocessingDocument testData, WordprocessingDocument testReport)
{
List<Blip> sourceBlips = DocumentHelper.GetAllBlips(oldTable);
List<Blip> targetBlips = DocumentHelper.GetAllBlips(newTable);
foreach (Blip sourceBlip in sourceBlips)
{
foreach (Blip targetBlip in targetBlips)
{
if (targetBlip.Embed.Value == sourceBlip.Embed.Value)
{
if (testData.MainDocumentPart.GetPartById(sourceBlip.Embed.Value) is ImagePart imagePart)
{
ImagePart newImagePart = testReport.MainDocumentPart.AddPart(imagePart);
targetBlip.Embed.Value = testReport.MainDocumentPart.GetIdOfPart(newImagePart);
break;
}
}
}
}
}
internal static void CopyEmbeddedVisioImages(OpenXmlElement oldTable, OpenXmlElement newTable,
WordprocessingDocument testData, WordprocessingDocument testReport)
{
List<EmbeddedObject> sourceObjects = oldTable.Descendants<EmbeddedObject>().ToList();
List<EmbeddedObject> targetObjects = newTable.Descendants<EmbeddedObject>().ToList();
foreach (EmbeddedObject targetobj in targetObjects)
{
foreach (EmbeddedObject sourceObj in sourceObjects)
{
if (testData.MainDocumentPart.GetPartById(sourceObj.Descendants<ImageData>()
.FirstOrDefault().RelationshipId) is ImagePart oldImagePart)
{
ImagePart newImagePart = testReport.MainDocumentPart.AddPart(oldImagePart);
targetobj.Descendants<ImageData>().FirstOrDefault().RelationshipId =
testReport.MainDocumentPart.GetIdOfPart(newImagePart);
}
if (testData.MainDocumentPart.GetPartById(sourceObj.Descendants<OleObject>()
.FirstOrDefault().Id) is OpenXmlPart openXmlPart)
{
EmbeddedObjectPart newEmbeddedObj = (EmbeddedObjectPart)testReport.MainDocumentPart.AddPart(openXmlPart);
targetobj.Descendants<OleObject>().FirstOrDefault().Id =
testReport.MainDocumentPart.GetIdOfPart(newEmbeddedObj);
}
}
}
}
I've tried calling Save() and Close() on the documents. I even tried calling Dispose(). using(WordprocessingDocument foo = WordprocessingDocument.Open(bar, false){}
doesn't seem to help either. I'm not too worried about the duplicate IDs for now, but I have no idea why only some of the relationships are being formed while others are not. This is a massive project so navigating through some of it can be pretty tricky.
Edit: It's probably also worth mentioning that the images stop forming relationships at a certain point. It isn't random. About 2/3 of the way down none of the images work.
Here's the updated set of methods
internal static void CopyImages(OpenXmlElement oldTable, OpenXmlElement newTable,
WordprocessingDocument testData, WordprocessingDocument testReport)
{
List<Blip> sourceBlips = DocumentHelper.GetAllBlips(oldTable);
List<Blip> targetBlips = DocumentHelper.GetAllBlips(newTable);
foreach (Blip sourceBlip in sourceBlips)
{
foreach (Blip targetBlip in targetBlips)
{
if (targetBlip.Embed.Value == sourceBlip.Embed.Value)
{
if (testData.MainDocumentPart.GetPartById(sourceBlip.Embed.Value) is ImagePart imagePart)
{
//ImagePart newImagePart = testReport.MainDocumentPart.AddPart(imagePart);
ImagePart newImagePart = testReport.MainDocumentPart.AddImagePart(imagePart.ContentType);
newImagePart.FeedData(imagePart.GetStream(FileMode.Open, FileAccess.Read));
targetBlip.Embed.Value = testReport.MainDocumentPart.GetIdOfPart(newImagePart);
break;
}
}
}
}
}
internal static void CopyEmbeddedVisioImages(OpenXmlElement oldTable, OpenXmlElement newTable,
WordprocessingDocument testData, WordprocessingDocument testReport)
{
List<EmbeddedObject> sourceObjects = oldTable.Descendants<EmbeddedObject>().ToList();
List<EmbeddedObject> targetObjects = newTable.Descendants<EmbeddedObject>().ToList();
foreach (EmbeddedObject targetobj in targetObjects)
{
foreach (EmbeddedObject sourceObj in sourceObjects)
{
if (testData.MainDocumentPart.GetPartById(sourceObj.Descendants<ImageData>()
.FirstOrDefault().RelationshipId) is ImagePart oldImagePart)
{
//ImagePart newImagePart = testReport.MainDocumentPart.AddPart(oldImagePart);
ImagePart newImagePart = testReport.MainDocumentPart.AddImagePart(oldImagePart.ContentType);
newImagePart.FeedData(oldImagePart.GetStream(FileMode.Open, FileAccess.Read));
targetobj.Descendants<ImageData>().FirstOrDefault().RelationshipId =
testReport.MainDocumentPart.GetIdOfPart(newImagePart);
}
if (testData.MainDocumentPart.GetPartById(sourceObj.Descendants<OleObject>()
.FirstOrDefault().Id) is OpenXmlPart openXmlPart)
{
EmbeddedObjectPart newEmbeddedObj = (EmbeddedObjectPart)testReport.MainDocumentPart.AddPart(openXmlPart);
targetobj.Descendants<OleObject>().FirstOrDefault().Id =
testReport.MainDocumentPart.GetIdOfPart(newEmbeddedObj);
}
}
}
}
Here's an update on my findings.
targetBlip.Embed.Value != sourceBlip.Embed.Value
in most cases or maybe it's something else?Images from a source document can't be added as-is into a target document; an image has a unique id/number within its parent document and this one might conflict with the target document if one already exists with that same id. Replace the following line
ImagePart newImagePart = testReport.MainDocumentPart.AddPart(imagePart);
with the one below. Here a whole new image file gets embedded and gets a new id assigned.
ImagePart newImagePart = testReport.MainDocumentPart.AddImagePart(oldImagePart.ContentType);
newImagePart.FeedData(oldImagePart.GetStream(FileMode.Open, FileAccess.Read));
It's important that the ids in the target document are unique. I share some (old(er)) code fragments about how I handled to merge images from one document into another. (This is a fragment of a more complete/complex implementation where duplicate images are being detected and prevented from being inserted more than once.)
It starts by iterating over all Drawings in the source document and building a list of these together with their original id as in this source document. Then all images get inserted into the target document; while doing so the new id as in the target document gets mapped to each item.
Each drawing in the source document gets updated with the id as in the target document; the list contains both orginal source and new target ids. (This sounds bizarre, but for me at that moment only this gave the expected result.)
Only after the image merge has completed, the content (paragraphs and tables) get merged into the target document, which consists of adding clones of these items.
public class DocumentMerger
{
private readonly WordprocessingDocument _targetDocument;
public DocumentMerger(WordprocessingDocument targetDocument)
{
this._targetDocument = targetDocument;
}
public void Merge(WordprocessingDocument sourceDocument)
{
ImagesMerger imagesMerger = new ImagesMerger(this._targetDocument);
this._imagesMerger.Merge(sourceDocument);
// Merge the content; paragraphs and tables.
this._targetDocumentPart.Document.Save();
}
}
public class ImageInfo
{
private String _id;
private ImagePart _image;
private readonly String _originalId;
private ImageInfo(ImagePart image, String id)
{
this._id = id;
this._image = image;
this._originalId = id;
}
public String Id
{
get { return this._id; }
}
public ImagePart Image
{
get { return this._image; }
}
public String OriginalId
{
get { return this._originalId; }
}
public static ImageInfo Create(MainDocumentPart documentPart, ImagePart image)
{
String id = documentPart.GetIdOfPart(image);
ImageInfo r = new ImageInfo(image, id);
return r;
}
public void Reparent(MainDocumentPart documentPart)
{
ImagePart newImage = documentPart.AddImagePart(this._image.ContentType);
newImage.FeedData(this._image.GetStream(FileMode.Open, FileAccess.Read));
String newId = documentPart.GetIdOfPart(newImage);
this._id = newId;
this._image = newImage;
}
}
public class ImagesMerger
{
private readonly IList<ImageInfo> _imageInfosOfTheTargetDocument = new List<ImageInfo>();
private readonly MainDocumentPart _targetDocumentPart;
public ImagesMerger(WordprocessingDocument targetDocument)
{
this._targetDocumentPart = targetDocument.MainDocumentPart;
}
public void Merge(WordprocessingDocument sourceDocument)
{
MainDocumentPart sourceDocumentPart = sourceDocument.MainDocumentPart;
IList<ImageInfo> imageInfosOfTheSourceDocument = this.getImageInfos(sourceDocumentPart);
if (0 == imageInfosOfTheSourceDocument.Count) { return; }
this.addTheImagesToTheTargetDocument(imageInfosOfTheSourceDocument);
this.rereferenceTheImagesToTheirCorrespondingImageParts(sourceDocumentPart, imageInfosOfTheSourceDocument);
}
private void addTheImagesToTheTargetDocument(IList<ImageInfo> imageInfosOfTheSourceDocument)
{
for (Int32 i = 0, j = imageInfosOfTheSourceDocument.Count; i < j; i++)
{
imageInfoOfTheSourceDocument.Reparent(this._targetDocumentPart);
this._imageInfosOfTheTargetDocument.Add(imageInfoOfTheSourceDocument);
}
}
private IList<ImageInfo> getImageInfos(MainDocumentPart documentPart)
{
List<ImageInfo> r = new List<ImageInfo>();
foreach (ImagePart image in documentPart.ImageParts)
{
ImageInfo imageInfo = ImageInfo.Create(documentPart, image);
r.Add(imageInfo);
}
return r;
}
private void rereferenceTheImagesToTheirCorrespondingImageParts(MainDocumentPart sourceDocumentPart, IList<ImageInfo> imageInfosOfTheSourceDocument)
{
IEnumerable<Drawing> images = sourceDocumentPart.Document.Body.Descendants<Drawing>();
foreach (Drawing image in images)
{
Blip blip = image.Inline.Graphic.GraphicData.Descendants<Blip>().FirstOrDefault();
String originalId = blip.Embed.Value;
ImageInfo imageInfo = imageInfosOfTheSourceDocument.FirstOrDefault(o => o.OriginalId._Equals(originalId));
blip.Embed.Value = imageInfo.Id;
}
}
}