Search code examples
c#spreadsheetembedded-resourceopenxml-sdk

Changing ImageData using Open XML SDK


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

Solution

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