How to generate Table Of Contents using OpenXML SDK 2.0?
Asked Answered
B

4

17

Using the SDK I'm building Word documents that contain reports. These documents need to have TOC. Does anybody have a complete solution that I can follow in order to understand how to do this?

(I've read everything on http://openxmldeveloper.org/)

Baroja answered 18/3, 2012 at 21:57 Comment(0)
F
14

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:

  1. 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:


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.


UPDATE 3


Thanks to @TScott - updated the link to Updating the TOC in a WordprocessingML Document using an AutoOpen Macro

Forenoon answered 19/3, 2012 at 10:58 Comment(13)
Thanks, but the link is broken. Was this it?Baroja
I have fixed link. And - yes - that is the same as posted on openxmldeveloper.org... After some research I have adjusted my answer with some new detailsForenoon
Thanks for the reply. I've also read most MSDN related content, but I have no idea how to implement this solution. Moreover, nobody seems to have provided an example. After all, a TOC is an important part of a document ... I thought it was covered by the OpenXML SDK in one way or another (or at least someone had figured out a workable easy to use solution).Baroja
A user from here put it so clear: "This whole OOXML stuff is amazing me more and more. Alas not always in a positive way.... There's so much potential but so little documentation!!! I just would like to enter some code in VS2008 using OOXML SDK2, to let Word re-generate (or generate, which is easier???) the TOC in my document. I keep trying but cant' seem to find the right bits of the parts of the package...."Baroja
Could you please describe your needs? Maybe (as often happens) you need no exactly what you are thinking about ;) Lets try to find out the workaround together...Forenoon
Thanks for the suggestion and for trying to help. So, I am getting the text from a database (titles, sub-titles, etc.) and with that text I need to create a new Word document (ideally using OpenXML). This document should have a TOC.Baroja
Do you need to mimic the TOC that Word provides? I mean do you need the features like Update TOC available later once the document is created and opened in MS Word? Or this document will be never changed once it's created?Forenoon
I need (to mimic) the functionality of what Word provides (update TOC). All the files will be later edited (using Word, or whatever program the user has installed that supports docx) after creation.Baroja
I have updated my answer with the proposed way to do the trick - please see above.Forenoon
Thanks so much for your help. I will try the suggested scenario. Will post updates and let everybody know how is everything turning out.Baroja
You're welcome! Please keep us posted on your results. Thanks!Forenoon
I've added an answer in order to give decent feedback. Thanks again.Baroja
The link is dead again, here's the updated URL: ericwhite.com/blog/…Destine
C
12

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!

Chak answered 26/11, 2019 at 0:25 Comment(1)
Works like a charm. Fixed the code block as it was leaving a couple of tags outTerrific
B
4

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.

Baroja answered 23/3, 2012 at 15:22 Comment(2)
Is it also possible to remove the modal dialog box when you're working with streams instead of files? Because I'm generating a document using memorystream, and then provide it to the user for download. Now they have to click yes to update the Table of Contents, because I can't find a way to avoid this modal dialog.Vannie
Here's an updated URL for Eric White's screencast: ericwhite.com/blog/…Destine
P
3

If you have a TOC field, this will cause it to be updated when the document is opened in Word (body is a reference to the document body):

DocumentFormat.OpenXml.Wordprocessing.SimpleField f;
f = new SimpleField();
f.Instruction="sdtContent";
f.Dirty = true;
body.Append(f);
Paltry answered 18/2, 2019 at 16:56 Comment(2)
Does this work? I tried it on a Word document that already had a TOC in it (made through Word Application, not programmatically), then I tried adding this SimpleField programmatically, it didn't auto-update when the Word document opened.Burger
When you open it in Word, you should get a message that says, "This document contains fields that may refer to other files. Do you want to update the fields in this document?" You should answer Yes.Paltry

© 2022 - 2024 — McMap. All rights reserved.