How to generate Table Of Contents using OpenXML SDK 2.0?

Have a look at Fourth and Final Screen-Cast in Series on Adding/Updating the TOC in OpenXML WordprocessingML Documents by Eric White.

Hope that helps!

UPDATE:


According FAQ from MSDN Forums I see that this feature is not supported:

8) How to generate TOC (table of contents) in Word document?

Open XML SDK 2.0 does not have this feature supported. But you can generate a small TOC through Word app, and reflect the TOC parts with Document Reflector component in Open XML SDK Productivity Tool to see how to generate a TOC programmatically. For more detailed information, please refer to:

  • MSDN forum thread: Generating Table of Contents and Applying a Custom Style,

  • MSDN forum thread: How can i Create dir for word document.


UPDATE 2


Based on our comments below I could propose to use this scenario:

  1. You manually create an empty DOCX file and insert TOC inside it.
  2. Then you save this file and open it in OpenXML SDK 2.0 Tool, which provides you with the C# code to generate such empty file with TOC placeholder inside.
  3. Then you programmatically flush all the data you need to this DOCX file and save it.
  4. In addition you will need provide the mechanism that will auto update TOC once the data are flushed (or once the document is opened). There are a few options to do that - see screen-casts 3-5 fromthe link to Eric White post I provided above. Especially, I think youshould pay your attention to 5th one - "Shows how to use an AutoOpen macro to update the TOC whenever any document that contains a TOC is opened".

All of that look a bit tricky, but I hope that helps.


Thanks to Dmitri Pavlov (@DmitryPavlov) for the help.

I don't want to give an answer to my own question, but this is just to illustrate the steps that I’ve taken.

The advice for anyone interested is to watch the 5-part screen-cast by Eric White - Exploring Tables-of-Contents in Open XML WordprocessingML Documents. This has all the info with respect to adding and updating a TOC (am much more).

My solution was to use a Template (just a regular empty document that had styles for everything I needed: Header 1-5, TOC style, etc.). This is particularly useful as a quick fix for the styles issue (the new document, that has the TOC, will have a new style.xml created; this file has some additional data; as a result the hierarchy in the TOC isn’t as expected – i.e., header 2 is the child of header 1, header 3 is a child of header 2, etc.).

Therefore:

  1. Create a Word document and add all the elements that you expect to be added later programmatically (e.g., Header 1-5, Table of Contents, etc.). Delete all the contents and save the document (the reason for this is to create styles for all the necessary elements).

  2. I personally added the template (the file created at step #1) as a resource in my project.

  3. In your code, create a new copy of the template (this will be the actual file that you will work on). I used:

    byte[] stream = Properties.Resources.Template;
    File.WriteAllBytes(@"D:\Template.docx", stream);
    File.Copy(@"D:\Template.docx", @"D:\New.docx");
    
  4. Flush all the data to this document.

  5. Add the source files from screen-cast 2, 3 or 4 to your project (for this please see screen-cast 3) - at the end of those posts you will find a link to download TocAdder.zip. Or just add a reference to TocAdder.dll.

  6. Insert the TOC. Just an example:

    using (WordprocessingDocument wdoc = WordprocessingDocument.Open(@"D:\New.docx", true))
    {
        XElement firstPara = wdoc
            .MainDocumentPart
            .GetXDocument()
            .Descendants(W.p)
            .FirstOrDefault();
        TocAdder.AddToc(wdoc, firstPara,
            @"TOC \o '1-3' \h \z \u", null, null);
    }
    
  7. Replace the styles in the newly created document with the ones from the template. You can use this resource from MSDN: Replacing the Styles Parts in Word 2010 Documents by Using the Open XML SDK 2.0. Again, an example:

    string fromDoc = @"D:\Template.docx";
    string toDoc = @"D:\New.docx";
    var node = WDExtractStyles(fromDoc, false);
    if (node != null)
        WDReplaceStyles(toDoc, node, false);
    node = WDExtractStyles(fromDoc);
    if (node != null)
        WDReplaceStyles(toDoc, node);
    
  8. Optionally use one of the methods described in screen-cast 3, 4 or 5 in order to get around the problem with the modal dialog box that Word puts up.

Hope this will be useful for somebody.


Use auto Table of contents (clickable)

  1. Set Heading

     public static Paragraph SetHeading1(this Paragraph p)
     {
         var pPr = p.Descendants<ParagraphProperties>().First();
         pPr.ParagraphStyleId = new ParagraphStyleId() { Val = "Heading1" };
         return p;
     }
    
  2. Generate TOC from empty document, you can use this:

private static string GetTOC(string title, int titleFontSize)
    {
        return $@"<w:sdt>
     <w:sdtPr>
        <w:id w:val=""-493258456"" />
        <w:docPartObj>
           <w:docPartGallery w:val=""Table of Contents"" />
           <w:docPartUnique />
        </w:docPartObj>
     </w:sdtPr>
     <w:sdtEndPr>
        <w:rPr>
           <w:rFonts w:asciiTheme=""minorHAnsi"" w:eastAsiaTheme=""minorHAnsi"" w:hAnsiTheme=""minorHAnsi"" w:cstheme=""minorBidi"" />
           <w:b />
           <w:bCs />
           <w:noProof />
           <w:color w:val=""auto"" />
           <w:sz w:val=""22"" />
           <w:szCs w:val=""22"" />
        </w:rPr>
     </w:sdtEndPr>
     <w:sdtContent>
        <w:p w:rsidR=""00095C65"" w:rsidRDefault=""00095C65"">
           <w:pPr>
              <w:pStyle w:val=""TOCHeading"" />
              <w:jc w:val=""center"" /> 
           </w:pPr>
           <w:r>
                <w:rPr>
                  <w:b /> 
                  <w:color w:val=""2E74B5"" w:themeColor=""accent1"" w:themeShade=""BF"" /> 
                  <w:sz w:val=""{titleFontSize * 2}"" /> 
                  <w:szCs w:val=""{titleFontSize * 2}"" /> 
              </w:rPr>
              <w:t>{title}</w:t>
           </w:r>
        </w:p>
        <w:p w:rsidR=""00095C65"" w:rsidRDefault=""00095C65"">
           <w:r>
              <w:rPr>
                 <w:b />
                 <w:bCs />
                 <w:noProof />
              </w:rPr>
              <w:fldChar w:fldCharType=""begin"" />
           </w:r>
           <w:r>
              <w:rPr>
                 <w:b />
                 <w:bCs />
                 <w:noProof />
              </w:rPr>
              <w:instrText xml:space=""preserve""> TOC \o ""1-3"" \h \z \u </w:instrText>
           </w:r>
           <w:r>
              <w:rPr>
                 <w:b />
                 <w:bCs />
                 <w:noProof />
              </w:rPr>
              <w:fldChar w:fldCharType=""separate"" />
           </w:r>
           <w:r>
              <w:rPr>
                 <w:noProof />
              </w:rPr>
              <w:t>No table of contents entries found.</w:t>
           </w:r>
           <w:r>
              <w:rPr>
                 <w:b />
                 <w:bCs />
                 <w:noProof />
              </w:rPr>
              <w:fldChar w:fldCharType=""end"" />
           </w:r>
        </w:p>
     </w:sdtContent>
  </w:sdt>"
        }
  1. Create SdtBlock, and set TOC xml

         var sdtBlock = new SdtBlock();
         sdtBlock.InnerXml = GetTOC(Translations.ResultsBooksTableOfContentsTitle, 16);
         document.MainDocumentPart.Document.Body.AppendChild(sdtBlock);
    
  2. Set UpdateFieldsOnOpen

         var settingsPart = document.MainDocumentPart.AddNewPart<DocumentSettingsPart>();
         settingsPart.Settings = new Settings { BordersDoNotSurroundFooter = new BordersDoNotSurroundFooter() { Val = true } };
    
         settingsPart.Settings.Append(new UpdateFieldsOnOpen() { Val = true });
    

if you need generate pdf file from docx, it's working fine!