使用OpenXML替换word doc中的图像

fea*_*net 26 .net ms-word openxml

继我在这里的最后一个问题

OpenXML看起来可能完全符合我的要求,但文档很糟糕.一个小时的谷歌搜索没有让我更接近找出我需要做什么.

我有一个word文档.我想以这样的方式将图像添加到该word文档(使用单词),然后我可以在OpenXML中打开文档并替换该图像.应该够简单,是吗?

我假设我应该能够将我的图像'占位符'赋予某种类型的ID,然后用它GetPartById来定位图像并替换它.这是正确的方法吗?这是什么ID?你如何使用Word添加它?

我能找到的每个远程类似的例子都是从ML开始构建整个word文档开始的,这真的没什么用处.

编辑:它发生在我身上,用新图像替换媒体文件夹中的图像会更容易,但再次找不到如何执行此操作的任何指示.

Ada*_*han 33

虽然OpenXML的文档不是很好,但是有一个很好的工具可以用来查看现有Word文档的构建方式.如果安装OpenXml SDK,则它随附Open XML Format SDK\V2.0\tools目录下的DocumentReflector.exe工具.

Word文档中的图像由图像数据和分配给它的ID组成,该ID在文档正文中引用.您的问题似乎可以分解为两个部分:在文档中查找图像的ID,然后为其重新写入图像数据.

要查找图像的ID,您需要解析MainDocumentPart.图像作为绘图元素存储在运行中

<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>
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,您需要找到存储在blip元素中的图像的ID.你如何找到这取决于你的问题,但如果你知道原始图像的文件名,你可以看看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;
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,当您有图像ID时,可以使用它来重写图像数据.我想你会这样做:

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();
Run Code Online (Sandbox Code Playgroud)


fea*_*net 17

我想更新这个帖子,并为了其他人的利益,在上面添加Adam的回答.

实际上,我在前几天(在亚当发布他的答案之前)设法将一些工作代码一起破解了,但这非常困难.文档真的很差,并没有很多信息.

我不知道Adam在他的答案中使用的元素InlineRun元素,但诀窍似乎是在获取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>()
    }
}
Run Code Online (Sandbox Code Playgroud)

一旦你有了它,搜索东西就相当容易了,尽管你必须弄清楚所谓的东西.例如,<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
}
Run Code Online (Sandbox Code Playgroud)

请参阅Adam的XML示例,了解这里的不同元素并查看我正在搜索的内容.

blip在Embed属性中有一个ID ,例如:<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" />

所以你需要做的是添加一个新图像,然后将这个blip指向你新创建的图像的id:

// 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);
Run Code Online (Sandbox Code Playgroud)

我认为这只是孤立的媒体文件夹中的旧图像,这是不理想的,虽然也许它可以说是垃圾收集它的聪明.可能有更好的方法,但我找不到它.

无论如何,你有它.这个帖子现在是关于如何在网络上任何地方交换图像的最完整的文档(我知道这一点,我花了几个小时搜索).所以希望有些人会发现它很有用.


Dan*_*iel 9

在看到这个帖子之前,我尝试了解如何做到这一点我有同样的乐趣.优秀有用的答案人.

如果您知道包中图像的名称,则选择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);
}


Lud*_*sed 5

我喜欢这个部分,因为关于这个主题有很多糟糕的文档,并且经过很多小时的尝试使上述答案发挥作用。我想出了自己的解决方案。

我如何为图像指定 tagName:

在此输入图像描述

首先,我选择要在 Word 中替换的图像,并为其命名(例如“toReplace”),然后循环遍历绘图,选择具有正确 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));
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)