Solución:
Aunque la documentación de OpenXML no es excelente, existe una herramienta excelente que puede utilizar para ver cómo se crean los documentos de Word existentes. Si instala OpenXml SDK, viene con el DocumentReflector.exe herramienta debajo del SDK V2.0 herramientas de formato XML abierto directorio.
Las imágenes en los documentos de Word constan de los datos de la imagen y un ID que se le asigna y que se hace referencia en el cuerpo del documento. Parece que su problema se puede dividir en dos partes: encontrar el ID de la imagen en el documento, y luego reescribiendo los datos de la imagen para ello.
Para encontrar el ID de la imagen, deberá analizar MainDocumentPart. Las imágenes se almacenan en Runs como un elemento de dibujo
<w:p>
<w:r>
<w:drawing>
<wp:inline>
<wp:extent cx="3200400" cy="704850" /> <!-- describes the size of the image -->
<wp:docPr id="2" name="Picture 1" descr="filename.JPG" />
<a:graphic>
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic>
<pic:nvPicPr>
<pic:cNvPr id="0" name="filename.JPG" />
<pic:cNvPicPr />
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId5" /> <!-- this is the ID you need to find -->
<a:stretch>
<a:fillRect />
</a:stretch>
</pic:blipFill>
<pic:spPr>
<a:xfrm>
<a:ext cx="3200400" cy="704850" />
</a:xfrm>
<a:prstGeom prst="rect" />
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
En el ejemplo anterior, necesita encontrar el ID de la imagen almacenada en el elemento blip. La forma de encontrar eso depende de su problema, pero si conoce el nombre de archivo de la imagen original, puede mirar el elemento docPr:
using (WordprocessingDocument document = WordprocessingDocument.Open("docfilename.docx", true)) {
// go through the document and pull out the inline image elements
IEnumerable<Inline> imageElements = from run in Document.MainDocumentPart.Document.Descendants<Run>()
where run.Descendants<Inline>().First() != null
select run.Descendants<Inline>().First();
// select the image that has the correct filename (chooses the first if there are many)
Inline selectedImage = (from image in imageElements
where (image.DocProperties != null &&
image.DocProperties.Equals("image filename"))
select image).First();
// get the ID from the inline element
string imageId = "default value";
Blip blipElement = selectedImage.Descendants<Blip>().First();
if (blipElement != null) {
imageId = blipElement.Embed.Value;
}
}
Luego, cuando tenga la identificación de la imagen, puede usarla para reescribir los datos de la imagen. Creo que así es como lo harías:
ImagePart imagePart = (ImagePart)document.MainDocumentPart.GetPartById(imageId);
byte[] imageBytes = File.ReadAllBytes("new_image.jpg");
BinaryWriter writer = new BinaryWriter(imagePart.GetStream());
writer.Write(imageBytes);
writer.Close();
Me gustaría actualizar este hilo y agregarlo a la respuesta de Adam anterior para el beneficio de otros.
De hecho, logré hackear un código que funcionaba juntos el otro día (antes de que Adam publicara su respuesta), pero fue bastante difícil. La documentación es realmente deficiente y no hay mucha información disponible.
No sabía sobre el Inline
y Run
elementos que Adam usa en su respuesta, pero el truco parece estar en llegar a la Descendants<>
propiedad y luego puede analizar prácticamente cualquier elemento como un mapeo XML normal.
byte[] docBytes = File.ReadAllBytes(_myFilePath);
using (MemoryStream ms = new MemoryStream())
{
ms.Write(docBytes, 0, docBytes.Length);
using (WordprocessingDocument wpdoc = WordprocessingDocument.Open(ms, true))
{
MainDocumentPart mainPart = wpdoc.MainDocumentPart;
Document doc = mainPart.Document;
// now you can use doc.Descendants<T>()
}
}
Una vez que lo tienes, es bastante fácil buscar cosas, aunque tienes que averiguar cómo se llama todo. Por ejemplo, el <pic:nvPicPr>
es Picture.NonVisualPictureProperties
etc.
Como Adam dice correctamente, el elemento que necesita encontrar para reemplazar la imagen es el Blip
elemento. Pero debe encontrar el blip correcto que corresponda a la imagen que está tratando de reemplazar.
Adam muestra una forma de usar el Inline
elemento. Simplemente me sumergí y busqué todos los elementos de la imagen. No estoy seguro de cuál es la forma mejor o más robusta (no sé qué tan consistente es la estructura xml entre documentos y si esto causa que se rompa el código).
Blip GetBlipForPicture(string picName, Document document)
{
return document.Descendants<Picture>()
.Where(p => picName == p.NonVisualPictureProperties.NonVisualDrawingProperties.Name)
.Select(p => p.BlipFill.Blip)
.Single(); // return First or ToList or whatever here, there can be more than one
}
Vea el ejemplo XML de Adam para entender los diferentes elementos aquí y ver lo que estoy buscando.
El blip tiene una ID en el Embed
propiedad, por ejemplo: <a:blip r:embed="rId4" cstate="print" />
, lo que hace es asignar el Blip a una imagen en la carpeta Media (puede ver todas estas carpetas y archivos si cambia el nombre de su .docx a .zip y lo descomprime). Puedes encontrar el mapeo en _relsdocument.xml.rels
:
<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png" />
Entonces, lo que debe hacer es agregar una nueva imagen y luego apuntar este punto a la identificación de su imagen recién creada:
// add new ImagePart
ImagePart newImg = mainPart.AddImagePart(ImagePartType.Png);
// Put image data into the ImagePart (from a filestream)
newImg .FeedData(File.Open(_myImgPath, FileMode.Open, FileAccess.Read));
// Get the blip
Blip blip = GetBlipForPicture("MyPlaceholder.png", doc);
// Point blip at new image
blip.Embed = mainPart.GetIdOfPart(newImg);
Supongo que esto solo deja huérfana a la imagen anterior en la carpeta Media, lo cual no es ideal, aunque tal vez sea lo suficientemente inteligente como para recolectarla, por así decirlo. Puede que haya una forma mejor de hacerlo, pero no pude encontrarla.
De todos modos, ahí lo tienes. Este hilo es ahora la documentación más completa sobre cómo intercambiar una imagen en cualquier lugar de la web (lo sé, pasé horas buscando). Así que, con suerte, algunas personas lo encontrarán útil.
Me divertí lo mismo tratando de averiguar cómo hacer esto hasta que vi este hilo. Excelentes respuestas útiles chicos.
Una forma sencilla de seleccionar ImagePart si conoce el nombre de la imagen en el paquete es verificar el Uri
ImagePart GetImagePart(WordprocessingDocument document, string imageName)
{
return document.MainDocumentPart.ImageParts
.Where(p => p.Uri.ToString().Contains(imageName)) // or EndsWith
.First();
}
Entonces puedes hacer
var imagePart = GetImagePart(document, imageName);
var newImageBytes = GetNewImageBytes(): // however the image is generated or obtained
using(var writer = new BinaryWriter(imagePart.GetStream()))
{
writer.Write(newImageBytes);
}