Подтвердить что ты не робот

Заменить изображение в word doc с помощью OpenXML

Следуя моему последнему вопросу здесь

OpenXML выглядит так, как будто он делает именно то, что я хочу, но документация ужасная. Час поискового робота не приблизил меня к тому, чтобы выяснить, что мне нужно делать.

У меня есть документ с текстом. Я хочу добавить изображение к этому текстовому документу (используя слово) таким образом, чтобы затем открыть документ в OpenXML и заменить это изображение. Должно быть достаточно просто, да?

Я предполагаю, что должен уметь присвоить образ "placeholder" моего изображения, а затем использовать GetPartById, чтобы найти изображение и заменить его. Будет ли это правильным методом? Что это за Идентификатор? Как добавить его с помощью Word?

Каждый пример, который я могу найти, который делает что-то отдаленно похожее, начинается с построения всего документа Word с нуля в ML, что действительно не так много.

EDIT: мне показалось, что было бы проще просто заменить изображение в медиа-папке новым изображением, но опять же не может найти никаких указаний о том, как это сделать.

4b9b3361

Ответ 1

Хотя документация для OpenXML невелика, есть отличный инструмент, который вы можете использовать, чтобы увидеть, как строятся существующие документы Word. Если вы устанавливаете OpenXML SDK, он поставляется с инструментом DocumentReflector.exe в каталоге Open XML Format SDK\V2.0\tools.

Изображения в документах Word состоят из данных изображения и идентификатора, присвоенного ему, на который ссылаются в тексте документа. Похоже, что ваша проблема может быть разбита на две части: найти идентификатор изображения в документе, а затем переписать данные изображения для него.

Чтобы найти идентификатор изображения, вам нужно проанализировать MainDocumentPart. Изображения сохраняются в Runes как элемент Drawing

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

В приведенном выше примере вам нужно найти идентификатор изображения, хранящегося в элементе blip. Как вы находите, что это зависит от вашей проблемы, но если вы знаете имя файла исходного изображения, вы можете посмотреть на элемент 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;
  }
}

Затем, когда у вас есть идентификатор изображения, вы можете использовать его для перезаписи данных изображения. Я думаю, именно так вы это сделаете:

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

Ответ 2

Я хочу обновить этот поток и добавить в ответ Адама выше для других.

На самом деле мне удалось на некоторое время взломать какой-то рабочий код (до того, как Адам опубликовал свой ответ), но это было довольно сложно. Документация действительно бедна, и информации там нет.

Я не знал о элементах Inline и Run, которые Адам использует в своем ответе, но трюк, похоже, находится в свойстве Descendants<>, и вы можете в значительной степени проанализировать любой элемент, например, нормальное отображение XML.

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

Как только у вас есть это, довольно легко искать вещи, хотя вам нужно решить, что все называется. Например, <pic:nvPicPr> - Picture.NonVisualPictureProperties и т.д.

Как правильно говорит Адам, элемент, который нужно найти для замены изображения, - это элемент Blip. Но вам нужно найти правильный снимок, который соответствует изображению, которое вы пытаетесь заменить.

Адам показывает способ, используя элемент Inline. Я просто нырнул прямо и искал все элементы изображения. Я не уверен, что это лучший или более надежный способ (я не знаю, насколько согласована структура xml между документами и если это вызывает нарушение кода).

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
}

См. пример XML XML, чтобы понять различные элементы здесь и посмотреть, что я ищу.

В объекте Embed у blip есть идентификатор, например: <a:blip r:embed="rId4" cstate="print" />, то, что это делает, сопоставляет Blip с изображением в папке Media (вы можете увидеть все эти папки и файлы, если вы переименуете вас .docx на .zip и разархивировать его). Вы можете найти отображение в _rels\document.xml.rels:

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

Итак, что вам нужно сделать, это добавить новое изображение, а затем указать этот клик на идентификаторе вашего вновь созданного образа:

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

Я предполагаю, что это просто сироты старого образа в папке "Медиа", которая не идеальна, хотя, может быть, она достаточно умна, чтобы мусор собирал ее так сказать. Там может быть лучший способ сделать это, но я не смог его найти.

В любом случае, у вас это есть. Этот поток теперь является самой полной документацией о том, как поменять образ в любом месте в Интернете (я знаю это, я потратил часы на поиск). Поэтому, надеюсь, некоторые люди посчитают это полезным.

Ответ 3

У меня было такое же удовольствие, пытаясь разобраться, как это сделать, пока я не увижу этот поток. Отличные полезные ответы ребята.

Простой способ выбора ImagePart, если вы знаете имя изображения в пакете, - это проверить Uri


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

Затем вы можете сделать


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

Ответ 4

Следующий код будет извлекать изображения из указанного документа (имя файла) и сохранять их в папке D:\TestArea, используя внутренние имена файлов. Ответы на этой странице помогли мне придумать мое решение.

Примечание. Это решение не помогает кому-то заменить изображение в слове doc, однако во всех моих поисках в том, как получить изображение из слова doc, это была единственная/ближайшая ссылка, которую я мог найти; на всякий случай, если кто-то еще находится в одной лодке, я размещаю свое решение здесь.

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

Ответ 5

чтобы получить изображения и скопировать их в папку, вы можете использовать более простой метод

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

        }

Ответ 6

Мне нравится этот раздел, потому что на эту тему так много плохой документации, и после многих часов попытки сделать вышеупомянутые ответы работают. Я придумал свое решение.

Как я получаю изображение tagName:

введите описание изображения здесь

Сначала я выбираю изображение, которое хочу заменить словом, и даю ему имя (например, "toReplace" ), после чего я просматриваю чертежи, выбираю Image с правильным именем tagName и записываю собственное изображение на свое место.

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

Ответ 7

Документация openXml очень тощая, и большинство из них занимают слишком много времени. Я выполнял определенную задачу и хочу поделиться этим решением. Надеюсь, это поможет людям, и они сэкономят ваше время. Мне нужно было получить изображение определенного места в тексте, особенно если это объект 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;
                }
            }

В частности, мне нужно перевести абзац документа в формате 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 это путь к картинке в .docx файле, например: "test.docx/media/image.bmp" используя эту картинку, чтобы вы могли получить изображение

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