Search code examples
c#openxmlwordml

Document.Body.Descendants<SdtBlock>() does not return all *sdt* elements


I'm trying to find all SdtElement in a Word document. I have used the below code:

using (var wordDoc = WordprocessingDocument.Open(FilePath, true))
{
    var docSdts = wordDoc.MainDocumentPart.Document.Descendants<SdtElement>();
    // MainDocumentPart.Document.Body.Descendants<SdtElement>(); also used but same result

    MainDocumentPart mainPart = wordDoc.MainDocumentPart;
    List<SdtBlock> sdtList = mainPart.Document.Body.Descendants<SdtBlock>().ToList();

    // process all sdt
}

The issue is the above code does not return all SdtElements from the file. In one document there are 19 SdtElements but it returns only 7.

As per document, the Descendants method should return elements from all levels:

Elements finds only those elements that are direct descendents, i.e. immediate children. vs Descendants finds children at any level, i.e. children, grand-children, etc...

One thing observed was only sdt under body, para, and table cells are returned, but when the sdt is under para inside a table cell, it is not returned.

I tried the code from http://www.ericwhite.com/blog/iterating-through-all-content-controls-in-an-open-xml-wordprocessingml-document/ and other similar articles.

How do I get all sdt elements from the entire document irrespective of the nesting levels?


Solution

  • SdtElement is base class for other sdt elements:

    • SdtBlock
    • SdtCell
    • SdtRow
    • SdtRun
    • SdtRunRuby

    (source: https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.sdtelement?view=openxml-2.8.1)

    You are trying to get only SdtBlock elements:

    List<SdtBlock> sdtList = mainPart.Document.Body.Descendants<SdtBlock>().ToList();
    

    If you want to get all SdtElements you should change it to:

    List<SdtElement> sdtList = mainPart.Document.Body.Descendants<SdtElement>().ToList();
    

    Note that the SdtElements may also be in other parts of the WordprocessingDocument like:

    In that case you should iterate over all those parts, for example:

    List<SdtElement> sdtList = mainPart.Document.Body.Descendants<SdtElement>().ToList();
    foreach(var part in mainPart.Document.HeaderParts)
    {
       sdtList.AddRange(part.Header.Descedants<SdtElement>());
    }
    foreach(var part in mainPart.Document.FooterParts)
    {
       sdtList.AddRange(part.Footer.Descedants<SdtElement>());
    }