Search code examples
c#.netpowerpointopenxmlopenxml-sdk

Can I add a CustomXmlPart to my presentation without storing the XML for it in a file?


TLDR; Please either confirm that the 2nd code snippit is the accepted method for creating a CustomXmlPart or show me another less tedious method.


So I'm trying to embed some application data in some of the elements in a .pptx that I'm modifying using the OpenXmlSDK.

To explain briefly, I need to embed an chart code into each image that is loaded into the presentation. It's so that the presentation can be re-uploaded and the charts can be generated again then replaced using the newest data.

Initially I was using Extended Attributes on the OpenXmlElement itself:

//OpenXmlDrawing = DocumentFormat.OpenXml.Drawing

// there's only one image per slide for now, so I just grab the blip which contains the image
OpenXmlDrawing.Blip blip = slidePart.Slide.Descendants<OpenXmlDrawing.Blip>().FirstOrDefault(); 

//then apply the attribute
blip.SetAttribute(new OpenXmlAttribute("customAttribute", null, customAttributeValue));

The issue with that being, when the .pptx is edited in PowerPoint 2013, it strips out all the Extended Attributes.

SO. I've read in multiple places now that the solution is to use a CustomXmlPart. So I was trying to find how to do it.. and it was looking like it would require me to have a separate file for each CustomXmlPart to feed into the part. Ex/

var customXmlPart = slidePart.AddCustomXmlPart(CustomXmlPartType.CustomXml);

using (FileStream stream = new FileStream(fileName, FileMode.Open))
{
    customXmlPart.FeedData(stream);
}

^ and that would need to be repeated with a different file for each CustomXmlPart. Which then means I'd likely just have to have a template file containing a skeleton custom XML part, and then dynamically fill in its contents for each individual slide before feeding it into the custom xml part.

It seems like a heck of a lot of work just to put in a little custom attribute. But I haven't been able to find any alternative methods.

Can anyone please either confirm that this is indeed the way I should do it, or point me in another direction? Greatly appreciated.


Solution

  • The answer is yes! :)

    public class CustomXMLPropertyClass
    {
        public string PropertyName { get; set; }
        public string PropertyValue { get; set; }
    }
    
    private static void AddCustomXmlPartCustomPropertyToSlidePart(string propertyName, string propertyValue, SlidePart part)
    {
        var customXmlPart = part.AddCustomXmlPart(CustomXmlPartType.CustomXml);
    
        var customProperty = new CustomXMLPropertyClass{ PropertyName = propertyName, PropertyValue = propertyValue };
    
    
        var serializer = new System.Xml.Serialization.XmlSerializer(customProperty.GetType());
        var stream = new MemoryStream();
    
        serializer.Serialize(stream, customProperty);
    
        var customXml = System.Text.Encoding.UTF8.GetString(stream.ToArray());
    
        using ( var streamWriter = new StreamWriter(customXmlPart.GetStream()))
        {
            streamWriter.Write(customXml);
            streamWriter.Flush();
        }
    }
    

    and then to get it back out:

    private static string GetCustomXmlPropertyFromCustomXmlPart(CustomXmlPart customXmlPart)
    {
        var customXmlProperty = new CustomXMLPropertyClass();
        string xml = "";
    
        using (var stream = customXmlPart.GetStream())
        {
            var streamReader = new StreamReader(stream);
            xml = streamReader.ReadToEnd();
        }
    
        using (TextReader reader = new StringReader(xml))
        {
            var serializer = new System.Xml.Serialization.XmlSerializer(typeof(customXmlProperty));
            customXmlProperty = (CustomXMLPropertyClass)serializer.Deserialize(reader);
        }
    
        var customPropertyValue = customXmlProperty.PropertyValue;
    
        return customPropertyValue;
    }