Remplacer l'image dans word doc en utilisant OpenXML

suite à ma dernière question ici

OpenXML semble faire exactement ce que je veux, mais la documentation est terrible. Une heure à chercher sur Google ne m'a pas permis de comprendre ce que je dois faire.

j'ai un document word. Je veux ajouter une image à ce document word (en utilisant word) de telle manière que je puisse ensuite ouvrir le document dans OpenXML et remplacer cette image. Devrait être assez simple, oui?

je suppose que je devrais être capable de donner à mon image 'placeholder' un id d'une sorte et puis utiliser GetPartById pour localiser l'image et la remplacer. Serait-ce la bonne méthode? Qu'est-ce que cette Id? Comment l'ajouter en utilisant Word?

chaque exemple que je peux trouver qui fait quelque chose de quelque peu similaire commence par construire le document entier de mot à partir de zéro dans ML, qui n'est vraiment pas beaucoup d'utilisation.

EDIT: il m'est venu à l'esprit qu'il serait plus facile de simplement remplacer l'image dans le dossier média par la nouvelle image, mais encore une fois je ne peux trouver aucune indication sur la façon de faire cela.

25
demandé sur Community 2010-05-11 15:13:55

8 réponses

bien que la documentation pour OpenXML ne soit pas Excellente, il y a un excellent outil que vous pouvez utiliser pour voir comment les documents Word existants sont construits. Si vous installez le SDK OpenXml, il est livré avec le DocumentReflector .exe outil sous le ouvrir le Format XML SDK\V2.0\tools directory.

les Images dans les documents Word se composent des données d'image et D'un ID qui leur est attribué et qui est référencé dans le corps du document. Il semble que votre problème peut être décomposé en deux parties: trouver l'ID de l'image dans le document, puis la ré-écriture des données d'image .

pour trouver L'ID de l'image, vous devez analyser la partie principale du document. Les Images sont stockées dans des tirages comme un élément de dessin

<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>

dans l'exemple ci-dessus, vous devez trouver L'ID de l'image stockée dans l'élément blip. Comment vous allez trouver cela dépend de votre problème, mais si vous connaissez le nom du fichier de l'image originale, vous pouvez regarder l'élément 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;
  }
}

Puis, quand vous avez l'ID de l'image, vous pouvez l'utiliser pour réécrire les données de l'image. Je pense que c'est ainsi que vous le feriez:

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();
32
répondu Adam Sheehan 2010-05-13 16:32:23

j'aimerais mettre à jour ce fil et ajouter à la réponse D'Adam ci-dessus pour le bénéfice des autres.

j'ai en fait réussi à hacker du code de travail ensemble l'autre jour, (avant Qu'Adam ait posté sa réponse) mais c'était assez difficile. La documentation est vraiment mauvaise et il n'y a pas beaucoup d'informations là-bas.

Je ne connaissais pas les éléments Inline et Run qu'Adam utilise dans sa réponse, mais le truc semble être dans accédez à la propriété Descendants<> et ensuite vous pouvez pratiquement analyser n'importe quel élément comme une cartographie XML normale.

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>()
    }
}

une fois que vous avez ce qu'il est assez facile de chercher des choses, bien que vous devez travailler sur ce que tout est appelé. Par exemple , le <pic:nvPicPr> est Picture.NonVisualPictureProperties , etc.

comme Adam dit correctement, l'élément que vous devez trouver pour remplacer l'image est l'élément Blip . Mais vous devez trouver le bon blip qui correspond à l'image que vous essayez de le remplacer.

Adam montre un chemin en utilisant l'élément Inline . J'ai plongé et j'ai cherché tous les éléments de l'image. Je ne suis pas sûr de savoir quelle est la meilleure ou la plus robuste façon (je ne sais pas comment la cohérence de la structure xml est entre les documents et si cette cause code de rupture).

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
}

voir L'exemple XML D'Adam pour donner un sens aux différents éléments ici et voir ce que je suis recherche pour.

le blip a un ID dans le Embed propriété, par exemple: <a:blip r:embed="rId4" cstate="print" /> , ce que cela fait est de mapper le Blip à une image dans le dossier médias (vous pouvez voir tous ces dossiers et les fichiers si vous vous renommer .docx en un .zip et décompressez-le). Vous pouvez trouver le mapping dans _rels\document.xml.rels :

<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png" />

donc ce que vous devez faire est d'ajouter une nouvelle image, et ensuite pointer ce blip à l'id de votre image nouvellement créée:

// 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);

je présume que cela ne fait que cacher la vieille image dans le dossier des médias qui n'est pas idéale, bien que peut-être c'est assez intelligent pour que les ordures la ramassent pour ainsi dire. Il y a peut-être un meilleur moyen, mais je ne l'ai pas trouvé.

bref, voilà. Ce fil est maintenant la documentation la plus complète sur la façon d'échanger une image n'importe où sur le web (Je le sais, j'ai passé des heures à chercher). J'espère donc que certaines personnes le trouveront utile.

17
répondu fearofawhackplanet 2012-07-19 17:46:35

j'ai eu le même plaisir à essayer de travailler sur la façon de faire ceci jusqu'à ce que j'ai vu ce fil. Excellentes réponses utiles, les gars.

une façon simple de sélectionner L'ImagePart si vous connaissez le nom de l'image dans le paquet est de vérifier L'Uri


ImagePart GetImagePart(WordprocessingDocument document, string imageName)
{
    return document.MainDocumentPart.ImageParts
        .Where(p => p.Uri.ToString().Contains(imageName)) // or EndsWith
        .First();
}

Vous pourrez alors faire une


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);
}

7
répondu Daniel 2011-07-11 03:04:36

le code suivant va récupérer les images à partir du document spécifié (nom du fichier) et les sauvegarder dans un D:\TestArea dossier utilisant les noms de fichiers internes. Les réponses sur cette page m'ont aidé à trouver ma solution.

Note: Cette solution n'aide pas quelqu'un à remplacer une image dans un mot doc, cependant dans toute ma recherche dans la façon de récupérer une image à partir d'un mot doc c'était le seul lien/le plus proche que j'ai pu trouver; juste au cas où quelqu'un d'autre est dans le même bateau I poste ma solution ici.

private void ProcessImages(string filename)
{
    var xpic = "";
    var xr = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";

    using (WordprocessingDocument document = WordprocessingDocument.Open(filename, true)) 
    {
        var imageParts = 
            from paragraph in document.MainDocumentPart.Document.Body
                from graphic in paragraph.Descendants<Graphic>()
                    let graphicData = graphic.Descendants<GraphicData>().FirstOrDefault()
                        let pic = graphicData.ElementAt(0)
                            let nvPicPrt = pic.ElementAt(0).FirstOrDefault()
                            let blip = pic.Descendants<Blip>().FirstOrDefault()
                            select new 
                            {
                                Id = blip.GetAttribute("embed",xr).Value,
                                Filename = nvPicPrt.GetAttribute("name",xpic).Value
                            };

        foreach(var image in imageParts)
        {
            var outputFilename = string.Format(@"d:\TestArea\{0}",image.Filename);
            Debug.WriteLine(string.Format("Creating file: {0}",outputFilename));

            // Get image from document
            var imageData = document.MainDocumentPart.GetPartById(image.Id);

            // Read image data into bytestream
            var stream = imageData.GetStream();
            var byteStream = new byte[stream.Length];
            int length = (int)stream.Length;
            stream.Read(byteStream, 0, length);

            // Write bytestream to disk
            using (var fileStream = new FileStream(outputFilename,FileMode.OpenOrCreate))
            {
                fileStream.Write(byteStream, 0, length);
            }
        }
    }
}
3
répondu BillKrat 2013-07-30 13:16:26

j'aime cette Section, parce qu'il y a tellement de mauvaise documentation sur ce sujet, et après de nombreuses heures d'essayer de faire fonctionner les réponses ci-dessus. Je suis venu avec ma propre solution.

Comment je donne l'Image d'un nom de balise:

enter image description here

d'abord je sélectionne l'Image que je veux remplacer dans word et lui donne un nom (par exemple "toremplace") ensuite je boucle à travers les dessins choisir L'Image avec le bon tagName et écrire ma propre Image à sa place.

private void ReplaceImage(string tagName, string imagePath)
{
    this.wordDoc = WordprocessingDocument.Open(this.stream, true);
    IEnumerable<Drawing> drawings = this.wordDoc.MainDocumentPart.Document.Descendants<Drawing>().ToList();
    foreach (Drawing drawing in drawings)
    {
        DocProperties dpr = drawing.Descendants<DocProperties>().FirstOrDefault();
        if (dpr != null && dpr.Name == tagName)
        {
            foreach (DocumentFormat.OpenXml.Drawing.Blip b in drawing.Descendants<DocumentFormat.OpenXml.Drawing.Blip>().ToList())
            {
                OpenXmlPart imagePart = wordDoc.MainDocumentPart.GetPartById(b.Embed);
                using (var writer = new BinaryWriter(imagePart.GetStream()))
                {
                    writer.Write(File.ReadAllBytes(imagePath));
                }
            }
        }
    }
}
2
répondu Ludisposed 2017-03-22 16:14:12

pour obtenir des images et les copier dans un dossier, vous pouvez utiliser une méthode plus simple

        System.Collections.Generic.IEnumerable<ImagePart> imageParts =  doc.MainDocumentPart.ImageParts;

        foreach (ImagePart img in imageParts)
        {
          var uri = img.Uri;
          var fileName = uri.ToString().Split('/').Last();
          var fileWordMedia = img.GetStream(FileMode.Open);
          string imgPath = mediaPath + fileName;//mediaPath it is folder
          FileStream fileHtmlMedia = new FileStream(imgPath, FileMode.Create);
          int i = 0;
          while (i != (-1))
          {
              i = fileWordMedia.ReadByte();
              if (i != (-1))
              {
                  fileHtmlMedia.WriteByte((byte)i);
              }
          }
          fileHtmlMedia.Close();
          fileWordMedia.Close();

        }
1
répondu barsmaga 2014-07-21 07:51:47

@Ludisposed excellente réponse a parfaitement fonctionné pour moi, mais il m'a fallu creuser un peu pour travailler sur la façon de réellement mettre le nom de l'image en mot en premier lieu. Pour tous ceux qui ne parlent pas l'Allemand, voici comment le faire:

dans MS Word, cliquez sur l'image puis dans le ruban D'accueil, sélectionnez Sélectionner - > Panneau de sélection dans le ruban pour afficher la liste des images dans la navigation de droite:

MS Word Selection Pane

vous pouvez alors cliquer sur le nom/étiquette d'une image dans le volet de sélection pour changer son nom:

Changing an Image name in the selection pane in MS Word

une fois que vous avez fait cela, vous pouvez voir comment ce texte a été incorporé dans le fichier XML ouvert en utilisant L'outil de productivité Open XML SDK 2.5:

enter image description here

ayant fait que j'ai légèrement étendu la solution de @Ludisposed dans une méthode réutilisable, et modifié le code de sorte que le passage dans un tableau d'octets nuls déclencherait la suppression de l'image du document:

/// <summary>
/// Replaces the image in a document with the new file bytes, or removes the image if the newImageBytes parameter is null.
/// Relies on a the image having had it's name set via the 'Selection Pane' in Word
/// </summary>
/// <param name="document">The OpenXML document</param>
/// <param name="oldImagesPlaceholderText">The placeholder name for the image set via Selection in Word</param>
/// <param name="newImageBytes">The new file. Pass null to remove the selected image from the document instead</param>
public void ReplaceInternalImage(WordprocessingDocument document, string oldImagesPlaceholderText, byte[] newImageBytes)
{
    var imagesToRemove = new List<Drawing>();

    IEnumerable<Drawing> drawings = document.MainDocumentPart.Document.Descendants<Drawing>().ToList();
    foreach (Drawing drawing in drawings)
    {
        DocProperties dpr = drawing.Descendants<DocProperties>().FirstOrDefault();
        if (dpr != null && dpr.Name == oldImagesPlaceholderText)
        {
            foreach (Blip b in drawing.Descendants<Blip>().ToList())
            {
                OpenXmlPart imagePart = document.MainDocumentPart.GetPartById(b.Embed);

                if (newImageBytes == null)
                {
                    imagesToRemove.Add(drawing);
                }
                else
                {
                    using (var writer = new BinaryWriter(imagePart.GetStream()))
                    {
                        writer.Write(newImageBytes);
                    }
                }
            }
        }

        foreach (var image in imagesToRemove)
        {
            image.Remove();
        }
    }
}
1
répondu tomRedox 2018-10-02 22:00:09

documentation openXml est très maigre et la plupart d'entre eux affaire prend trop de temps. Je faisais une tâche spécifique et je voulais partager la solution. J'espère que ça aidera les gens et qu'ils vous feront gagner du temps. Je devais obtenir une image d'un endroit particulier dans le texte, en particulier si c'est un objet de course.

 static string RunToHTML(Run r)
       {
            string exit = "";
            OpenXmlElementList list = r.ChildElements;
            foreach (OpenXmlElement element in list)
            {
                if (element is DocumentFormat.OpenXml.Wordprocessing.Picture)
                {
                    exit += AddPictureToHtml((DocumentFormat.OpenXml.Wordprocessing.Picture)element);
                    return exit;
                }
            }

plus précisément, je dois traduire le paragraphe du document en format html.

 static string AddPictureToHtml(DocumentFormat.OpenXml.Wordprocessing.Picture pic)
        {
            string exit = "";
            DocumentFormat.OpenXml.Vml.Shape shape = pic.Descendants<DocumentFormat.OpenXml.Vml.Shape>().First();
            DocumentFormat.OpenXml.Vml.ImageData imageData = shape.Descendants<DocumentFormat.OpenXml.Vml.ImageData>().First();                 
            //style image
            string style = shape.Style;
            style = style.Replace("width:", "");
            style = style.Replace("height:", "");
            style = style.Replace('.', ',');
            style = style.Replace("pt", "");
            string[] arr = style.Split(';');
            float styleW = float.Parse(arr[0]);//width picture
            float styleH = float.Parse(arr[1]);//height picture
            string relationId = imageData.RelationshipId;
            var img = doc.MainDocumentPart.GetPartById(relationId);
            var uri = img.Uri;//path in file
            var fileName = uri.ToString().Split('/').Last();//name picture
            var fileWordMedia = img.GetStream(FileMode.Open);
            exit = String.Format("<img src=\"" + docPath+uri+ "\" width=\""+styleW+"\" heigth=\""+styleH+"\" > ");
            return exit;
        }

uri c'est un chemin pour l'image .fichier docx, par exemple: "test.docx / media / image.bmp" en utilisant cette image d'imformation de sorte que vous pouvez obtenir l'image

static void SavePictures(ImagePart img, string savePath)
        {
                var uri = img.Uri;
               var fileName = uri.ToString().Split('/').Last();
                var fileWordMedia = img.GetStream(FileMode.Open);
                string imgPath = savePath + fileName;
                FileStream fileHtmlMedia = new FileStream(imgPath, FileMode.Create);
                int i = 0;
                while (i != (-1))
                {
                    i = fileWordMedia.ReadByte();
                    if (i != (-1))
                    {
                        fileHtmlMedia.WriteByte((byte)i);
                    }
                }
                fileHtmlMedia.Close();
                fileWordMedia.Close();       
        }
0
répondu barsmaga 2014-07-22 12:20:20