I use Open XML SDK to manipulate OOXML spreadsheets.
I need to change the data in a VmlDrawingsPart, specifically the ImageData. ImageData have an o:relid property which is the reference to the image/file being controlled by the ImageData. I want to replace this reference, so the ImageData points to another source image/file.
I have a worksheetPart, old imagePart and the id to the new imagePart. From this, I need to find the ImageData, which is saved in xl/drawings/vmldrawing1.vml
and then use Descendants
to find ImageData and change it, but this is where I have trouble. I cannot find the right entry for this. How can I reach ImageData to change the reference?
string id = Get_RelationshipId(imagePart);
ImageData imageData = worksheetPart.VmlDrawingParts.First().RootElement.Descendants<ImageData>()
.Where(p => p.RelId == id)
.Select(p => p)
.Single();
imageData.RelId = Get_RelationshipId(new_ImagePart);
UPDATE
The above code give me this exception:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
DocumentFormat.OpenXml.Packaging.OpenXmlPart.RootElement.get returned null.
Here's the actual content of the vmlDrawing1.vml file, where you can find o:relid="rId2"
and I want to change this relationship value to point to another relationship:
<xml xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel">
<o:shapelayout v:ext="edit">
<o:idmap v:ext="edit" data="1"/>
</o:shapelayout><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75"
o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
<v:stroke joinstyle="miter"/>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0"/>
<v:f eqn="sum @0 1 0"/>
<v:f eqn="sum 0 0 @1"/>
<v:f eqn="prod @2 1 2"/>
<v:f eqn="prod @3 21600 pixelWidth"/>
<v:f eqn="prod @3 21600 pixelHeight"/>
<v:f eqn="sum @0 0 1"/>
<v:f eqn="prod @6 1 2"/>
<v:f eqn="prod @7 21600 pixelWidth"/>
<v:f eqn="sum @8 21600 0"/>
<v:f eqn="prod @7 21600 pixelHeight"/>
<v:f eqn="sum @10 21600 0"/>
</v:formulas>
<v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
<o:lock v:ext="edit" aspectratio="t"/>
</v:shapetype><v:shape id="_x0000_s1025" type="#_x0000_t75" style='position:absolute;
margin-left:2in;margin-top:165pt;width:144.75pt;height:15.75pt;z-index:1'
filled="t" fillcolor="window [65]" stroked="t" strokecolor="windowText [64]"
o:insetmode="auto">
<v:fill color2="window [65]"/>
<v:imagedata o:relid="rId1" o:title=""/>
<x:ClientData ObjectType="Pict">
<x:SizeWithCells/>
<x:Anchor>
3, 0, 11, 0, 6, 1, 12, 1</x:Anchor>
<x:CF>Pict</x:CF>
<x:AutoPict/>
</x:ClientData>
</v:shape><v:shape id="_x0000_s1026" type="#_x0000_t75" style='position:absolute;
margin-left:670.5pt;margin-top:97.5pt;width:207.75pt;height:42.75pt;
z-index:2' filled="t" fillcolor="window [65]" stroked="t" strokecolor="windowText [64]"
o:insetmode="auto">
<v:fill color2="window [65]"/>
<v:imagedata o:relid="rId2" o:title=""/>
<x:ClientData ObjectType="Pict">
<x:SizeWithCells/>
<x:Anchor>
13, 62, 6, 10, 18, 19, 9, 7</x:Anchor>
<x:CF>Pict</x:CF>
<x:AutoPict/>
</x:ClientData>
</v:shape><v:shape id="_x0000_s1027" type="#_x0000_t75" style='position:absolute;
margin-left:145.5pt;margin-top:244.5pt;width:144.75pt;height:15pt;z-index:3'
filled="t" fillcolor="window [65]" stroked="t" strokecolor="windowText [64]"
o:insetmode="auto">
<v:fill color2="window [65]"/>
<v:imagedata o:relid="rId3" o:title=""/>
<x:ClientData ObjectType="Pict">
<x:SizeWithCells/>
<x:Anchor>
3, 2, 16, 6, 6, 3, 17, 6</x:Anchor>
<x:CF>Pict</x:CF>
<x:AutoPict/>
<x:DDE/>
<x:Camera/>
</x:ClientData>
</v:shape></xml>
I know I can get this code to wrok somehow, because I got the same concept working for changing Blip image controls on ordinarily embedded images by using this code:
// Change relationships of image
string id = Get_RelationshipId(imagePart);
Blip blip = worksheetPart.DrawingsPart.WorksheetDrawing.Descendants<DocumentFormat.OpenXml.Drawing.Spreadsheet.Picture>()
.Where(p => p.BlipFill.Blip.Embed == id)
.Select(p => p.BlipFill.Blip)
.Single();
blip.Embed = Get_RelationshipId(new_ImagePart);
I am attempting a different approach which runs through the XML searching for the corresponding attribute. I can find the attribute now and change it. However, I don't think this is an ideal approach, so I welcome better answers.
Here's the code I got working:
// Change relationships of image
string id = Get_RelationshipId(imagePart);
XDocument xElement = worksheetPart.VmlDrawingParts.First().GetXDocument();
IEnumerable<XElement> descendants = xElement.FirstNode.Document.Descendants();
foreach (XElement descendant in descendants)
{
if (descendant.Name == "{urn:schemas-microsoft-com:vml}imagedata")
{
IEnumerable<XAttribute> attributes = descendant.Attributes();
foreach (XAttribute attribute in attributes)
{
if (attribute.Name == "{urn:schemas-microsoft-com:office:office}relid")
{
if (attribute.Value == id)
{
attribute.Value = Get_RelationshipId(new_ImagePart);
worksheetPart.VmlDrawingParts.First().SaveXDocument();
}
}
}
}
}