Can anyone tell me how to update word table of contents using Open XML SDK 2.0 dynamically.
Hi prosantak,
I see your code create the TOC, but is the TOC able to update the hyperlinks automatically?
Hi goodol
Yes, it will update the hyperlink automatically when the document open.
Thanks
Prosanta
goodol:... but there's a workaround for you: use dirty value to indicate Word client to do it when you open the document. here's the discusstion: http://social.msdn.microsoft.com/Forums/en-US/oxmlsdk/thread/29199b44-f033-466a-a471-6bdcfeb47e41
The logic behind TOC generation is very simple. It opens the docx file using openxml.
1. Find the paragraph marks with diffrent heading style 2. Sets a hyperlink there3. Update TOC entry
But in this case page numbers will not updated. When user opens the generated document, he/she have to update page numbers manually. Also you can set w:dirty attribute in TOC, but it will prompt for updation when the document is open first time. In my case I didn't use the w:diry attribute. I leave it to user to update page numbers manually.
ThanksProsanta
What is "ns" ,"bookmarkNamePrefix" and XDocument xmlMainDocument, XElement TOCRefNode in the code?
And also
sdtContent.Descendants().Where( tag => tag.Name == ns + "instrText" && tag.Value.Contains("TOCLIMIT") ).FirstOrDefault().Value = String.Format(@" TOC \o ""1-{0}"" \h \z \u ",maxHeading);What is TOCLIMIT ?
i found public static XNamespace ns = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
Here is the complete solution
using (WordprocessingDocument WorDocument = WordprocessingDocument.Create(@"C:\TestDocuments\Business Case Document.docx", WordprocessingDocumentType.Document)) { // Add a new main document part. MainDocumentPart mainPart = WorDocument.AddMainDocumentPart(); //Create Document tree for simple document. mainPart.Document = new Document(); //Create Body (this element contains other elements that we want to include Body body = new Body();
//Save changes to the main document part. mainPart.Document.Append(body); XDocument xmlXdocument = XDocument.Parse(mainPart.Document.InnerXml); IEnumerable<XElement> xmlelement = xmlXdocument.Descendants(ns + "sdt"); XElement TOCRefNode = xmlelement.First(); GenerateTOC(xmlXdocument, TOCRefNode);
mainPart.Document.InnerXml = xmlXdocument.ToString(); mainPart.Document.Save(); WorDocument.Close();
}
#region TOC Creation /// <summary> /// returns title paragraphs of genereated doc fro creating toc hyperlink /// </summary> private static IEnumerable<XElement> TitleParagraphsElements(XDocument mainDocument) { IEnumerable<XElement> results = mainDocument.Descendants().Where ( tag => tag.Name == ns + "p" && tag.Descendants(ns + "t").Count() > 0 && tag.Descendants().Where ( tag2 => tag2.Name == ns + "pStyle" && ( tag2.Attribute(ns + "val").Value == "Head1" || tag2.Attribute(ns + "val").Value == "Head2" || tag2.Attribute(ns + "val").Value == "Head3" || tag2.Attribute(ns + "val").Value == "Head4" || tag2.Attribute(ns + "val").Value == "Head5" || tag2.Attribute(ns + "val").Value == "Head6" ) ).Count() > 0 );
return results; }
private static void GenerateTOC(XDocument xmlMainDocument, XElement TOCRefNode) { int bookMarkIdCounter = 0; int maxHeading = 1; int tempheading = 1;
// sdtContent, will contain all the paragraphs used in the TOC XElement sdtContent = new XElement(ns + "sdtContent"); String strContentHdr = ""; XElement xContentHdr = TOCRefNode.Elements(ns + "sdtContent").First().Descendants().Where( tag => tag.Name == ns + "p" && tag.Descendants().Where ( tag2 => tag2.Name == ns + "pStyle" && tag2.Attribute(ns + "val").Value == "TOCHeading" ).Count() > 0 ).FirstOrDefault();
if (xContentHdr != null) strContentHdr = xContentHdr.Descendants(ns + "t").FirstOrDefault().Value;
// some information regarding the attributes of the TOC xContentHdr.Add(
new XElement(ns + "r", new XElement(ns + "fldChar", new XAttribute(ns + "fldCharType", "begin"))), new XElement(ns + "r", new XElement(ns + "instrText", new XAttribute(XNamespace.Xml + "space", "preserve"), "TOCLIMIT")), new XElement(ns + "r", new XElement(ns + "fldChar", new XAttribute(ns + "fldCharType", "separate")))); sdtContent.Add(new XElement(xContentHdr));
// for each title found it in the document, we have to wrap the run inside of it, // with a bookmark, this bookmark will have an id which will work as an anchor, // for link references in the TOC foreach (XElement titleParagraph in TitleParagraphsElements(xmlMainDocument)) { string bookmarkName = "_TOC" + bookMarkIdCounter; XElement bookmarkStart = new XElement(ns + "bookmarkStart", new XAttribute(ns + "id", bookMarkIdCounter), new XAttribute(ns + "name", bookmarkName)); XElement bookmarkEnd = new XElement(ns + "bookmarkEnd", new XAttribute(ns + "id", bookMarkIdCounter));
// wrap the run with bookmarkStart and bookmarkEnd titleParagraph.AddFirst(bookmarkStart); titleParagraph.Add(bookmarkEnd);
// get the name of the style of the parapgraph of the title, and for each one, // choose a style to add in the paragraph inside the TOC string referenceTitleStyle = ""; switch (titleParagraph.Descendants(ns + "pStyle").First().Attribute(ns + "val").Value) { case "Head1": { referenceTitleStyle = "TOC1"; tempheading = 1; break; } case "Head2": { referenceTitleStyle = "TOC2"; tempheading = 2; break; } case "Head3": { referenceTitleStyle = "TOC3"; tempheading = 3; break; } case "Head4": { referenceTitleStyle = "TOC4"; tempheading = 4; break; } case "Head5": { referenceTitleStyle = "TOC5"; tempheading = 5; break; } case "Head6": { referenceTitleStyle = "TOC6"; tempheading = 6; break; } }
string entryContent = ""; IEnumerable<XElement> owTList = titleParagraph.Descendants(ns + "t"); foreach (XElement entryElement in owTList) { entryContent += (entryElement == null ? string.Empty : entryElement.Value); }
XElement TOCElement = null; XElement tempTOCElement = TOCRefNode.Elements(ns + "sdtContent").First().Descendants().Where( tag => tag.Name == ns + "p" && tag.Descendants().Where ( tag2 => tag2.Name == ns + "pStyle" && tag2.Attribute(ns + "val").Value == referenceTitleStyle ).Count() > 0 ).FirstOrDefault();
if (tempTOCElement != null) { if (maxHeading < tempheading) maxHeading = tempheading;
TOCElement = new XElement(tempTOCElement); //delete instrText which contains TOC 1-n XElement instrTextTOC = TOCElement.Descendants().Where( tag => tag.Name == ns + "r" && tag.Descendants().Where( tag2 => tag2.Name == ns + "instrText" && tag2.Value.Contains(@"TOC \o ") ).Count() > 0
).FirstOrDefault(); if (instrTextTOC != null) { instrTextTOC.ElementsAfterSelf(ns + "r").Remove(); instrTextTOC.ElementsBeforeSelf(ns + "r").Remove(); instrTextTOC.Remove(); }
//get hyperlink node XElement hyperlink = TOCElement.Descendants().Where( tag => tag.Name == ns + "hyperlink"
).FirstOrDefault(); //update anchor attribute value hyperlink.Attribute(ns + "anchor").Value = bookmarkName;
//get entry content node XElement contentNode = hyperlink.Descendants().Where( tag => tag.Name == ns + "r" && tag.Descendants().Where( tag2 => tag2.Name == ns + "rStyle" && tag2.Attribute(ns + "val").Value == "Hyperlink" ).Count() > 0 && tag.Elements(ns + "t").Count() > 0
).FirstOrDefault().Elements(ns + "t").FirstOrDefault();
contentNode.Value = entryContent;
//update PAGEREF value XElement instrText = TOCElement.Descendants().Where( tag => tag.Name == ns + "instrText" && tag.Value.Contains("PAGEREF ") ).FirstOrDefault(); if (instrText != null) { instrText.Value = " PAGEREF " + bookmarkName + @" \h "; }
sdtContent.Add(TOCElement); bookMarkIdCounter++; }
sdtContent.Descendants().Where( tag => tag.Name == ns + "instrText" && tag.Value.Contains("TOCLIMIT") ).FirstOrDefault().Value = String.Format(@"TOC \o ""1-{0}"" \h \z \u ", maxHeading);
sdtContent.Add( new XElement(ns + "p", new XElement(ns + "r", new XElement(ns + "fldChar", new XAttribute(ns + "fldCharType", "end")))));
// Finish the xml construction of the TOC XElement TOC = new XElement(ns + "sdt", new XElement(ns + "sdtPr", new XElement(ns + "docPartObj", new XElement(ns + "docPartGallery", new XAttribute(ns + "val", "Table of Contents")), new XElement(ns + "docPartUnique"))), sdtContent);
// add it to the original document
IEnumerable<XElement> tocNodes = xmlMainDocument.Descendants().Where ( tag => tag.Name == ns + "sdt" && tag.Descendants(ns + "sdtContent").Count() > 0 && tag.Descendants().Where ( tag2 => tag2.Name == ns + "p" && tag2.Descendants().Where ( tag3 => tag3.Name == ns + "pStyle" && ( tag3.Attribute(ns + "val").Value == "TOCHeading" ) ).Count() > 0
).Count() > 0 );
TOCRefNode.ReplaceWith(TOC);
#endregion
It doesn't work too, and when I run this program the variable "xContentHdr" will be null.
Can someone tell me what should be in "TOCRefNode"?
I want to know whether this prograom just update the exist TOC but not create it?
LMars: goodol:... but there's a workaround for you: use dirty value to indicate Word client to do it when you open the document. here's the discusstion: http://social.msdn.microsoft.com/Forums/en-US/oxmlsdk/thread/29199b44-f033-466a-a471-6bdcfeb47e41 Unfortunately, it doesn't help me. Word 2007 doesn't update content of TOC on open :( I experimented with other field types (AUTHOR, FILENAME), Word 2007 doesn't update these fields too.
Hi,
It is working fine. Check out the following Steps:
static XNamespace ns = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; public void generateWordDocument() { WindowsIdentity currentUserIdentity = (WindowsIdentity)User.Identity; WindowsImpersonationContext impersonationContext = currentUserIdentity.Impersonate(); try { string fileName = currentUserIdentity.Name.Replace("\\", "_") + "_Report.docx"; string filePath = @"D:\Personals\Sudip\" + fileName; File.Copy(@"D:\Personals\Sudip\Contents.docx", filePath); using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(filePath, true)) { MainDocumentPart mainPart = wordDoc.MainDocumentPart; //Create Document tree for simple document. mainPart.Document = new Document(); //Create Body (this element contains other elements that we want to include Body body = new Body(); //This body should contain Headers. //Save changes to the main document part. mainPart.Document.Append(body);
XDocument xmlXdocument = XDocument.Parse(mainPart.Document.InnerXml); IEnumerable<XElement> xmlelement = xmlXdocument.Descendants(ns + "sdt"); XElement TOCRefNode = xmlelement.First(); GenerateTOC(xmlXdocument, TOCRefNode);
mainPart.Document.InnerXml = xmlXdocument.ToString();
mainPart.Document.Save(); wordDoc.Close(); } } } #region TOC Creation /// <summary> /// returns title paragraphs of genereated doc fro creating toc hyperlink /// </summary> private static IEnumerable<XElement> TitleParagraphsElements(XDocument mainDocument) { IEnumerable<XElement> results = mainDocument.Descendants().Where ( tag => tag.Name == ns + "p" && tag.Descendants(ns + "t").Count() > 0 && tag.Descendants().Where ( tag2 => tag2.Name == ns + "pStyle" && ( tag2.Attribute(ns + "val").Value == "Head1" || tag2.Attribute(ns + "val").Value == "Head2" || tag2.Attribute(ns + "val").Value == "Head3" || tag2.Attribute(ns + "val").Value == "Head4" || tag2.Attribute(ns + "val").Value == "Head5" || tag2.Attribute(ns + "val").Value == "Head6" || tag2.Attribute(ns + "val").Value == "Heading1"|| tag2.Attribute(ns + "val").Value == "Heading2" ) ).Count() > 0 );
// get the name of the style of the parapgraph of the title, and for each one, // choose a style to add in the paragraph inside the TOC string referenceTitleStyle = ""; switch (titleParagraph.Descendants(ns + "pStyle").First().Attribute(ns + "val").Value) { case "Head1": { referenceTitleStyle = "TOC1"; tempheading = 1; break; } case "Head2": { referenceTitleStyle = "TOC2"; tempheading = 2; break; } case "Head3": { referenceTitleStyle = "TOC3"; tempheading = 3; break; } case "Head4": { referenceTitleStyle = "TOC4"; tempheading = 4; break; } case "Head5": { referenceTitleStyle = "TOC5"; tempheading = 5; break; } case "Head6": { referenceTitleStyle = "TOC6"; tempheading = 6; break; } case "Heading1": { referenceTitleStyle = "TOC1"; tempheading = 6; break; } case "Heading2": { referenceTitleStyle = "TOC2"; tempheading = 6; break; } }