none
Locating a Content Control inside a table (or other element) RRS feed

  • Question

  • Hi Folks,

    I have a simple word document that contains content controls (just plain text ones).  The issue that I am having is that some of the content controls are embedded in other elements (such as tables) and I am unable to find them in the document.

    if I create a simple document that contains a PlainText content control called bob and put it in the root of the document and perform  the following:-

    List<SdtBlock> blockList = mainDocumentPart.Document.Decendants<SdtBlock>().ToList()

    I get one element returned which I can examine blockList[0].SdtProperties.GetFirstChild<Tag>() and I get a valid tag element back.

     

    However, if I then add a table, then put another control inside called 'fred',  block list will contain 2 elements, but the second element will not contain a tag property. 

    The fred block is nested several levels deep and I am looking for a way to find it (regardless of what it is nested in) so that I can update its contents?

    Does anyone have a working way to do this

    Monday, October 10, 2011 2:45 AM

Answers

  • Hi AndyW2007,

    Thank you for posting.

    After reading your problem, I figured out the problem, if we insert the plain text content controls into the table, they will become SdtCell elements. So, you can get all the SdtCell elements and then find the content control according to the SdtProperties tag name. Please refer to the following code snippet which I tested:

    using System;
    using System.Linq;
    using DocumentFormat.OpenXml.Packaging;
    using DocumentFormat.OpenXml.Wordprocessing;
     
    namespace OpenXmlWordGetContentCtrlInTable
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (WordprocessingDocument document = WordprocessingDocument.Open(
    @"contentctlsintable.docx"true))
                {
                    MainDocumentPart mainPart = document.MainDocumentPart;
                    Text text = null;
                    SdtContentCell sdtContentBlock = null;
                    SdtCell sdtcell = mainPart.Document.Body.Descendants<SdtCell>()
                        .Where(r => r.SdtProperties.GetFirstChild<Tag>().Val == "c2").Single();
                    if (sdtcell != null)
                    {
                        sdtContentBlock = sdtcell.Descendants<SdtContentCell>().FirstOrDefault();
                        text = sdtContentBlock.Descendants<Text>().FirstOrDefault();
                        Console.WriteLine(text.Text);
                    }
                }
            }
        }
    }
    

    which can get the text of the content control in the table.

    Hope this can help you and just feel free to follow up after you have tried.

    Best Regards,

     


    Bruce Song [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • Marked as answer by Bruce Song Monday, October 17, 2011 2:12 AM
    Tuesday, October 11, 2011 8:48 AM
  • update:

    The solution I am using for now is to  search for the item in a cell using the above code from bruce.  If it is not found, then I get a list of all of the SdtBlock elements.  For each block I check to see if it has a Tag (not all of them do) and if it does, then I check to see if the tag value is that of the control I am looking for.  

    The other thing to note is that a Cell uses a SdtCellContent object and a block uses a SdtBlockContent object, other than that the code is roughly the same for each.

    • Marked as answer by AndyW2007 Thursday, October 13, 2011 2:23 AM
    Thursday, October 13, 2011 2:23 AM

All replies

  • Hi AndyW2007,

    Thank you for posting.

    After reading your problem, I figured out the problem, if we insert the plain text content controls into the table, they will become SdtCell elements. So, you can get all the SdtCell elements and then find the content control according to the SdtProperties tag name. Please refer to the following code snippet which I tested:

    using System;
    using System.Linq;
    using DocumentFormat.OpenXml.Packaging;
    using DocumentFormat.OpenXml.Wordprocessing;
     
    namespace OpenXmlWordGetContentCtrlInTable
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (WordprocessingDocument document = WordprocessingDocument.Open(
    @"contentctlsintable.docx"true))
                {
                    MainDocumentPart mainPart = document.MainDocumentPart;
                    Text text = null;
                    SdtContentCell sdtContentBlock = null;
                    SdtCell sdtcell = mainPart.Document.Body.Descendants<SdtCell>()
                        .Where(r => r.SdtProperties.GetFirstChild<Tag>().Val == "c2").Single();
                    if (sdtcell != null)
                    {
                        sdtContentBlock = sdtcell.Descendants<SdtContentCell>().FirstOrDefault();
                        text = sdtContentBlock.Descendants<Text>().FirstOrDefault();
                        Console.WriteLine(text.Text);
                    }
                }
            }
        }
    }
    

    which can get the text of the content control in the table.

    Hope this can help you and just feel free to follow up after you have tried.

    Best Regards,

     


    Bruce Song [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • Marked as answer by Bruce Song Monday, October 17, 2011 2:12 AM
    Tuesday, October 11, 2011 8:48 AM
  • Hi Bruce,

    I was able to use your code to locate items in tables (thanks muchly) but it does not solve the problem of finding items which may be embedded in other elements (or not embedded at all).   Normally with collections one would search on a generic type with a common property and then cast it to the appropriate type when found, so I was thinking would it be better to iterate through the SdtElements instead?

    I modified your code, changing the .Single() to .SingleOrDefault() which allows me to test sdtCell for null.  if it is null, then I assume the plain text control is not embedded in a table and must be either root level or embedded in something else. This is the bit I am now trying to work out.

     

    Also, as an aside, looking at the invariant notation of your code sample, there are two invariants:  1) mainPart.Document.Body.Decendants<SdtCell>  can be null   and 2) r.SdtProperties.GetFirstChild<Tag>()  can be null.     Both of these invariants are violated by injecting the rule "must not be null' via the use of  .Single() and  .Val().    In the case of either of the two collections being null, an exception will be thrown which will mean your conditional test (if sdtcell) will never be executed.

     

    Wednesday, October 12, 2011 8:55 PM
  • update:

    The solution I am using for now is to  search for the item in a cell using the above code from bruce.  If it is not found, then I get a list of all of the SdtBlock elements.  For each block I check to see if it has a Tag (not all of them do) and if it does, then I check to see if the tag value is that of the control I am looking for.  

    The other thing to note is that a Cell uses a SdtCellContent object and a block uses a SdtBlockContent object, other than that the code is roughly the same for each.

    • Marked as answer by AndyW2007 Thursday, October 13, 2011 2:23 AM
    Thursday, October 13, 2011 2:23 AM
  • Andy wrote: "so I was thinking would it be better to iterate through the SdtElements instead?".

    For me that seems to work.

    ...Descendants<SdtElement>().ToList()

    should find any type of Content Control since they all inherit from it.

    With the <w:sdtContent> it is worse as the various types do not have the same base class.

    I simply look for .LocalName == "sdtContent".

    Stein-Tore

    Sunday, December 8, 2013 3:18 PM