InsertDocument with a document that contains Images results in message "This image cannot currently be displayed"

Jun 25, 2014 at 9:20 PM
I have an existing document:
Dim targetDoc As DocX = DocX.Load(targetPath)
I also have a template document that I am populating, including adding a number of images:
Dim templateDoc As DocX = DocX.Load(templatePath)
templateDoc.ReplaceText("[FindMe]", "Found")

Dim docImage As Image = templateDoc.AddImage(memoryStream)
Dim docPicture As Picture = docImage.CreatePicture()
Dim p As Paragraph = templateDoc.InsertParagraph
p.InsertPicture(docPicture)
If I save the template document as is, then the images display correctly:
templateDoc.SaveAs(localPath)
If, however, I append the template document to the end of the target Document, then the images only show as an error "This image cannot be currently displayed":
targetDoc.InsertSectionPageBreak()
targetDoc.InsertDocument(templateDoc)
targetDoc.SaveAs(localPath)
When I look inside the word\media folder of the targetDoc I don't see the .jpeg files that should be coming from the template document. So, it looks like I need to add the images to the target document at the same time I am adding them to the template document. I could do that, but the problem is that the FileName property of the Picture elements in the template document are read only so there is no way to associate the Pictures with the new FileName values they should have from the the target document media files.

Is there a way to make this work?
Jun 26, 2014 at 2:52 PM
I believe there are two parts of the merge_images method (in DocX.cs) that are causing this.

The first problem is that the method can't find any reference in the remote document's relationships to the image, I believe this is caused by the replacement of "/word" by "".
To fix this, I have changed that part of the code as follows:
//Change this...
            var remote_rel = remote_document.mainPart.GetRelationships().Where(r => r.TargetUri.OriginalString.Equals(remote_pp.Uri.OriginalString.Replace("/word/", ""))).FirstOrDefault();
            if (remote_rel == null)
                    return;
//...to this:
            var remote_rel = remote_document.mainPart.GetRelationships().Where(r => r.TargetUri.OriginalString.Equals(remote_pp.Uri.OriginalString.Replace("/word/", ""))).FirstOrDefault();
            if (remote_rel == null)
            {
                remote_rel = remote_document.mainPart.GetRelationships().Where(r => r.TargetUri.OriginalString.Equals(remote_pp.Uri.OriginalString)).FirstOrDefault();
                if (remote_rel == null)
                    return;
            }
That adds the "physical" image. However, the error "This image cannot be currently displayed" is still displayed. That's because the second part of the problem is that the relationship Id in the document.xml.rels file has been updated in the target document, but the reference inside the document.xml is still the same as the source document's Id. To fix that, I added a part where the local relationship id is updated. That did the trick for me when it came to adding a picture that had been inserted using DocX. However, I found that when both source and target documents have 'existing' images (inserted in Word with a default relationship id, like "rId5") adding the elems_local part could cause an existing image in the target document to be changed into the existing image of the source document, if they had the same rId. So updating the local relId only happens if the remote_id doesn't match the "rId + 'number(s)'" pattern.
//Change this...
                // Replace all instances of remote_Id in the local document with local_Id
                var elems = remote_mainDoc.Descendants(XName.Get("blip", DocX.a.NamespaceName));
                foreach (var elem in elems)
                {
                    XAttribute embed = elem.Attribute(XName.Get("embed", DocX.r.NamespaceName));
                    if (embed != null && embed.Value == remote_Id)
                    {
                        embed.SetValue(new_Id);
                    }
                }

                // Replace all instances of remote_Id in the local document with local_Id (for shapes as well)
                var v_elems = remote_mainDoc.Descendants(XName.Get("imagedata", DocX.v.NamespaceName));
                foreach (var elem in v_elems)
                {
                    XAttribute id = elem.Attribute(XName.Get("id", DocX.r.NamespaceName));
                    if (id != null && id.Value == remote_Id)
                    {
                        id.SetValue(new_Id);
                    }
                }

//... to this:

                //Check if the remote relationship id is a default rId from Word
                Match defRelId = Regex.Match(remote_Id, @"rId\d+", RegexOptions.IgnoreCase);
               
                // Replace all instances of remote_Id in the local document with local_Id
                var elems = remote_mainDoc.Descendants(XName.Get("blip", DocX.a.NamespaceName));
                foreach (var elem in elems)
                {
                    XAttribute embed = elem.Attribute(XName.Get("embed", DocX.r.NamespaceName));
                    if (embed != null && embed.Value == remote_Id)
                    {
                        embed.SetValue(new_Id);
                    }
                }                
               
                if (!defRelId.Success)
                {
                    // Replace all instances of remote_Id in the local document with local_Id
                    var elems_local = mainDoc.Descendants(XName.Get("blip", DocX.a.NamespaceName));
                    foreach (var elem in elems_local)
                    {
                        XAttribute embed = elem.Attribute(XName.Get("embed", DocX.r.NamespaceName));
                        if (embed != null && embed.Value == remote_Id)
                        {
                            embed.SetValue(new_Id);
                        }
                    }
                    
                                    
                    // Replace all instances of remote_Id in the local document with local_Id (for shapes as well)
                    var v_elems_local = mainDoc.Descendants(XName.Get("imagedata", DocX.v.NamespaceName));
                    foreach (var elem in v_elems_local)
                    {
                        XAttribute id = elem.Attribute(XName.Get("id", DocX.r.NamespaceName));
                        if (id != null && id.Value == remote_Id)
                        {
                            id.SetValue(new_Id);
                        }
                    }
                }

                // Replace all instances of remote_Id in the local document with local_Id (for shapes as well)
                var v_elems = remote_mainDoc.Descendants(XName.Get("imagedata", DocX.v.NamespaceName));
                foreach (var elem in v_elems)
                {
                    XAttribute id = elem.Attribute(XName.Get("id", DocX.r.NamespaceName));
                    if (id != null && id.Value == remote_Id)
                    {
                        id.SetValue(new_Id);
                    }
                }
Are you using the (lastest) source code and could you verify if these changes tot he merge_images method have the desired results for you as well?
Jun 26, 2014 at 3:38 PM
Thanks! I'll get the latest code and give it a try.
Jun 26, 2014 at 8:10 PM
Those changes worked for me. Thanks!
Jun 27, 2014 at 2:19 AM
Actually, I have one tweak to add.

In the case where an image is already present in the target document, but it is located in the \word\media folder instead of the \media folder, then the above code fails.

In merge_images I added another search for the local_rel that doesn't replace /word/:
//Change this
                    // This image already exists in this document.
                    found = true;

                    var local_rel = mainPart.GetRelationships().Where(r => r.TargetUri.OriginalString.Equals(part.Uri.OriginalString.Replace("/word/", ""))).FirstOrDefault();

//To this
                    // This image already exists in this document.
                    found = true;

                    var local_rel = mainPart.GetRelationships().Where(r => r.TargetUri.OriginalString.Equals(part.Uri.OriginalString.Replace("/word/", ""))).FirstOrDefault();
                    if (local_rel == null)
                    {
                        local_rel = mainPart.GetRelationships().Where(r => r.TargetUri.OriginalString.Equals(part.Uri.OriginalString)).FirstOrDefault();
                    }
Jun 27, 2014 at 10:08 AM
Edited Jun 27, 2014 at 1:59 PM
I'm glad that worked for you and thanks for adding that part, didn't come across that problem while testing so I must have overlooked it.
I'll see if I'm able to further test our combined changes and submit them as a patch today.

Edit: I just uploaded the patch.
Developer
Jun 27, 2014 at 3:00 PM
Thanks. Applied. Please test if everything is correct.
Jun 30, 2014 at 8:51 AM
Hi MadBoy, thanks for applying the patch.
It didn't work for me because in line 1159 (in my editor) there's still a return after the first check if the remote_rel is null. That return should be removed so the image will be found inside the /word/media folder as well.
Developer
Jun 30, 2014 at 8:59 AM
Done.
Developer
Jun 30, 2014 at 9:18 AM
I'll also apply https://docx.codeplex.com/workitem/13080 later on, as long as it fixes the other issue with images.