2010-05-11 5 views
23

Faisant suite à ma dernière question hereRemplacer l'image dans le document Word en utilisant OpenXML

OpenXML ressemble comme il le fait sans doute exactement ce que je veux, mais la documentation est terrible. Une heure de googling ne m'a pas aidé à déterminer ce que je devais faire.

J'ai un document Word. Je veux ajouter une image à ce document Word (en utilisant un mot) 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 en mesure de donner un identifiant à mon espace réservé pour l'image, puis d'utiliser GetPartById pour localiser l'image et la remplacer. Serait-ce la bonne méthode? Quel est cet identifiant? Comment l'ajoutez-vous en utilisant Word?

Chaque exemple, je peux trouver ce qui ne fait rien démarre à distance similaire en construisant le document mot entier à partir de zéro en ML, ce qui est vraiment pas beaucoup d'utilisation. Il m'est apparu qu'il serait plus facile de simplement remplacer l'image dans le dossier multimédia par la nouvelle image, mais encore une fois je ne trouve aucune indication sur la façon de procéder.

Répondre

31

Bien que la documentation OpenXML est pas grande, il est 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 l'outil DocumentReflector.exe sous le format Open XML SDK \ V2.0 répertoire \ tools.

images dans les documents Word se composent des données d'image et un ID qui est attribué à ce 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 réécrire les données d'image pour elle.

Pour trouver l'ID de l'image, vous devez analyser la pièce MainDocumentPart. Les images sont stockées dans les essais 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 de blip. Comment allez-vous trouver qui dépend de votre problème, mais si vous connaissez le nom de l'image d'origine, 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; 
    } 
} 

Ensuite, lorsque vous avez l'ID d'image, vous pouvez l'utiliser pour réécrire les données d'image. Je pense que c'est comment vous le faire:

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(); 
+0

Adam, merci pour votre réponse. J'avais réussi à faire fonctionner quelque chose avant de poster ceci, donc j'ai ajouté quelques informations supplémentaires dans ma propre réponse ci-dessous. – fearofawhackplanet

+0

Le second codeblock est le plus simple que j'ai trouvé jusqu'ici pour remplacer une image plutôt que d'en ajouter une nouvelle. Je voterais 2x si je le pouvais! –

17

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

En fait, je réussi à pirater un code de travail ainsi que l'autre jour, (avant Adam a posté sa réponse), mais il était assez difficile. La documentation est vraiment pauvre et il n'y a pas beaucoup d'informations là-bas.

Je ne connaissais pas les Inline et Run éléments Adam utilise dans sa réponse, mais l'affaire semble être en arriver à la propriété Descendants<> et vous pouvez analyser à peu près tous les éléments comme un mapping 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>() 
    } 
} 

Une fois que vous avez ceci, il est assez facile de chercher des choses, bien que vous deviez déterminer ce que tout est appelé. Par exemple, le <pic:nvPicPr> est Picture.NonVisualPictureProperties, etc.

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

Adam montre un moyen d'utiliser l'élément Inline. J'ai juste plongé directement et j'ai cherché tous les éléments d'image. Je ne suis pas sûr de ce qui est le meilleur ou le plus robuste (je ne sais pas à quel point la structure xml est cohérente entre les documents et si cela cause un problème de code).

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 recherche. Le blip possède un ID dans la propriété Embed, par exemple: <a:blip r:embed="rId4" cstate="print" />, ce qui permet de mapper le Blip à une image dans le dossier Media (vous pouvez voir tous ces dossiers et fichiers si vous renommez .docx en. zip et décompressez-le). Vous trouverez la carte dans _rels\document.xml.rels:

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

Alors ce que vous devez faire est d'ajouter une nouvelle image, puis pointez cette blip à l'id de l'image nouvellement créé:

// 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 ce n'est que la vieille image dans le dossier Media qui n'est pas idéale, bien que peut-être il est assez intelligent pour ramasser les ordures pour ainsi dire. Il y a peut-être une meilleure façon de le faire, mais je n'ai pas pu le trouver.

Quoi qu'il en soit, vous l'avez. 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 sais cela, j'ai passé des heures à chercher). Donc, j'espère que certaines personnes le trouveront utile.

+0

On dirait plutôt bien, content que tu l'aies fonctionné. La raison pour laquelle j'ai utilisé Inline était que je regardais le pour le nom de fichier de l'image qui est un élément enfant de Inline. Votre solution a plus de sens, car le nom de fichier est également dans l'élément . –

+0

Un peu en retard à la fête, mais je voulais juste remercier Adam et vous pour cela. J'ai sauvé beaucoup d'heures en essayant de trouver comment remplacer une image d'espace réservé et en parcourant le document Open XML –

6

J'ai eu le même plaisir à essayer de comprendre comment faire cela jusqu'à ce que je vois ce fil. Excellentes réponses utiles les gars.

Une façon simple de sélectionner le ImagePart si vous connaissez le nom de l'image dans le package 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 pouvez alors faire

 

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

2

Le code suivant extrait les images du document spécifié (filename) et les enregistre dans un dossier D: \ TestArea à l'aide des noms de fichiers internes. Les réponses sur cette page m'ont aidé à trouver ma solution. Remarque: cette solution n'aide pas quelqu'un à remplacer une image dans un document Word, mais dans toutes mes recherches sur la façon de récupérer une image à partir d'un document Word, c'était le seul lien/le plus proche que je pouvais trouver; juste au cas où quelqu'un d'autre est dans le même bateau, je 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); 
      } 
     } 
    } 
} 
1

afin d'obtenir des images et de les copier dans un dossier, vous pouvez utiliser plus méthode 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(); 

     } 
0

documentation OpenXML est très maigre et la plupart d'entre eux traitent prend trop de temps. Je faisais une tâche spécifique et je voulais partager la solution. J'espère que cela va aider les gens et qu'ils économisent votre temps. Je devais obtenir une image d'un endroit particulier dans le texte, en particulier s'il s'agit d'un objet de Run.

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, j'ai besoin de traduire le paragraphe du document au 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 est un chemin à l'image dans le fichier .docx, par exemple: "test.docx/media/image.bmp" en utilisant cette image de 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();  
     } 
1

J'aime cette section, car 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 tagName:

enter image description here

D'abord, je sélectionner l'image que je veux remplacer la parole et lui donner un nom (par exemple « toReplace ») après je boucle à travers les dessins sélectionnez l'image avec le tagName correct et écrivez 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)); 
       } 
      } 
     } 
    } 
} 
+1

En effet, celui-ci fonctionne. Juste le marquage du document est différent dans mon cas. Je devais utiliser 'dpr.Title' – pxp

Questions connexes