Заменить изображение в word doc с помощью OpenXML
Следуя моему последнему вопросу здесь
OpenXML выглядит так, как будто он делает именно то, что я хочу, но документация ужасная. Час поискового робота не приблизил меня к тому, чтобы выяснить, что мне нужно делать.
У меня есть документ с текстом. Я хочу добавить изображение к этому текстовому документу (используя слово) таким образом, чтобы затем открыть документ в OpenXML и заменить это изображение. Должно быть достаточно просто, да?
Я предполагаю, что должен уметь присвоить образ "placeholder" моего изображения, а затем использовать GetPartById
, чтобы найти изображение и заменить его. Будет ли это правильным методом? Что это за Идентификатор? Как добавить его с помощью Word?
Каждый пример, который я могу найти, который делает что-то отдаленно похожее, начинается с построения всего документа Word с нуля в ML, что действительно не так много.
EDIT: мне показалось, что было бы проще просто заменить изображение в медиа-папке новым изображением, но опять же не может найти никаких указаний о том, как это сделать.
Ответы
Ответ 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();
}