none
Insert a Picture into the Picture/Image Content Control using Open XML SDK 2.0 RRS feed

  • Question

  • I have created a word template as follows

    Here PizzaPicture is a Picture Content Control, PizzaName is a Plain Text Content Control and PizzaDescription is a Rich Text Content Control

     

    I am able to assign text to PizzaName and PizzaDescription Easily but not able to even locate the PizzaPicture. Here is my code

     

                using (WordprocessingDocument document = WordprocessingDocument.Open(@".\test.docx", true)) {
                    
                    MainDocumentPart mainPart = document.MainDocumentPart;
                    DocumentFormat.OpenXml.Wordprocessing.Text text = null;
                    SdtContentBlock pizzaNameBlock = null;  
                    SdtContentBlock pizzaDescriptionBlock = null;  
                    //SdtContentBlock pizzaPictureBlock = null;  
    
                    List<SdtBlock> sdtList = mainPart.Document.Descendants<SdtBlock>().ToList();
                    foreach (SdtBlock sdt in sdtList)
                    {
                        Console.WriteLine(sdt.SdtProperties.GetFirstChild<Tag>().Val.Value);
                    }
    
                    SdtBlock s1 = mainPart.Document.Body.Descendants<SdtBlock>().Where(r => r.SdtProperties.GetFirstChild<Tag>().Val == "PizzaName").Single();
                    SdtBlock s2 = mainPart.Document.Body.Descendants<SdtBlock>().Where(r => r.SdtProperties.GetFirstChild<Tag>().Val == "PizzaDescription").Single();
                    SdtBlock s3 = mainPart.Document.Body.Descendants<SdtBlock>().FirstOrDefault(r =>
                    {
                        SdtProperties p = r.Elements<SdtProperties>().FirstOrDefault();
                        if (p != null)
                        {
                            Console.WriteLine("P is not null");
                            // Is it a picture content control?
                            SdtContentPicture pict =
                                p.Elements<SdtContentPicture>().FirstOrDefault();
                            if (pict != null) Console.WriteLine("Pict is not null");
                            // Get the alias.
                            SdtAlias a = p.Elements<SdtAlias>().FirstOrDefault();
                            if (pict != null && a.Val == "PizzaPicture")
                                return true;
                        }
                        return false;
                    });
    
                    if (s3 == null) {
                        Console.Write(" s3 is Null");
                    } else { 
                        Console.WriteLine("s3 not null");
                    }
    
                    if (s1 != null) {
                        pizzaNameBlock = s1.Descendants<SdtContentBlock>().FirstOrDefault();
                        text = pizzaNameBlock.Descendants<DocumentFormat.OpenXml.Wordprocessing.Text>().FirstOrDefault();
                        // here you can set the current time
                        text.Text = "Peperoni";
                        Console.WriteLine(text.Text);
                    }
    
                    string embed = null;
                    if (s3 != null)
                    {
                        Drawing dr = s3.Descendants<Drawing>().FirstOrDefault();
                        if (dr != null)
                        {
                            Blip blip = dr.Descendants<Blip>().FirstOrDefault();
                            if (blip != null)
                                embed = blip.Embed;
                        }
                    }
    
                    if (embed != null)
                    {
                        IdPartPair idpp = document.MainDocumentPart.Parts
                            .Where(pa => pa.RelationshipId == embed).FirstOrDefault();
                        if (idpp != null)
                        {
                            ImagePart ip = (ImagePart)idpp.OpenXmlPart;
                            using (FileStream fileStream =
                                File.Open(@"c:\temp\pepperoni.jpg", FileMode.Open))
                                ip.FeedData(fileStream);                        
                        }
                    }
    
                    if (s2 != null) {
                        pizzaDescriptionBlock = s2.Descendants<SdtContentBlock>().FirstOrDefault();
    
                        string altChunkId = "AltChunkId12345";
                        AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.Xhtml, altChunkId);
                        chunk.FeedData(File.Open(@".\html.txt", FileMode.Open));
                        AltChunk altChunk = new AltChunk();
                        altChunk.Id = altChunkId;
                        ////Replace content control with altChunk information                
                        OpenXmlElement parent = pizzaDescriptionBlock.Parent;
                        parent.InsertAfter(altChunk, pizzaDescriptionBlock);
                        pizzaDescriptionBlock.Remove();
                    }
    
                    mainPart.Document.Save();
                    document.Close();
                }
    
    


    Here the call mainPart.Document.Descendants<SdtBlock>().ToList(); returns me only 2 items PizzaName, PizzaDescription but no PizzaPicture.

     

    Secondly the line   SdtContentPicture pict =  p.Elements<SdtContentPicture>().FirstOrDefault(); returns NULL. So it seems that there is no element of type SdtContentPicture.

    What am I doing wrong? I am pretty sure that I have put PizzaPicture as a Picture content control.

     

    My objective is to locate this control in the document and then insert an image into it programmatically.

     

     


    MSDNStudent Knows not much!
    Saturday, February 4, 2012 5:58 PM

Answers

  • Hi,

    I'm running some test on your code, and I'll come back as soon as I get some update. But before that, I would recommend you to bind customXMLPart with the content controls via Content Control Tool Kit, which enables us to bind customXMLPart with CC by dragging and dropping.

    After binding, you can change contents of the template via OpenXML SDK easily as the following code:

     

         private void button1_Click(object sender, EventArgs e)
            {
                string fileName = @"C:\CCTest.docx";
                WordprocessingDocument wordDoc = WordprocessingDocument.Open(fileName, true);
                MainDocumentPart mainPart = wordDoc.MainDocumentPart;
                CustomXmlPart customPart = mainPart.CustomXmlParts.FirstOrDefault();                        
                
                //convert image into string
                string picName = @"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg";
                System.IO.FileStream fileStream = System.IO.File.Open(picName, System.IO.FileMode.Open);
                System.IO.BinaryReader br = new System.IO.BinaryReader(fileStream);
                byte[] byteArea;
                byteArea = br.ReadBytes(System.Convert.ToInt32(fileStream.Length));
                string picString = System.Convert.ToBase64String(byteArea);
                
                //Load the XML template
                string DataString = Properties.Resources.XMLData;           
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(DataString);
    
                //change the value
                XmlNodeList xmlNode = xmlDoc.GetElementsByTagName("pizzaPic");
                xmlNode[0].InnerText = picString;
    
                xmlNode = xmlDoc.GetElementsByTagName("pizzaName");
                xmlNode[0].InnerText = "Pizza Name";
    
                xmlNode = xmlDoc.GetElementsByTagName("pizzaDescription");
                xmlNode[0].InnerText = "Pizza Description";
    
                //write the custom xml data into the customxmlpart
                System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(customPart.GetStream(System.IO.FileMode.Create), System.Text.Encoding.UTF8);
                writer.WriteRaw(xmlDoc.InnerXml);
                writer.Flush();
                writer.Close();
    
                fileStream.Close();
                br.Close();
                
                mainPart.Document.Save();
                wordDoc.Close();
            }
    

     

    and the XML template of the project would something like:

     

    <?xml version="1.0"?>
    <Pizza xmlns="http://Test/ContentControls/Pizzas">
      <pizzaPic></pizzaPic>
      <pizzaName></pizzaName>
      <pizzaDescription></pizzaDescription>
    </Pizza>
    

    I hope this helps.

     


    Calvin Gao[MSFT]
    MSDN Community Support | Feedback to us
    Monday, February 6, 2012 10:36 AM
    Moderator
  • Hi Abhishek,

    The fact is when you are working with Content Control, you are working with CustomXMLPart as well. There are three way to bind customxmlpart with content controls:

    1. Using some methods of Object library like SetMapping, see this workthrough:

    http://msdn.microsoft.com/en-us/library/bb398244.aspx

    2. Using Open XML SDK

    3. Using Tool Kit

    I would recommend you to use the tool kit to design the template since this is the easiest way to do unless you are trying to generate the template on the server side, which means you would have to use Open XML APIs.

    After the binding, you can change the CustomXMLPart to change the content of the controls (just like I showed you above) and how you implement binding doesn't matter how your users would use the template, so they don't need to install the tool kit at their client.

    See the last sample of the document, which is kind of similar with yours:

    http://msdn.microsoft.com/en-us/library/dd469465(v=office.12).aspx

    I hope this helps.


    Calvin Gao[MSFT]
    MSDN Community Support | Feedback to us
    Monday, February 6, 2012 3:52 PM
    Moderator

All replies

  • I solved the problem. for Picture content control I should use SdtElement rather than SdtBlock. This works

    List<SdtElement> sdtList = mainPart.Document.Descendants<SdtElement>().ToList();
    foreach (SdtBlock sdt in sdtList)
    {
          Console.WriteLine(sdt.SdtProperties.GetFirstChild<Tag>().Val.Value);
    }

    This code also works and returns a non null SdtElement for Picture Content Control

    SdtElement s3 = mainPart.Document.Body.Descendants<SdtElement>().Where(r => r.SdtProperties.GetFirstChild<Tag>().Val == "PizzaPicture").Single();

    The code to insert the image also works perfectly but the problem is that there is an annoying icon on top of the inserted image



    Can anyone tell me how can I get rid of the image icon from the center of the image??


    MSDNStudent Knows not much!
    Saturday, February 4, 2012 6:36 PM
  • Hi,

    I'm running some test on your code, and I'll come back as soon as I get some update. But before that, I would recommend you to bind customXMLPart with the content controls via Content Control Tool Kit, which enables us to bind customXMLPart with CC by dragging and dropping.

    After binding, you can change contents of the template via OpenXML SDK easily as the following code:

     

         private void button1_Click(object sender, EventArgs e)
            {
                string fileName = @"C:\CCTest.docx";
                WordprocessingDocument wordDoc = WordprocessingDocument.Open(fileName, true);
                MainDocumentPart mainPart = wordDoc.MainDocumentPart;
                CustomXmlPart customPart = mainPart.CustomXmlParts.FirstOrDefault();                        
                
                //convert image into string
                string picName = @"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg";
                System.IO.FileStream fileStream = System.IO.File.Open(picName, System.IO.FileMode.Open);
                System.IO.BinaryReader br = new System.IO.BinaryReader(fileStream);
                byte[] byteArea;
                byteArea = br.ReadBytes(System.Convert.ToInt32(fileStream.Length));
                string picString = System.Convert.ToBase64String(byteArea);
                
                //Load the XML template
                string DataString = Properties.Resources.XMLData;           
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(DataString);
    
                //change the value
                XmlNodeList xmlNode = xmlDoc.GetElementsByTagName("pizzaPic");
                xmlNode[0].InnerText = picString;
    
                xmlNode = xmlDoc.GetElementsByTagName("pizzaName");
                xmlNode[0].InnerText = "Pizza Name";
    
                xmlNode = xmlDoc.GetElementsByTagName("pizzaDescription");
                xmlNode[0].InnerText = "Pizza Description";
    
                //write the custom xml data into the customxmlpart
                System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(customPart.GetStream(System.IO.FileMode.Create), System.Text.Encoding.UTF8);
                writer.WriteRaw(xmlDoc.InnerXml);
                writer.Flush();
                writer.Close();
    
                fileStream.Close();
                br.Close();
                
                mainPart.Document.Save();
                wordDoc.Close();
            }
    

     

    and the XML template of the project would something like:

     

    <?xml version="1.0"?>
    <Pizza xmlns="http://Test/ContentControls/Pizzas">
      <pizzaPic></pizzaPic>
      <pizzaName></pizzaName>
      <pizzaDescription></pizzaDescription>
    </Pizza>
    

    I hope this helps.

     


    Calvin Gao[MSFT]
    MSDN Community Support | Feedback to us
    Monday, February 6, 2012 10:36 AM
    Moderator
  • Hello Calvin,

     

    What is the advantage of using the Custom Xml Part? Can't I get a template solution by using pure Microsoft code? 

     

    As far as I can see that rather than looking up content controls by tagname, I will only have to pass an XML and the binding of XML to content control will be done at the word level itself.

    The question is will this toolkit be required to be installed on all machines where this word document is being opened? in my case the document might be opened from 1000s of machines.

     

    Another problem is that since I am converting binary data into XML, I will be to convert each image etc into Base64 which is 1.5 times in size as compared to the original binary data.

     

    Are you saying that I cannot do the mapping of Image, RichText(HTML) and Plain text to a word document without using this custom utility?

    Regards,

    Abhishek.


    MSDNStudent Knows not much!
    Monday, February 6, 2012 12:48 PM
  • Hi Abhishek,

    The fact is when you are working with Content Control, you are working with CustomXMLPart as well. There are three way to bind customxmlpart with content controls:

    1. Using some methods of Object library like SetMapping, see this workthrough:

    http://msdn.microsoft.com/en-us/library/bb398244.aspx

    2. Using Open XML SDK

    3. Using Tool Kit

    I would recommend you to use the tool kit to design the template since this is the easiest way to do unless you are trying to generate the template on the server side, which means you would have to use Open XML APIs.

    After the binding, you can change the CustomXMLPart to change the content of the controls (just like I showed you above) and how you implement binding doesn't matter how your users would use the template, so they don't need to install the tool kit at their client.

    See the last sample of the document, which is kind of similar with yours:

    http://msdn.microsoft.com/en-us/library/dd469465(v=office.12).aspx

    I hope this helps.


    Calvin Gao[MSFT]
    MSDN Community Support | Feedback to us
    Monday, February 6, 2012 3:52 PM
    Moderator
  • Hello,

    "The code to insert the image also works perfectly but the problem is that there is an annoying icon on top of the inserted image

    "

    Did you find a solution for this ?

    Tuesday, November 18, 2014 3:57 PM
  • Hi,

    did you find a way to remove the icon on image ?

    Vincent

    Saturday, March 28, 2015 11:50 PM
  • This is too late, might be useful for some one. The icon is not shown if I set properties of the content control as below


    Narendra

    Wednesday, November 18, 2015 10:59 AM
  • In my case, I have switched to the community Edition of Spire Office :

    http://www.e-iceblue.com/Introduce/free-doc-component.html

    The support is very reactive and often propose C# code to solve problem.

    Wednesday, November 18, 2015 5:03 PM
  • what is this "Properties.Resources.XMLData";

    in my code it's unknown.

    Tuesday, August 25, 2020 7:36 AM