// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using DocumentFormat.OpenXml.Packaging; namespace OpenXmlPowerTools { public partial class WmlDocument : OpenXmlPowerToolsDocument { public WmlDocument AddToc(string xPath, string switches, string title, int? rightTabPos) { return (WmlDocument)ReferenceAdder.AddToc(this, xPath, switches, title, rightTabPos); } public WmlDocument AddTof(string xPath, string switches, int? rightTabPos) { return (WmlDocument)ReferenceAdder.AddTof(this, xPath, switches, rightTabPos); } public WmlDocument AddToa(string xPath, string switches, int? rightTabPos) { return (WmlDocument)ReferenceAdder.AddToa(this, xPath, switches, rightTabPos); } } public class ReferenceAdder { public static WmlDocument AddToc(WmlDocument document, string xPath, string switches, string title, int? rightTabPos) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document)) { using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument()) { AddToc(doc, xPath, switches, title, rightTabPos); } return streamDoc.GetModifiedWmlDocument(); } } public static void AddToc(WordprocessingDocument doc, string xPath, string switches, string title, int? rightTabPos) { UpdateFontTablePart(doc); UpdateStylesPartForToc(doc); UpdateStylesWithEffectsPartForToc(doc); if (title == null) title = "Contents"; if (rightTabPos == null) rightTabPos = 9350; // {0} tocTitle (default = "Contents") // {1} rightTabPosition (default = 9350) // {2} switches String xmlString = @" {0} {2} "; XmlReader sdtReader = XmlReader.Create(new StringReader(String.Format(xmlString, title, rightTabPos, switches))); XElement sdt = XElement.Load(sdtReader); XmlNamespaceManager namespaceManager; XDocument mainXDoc = doc.MainDocumentPart.GetXDocument(out namespaceManager); namespaceManager.AddNamespace("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"); XElement addBefore = mainXDoc.XPathSelectElement(xPath, namespaceManager); if (addBefore == null) throw new OpenXmlPowerToolsException("XPath expression did not select an element"); addBefore.AddBeforeSelf(sdt); doc.MainDocumentPart.PutXDocument(); XDocument settingsXDoc = doc.MainDocumentPart.DocumentSettingsPart.GetXDocument(); XElement updateFields = settingsXDoc.Descendants(W.updateFields).FirstOrDefault(); if (updateFields != null) updateFields.Attribute(W.val).Value = "true"; else { updateFields = new XElement(W.updateFields, new XAttribute(W.val, "true")); settingsXDoc.Root.Add(updateFields); } doc.MainDocumentPart.DocumentSettingsPart.PutXDocument(); } public static WmlDocument AddTof(WmlDocument document, string xPath, string switches, int? rightTabPos) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document)) { using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument()) { AddTof(doc, xPath, switches, rightTabPos); } return streamDoc.GetModifiedWmlDocument(); } } public static void AddTof(WordprocessingDocument doc, string xPath, string switches, int? rightTabPos) { UpdateFontTablePart(doc); UpdateStylesPartForTof(doc); UpdateStylesWithEffectsPartForTof(doc); if (rightTabPos == null) rightTabPos = 9350; // {0} rightTabPosition (default = 9350) // {1} switches string xmlString = @" {1} "; XDocument mainXDoc = doc.MainDocumentPart.GetXDocument(); XmlReader paragraphReader = XmlReader.Create(new StringReader(String.Format(xmlString, rightTabPos, switches))); XElement paragraph = XElement.Load(paragraphReader); XmlNameTable nameTable = paragraphReader.NameTable; XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable); namespaceManager.AddNamespace("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"); XElement addBefore = mainXDoc.XPathSelectElement(xPath, namespaceManager); if (addBefore == null) throw new OpenXmlPowerToolsException("XPath expression did not select an element"); addBefore.AddBeforeSelf(paragraph); doc.MainDocumentPart.PutXDocument(); XDocument settingsXDoc = doc.MainDocumentPart.DocumentSettingsPart.GetXDocument(); XElement updateFields = settingsXDoc.Descendants(W.updateFields).FirstOrDefault(); if (updateFields != null) updateFields.Attribute(W.val).Value = "true"; else { updateFields = new XElement(W.updateFields, new XAttribute(W.val, "true")); settingsXDoc.Root.Add(updateFields); } doc.MainDocumentPart.DocumentSettingsPart.PutXDocument(); } public static WmlDocument AddToa(WmlDocument document, string xPath, string switches, int? rightTabPos) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document)) { using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument()) { AddToa(doc, xPath, switches, rightTabPos); } return streamDoc.GetModifiedWmlDocument(); } } public static void AddToa(WordprocessingDocument doc, string xPath, string switches, int? rightTabPos) { UpdateFontTablePart(doc); UpdateStylesPartForToa(doc); UpdateStylesWithEffectsPartForToa(doc); if (rightTabPos == null) rightTabPos = 9350; // {0} rightTabPosition (default = 9350) // {1} switches string xmlString = @" {1} "; XDocument mainXDoc = doc.MainDocumentPart.GetXDocument(); XmlReader paragraphReader = XmlReader.Create(new StringReader(String.Format(xmlString, rightTabPos, switches))); XElement paragraph = XElement.Load(paragraphReader); XmlNameTable nameTable = paragraphReader.NameTable; XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable); namespaceManager.AddNamespace("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"); XElement addBefore = mainXDoc.XPathSelectElement(xPath, namespaceManager); if (addBefore == null) throw new OpenXmlPowerToolsException("XPath expression did not select an element"); addBefore.AddBeforeSelf(paragraph); doc.MainDocumentPart.PutXDocument(); XDocument settingsXDoc = doc.MainDocumentPart.DocumentSettingsPart.GetXDocument(); XElement updateFields = settingsXDoc.Descendants(W.updateFields).FirstOrDefault(); if (updateFields != null) updateFields.Attribute(W.val).Value = "true"; else { updateFields = new XElement(W.updateFields, new XAttribute(W.val, "true")); settingsXDoc.Root.Add(updateFields); } doc.MainDocumentPart.DocumentSettingsPart.PutXDocument(); } private static void AddElementIfMissing(XDocument partXDoc, XElement existing, string newElement) { if (existing != null) return; XElement newXElement = XElement.Parse(newElement); newXElement.Attributes().Where(a => a.IsNamespaceDeclaration).Remove(); partXDoc.Root.Add(newXElement); } private static void UpdateFontTablePart(WordprocessingDocument doc) { FontTablePart fontTablePart = doc.MainDocumentPart.FontTablePart; if (fontTablePart == null) throw new Exception("Todo need to insert font table part"); XDocument fontTableXDoc = fontTablePart.GetXDocument(); AddElementIfMissing(fontTableXDoc, fontTableXDoc .Root .Elements(W.font) .Where(e => (string)e.Attribute(W.name) == "Tahoma") .FirstOrDefault(), @" "); fontTablePart.PutXDocument(); } private static void UpdatePartForToc(OpenXmlPart part) { XDocument xDoc = part.GetXDocument(); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOCHeading") .FirstOrDefault(), @" "); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOC1") .FirstOrDefault(), @" "); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOC2") .FirstOrDefault(), @" "); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOC3") .FirstOrDefault(), @" "); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOC4") .FirstOrDefault(), @" "); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Hyperlink") .FirstOrDefault(), @" "); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "BalloonText") .FirstOrDefault(), @" "); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "character" && (bool?)e.Attribute(W.customStyle) == true && (string)e.Attribute(W.styleId) == "BalloonTextChar") .FirstOrDefault(), @" "); part.PutXDocument(); } private static void UpdateStylesPartForToc(WordprocessingDocument doc) { StylesPart stylesPart = doc.MainDocumentPart.StyleDefinitionsPart; if (stylesPart == null) return; UpdatePartForToc(stylesPart); } private static void UpdateStylesWithEffectsPartForToc(WordprocessingDocument doc) { StylesWithEffectsPart stylesWithEffectsPart = doc.MainDocumentPart.StylesWithEffectsPart; if (stylesWithEffectsPart == null) return; UpdatePartForToc(stylesWithEffectsPart); } private static void UpdatePartForTof(OpenXmlPart part) { XDocument xDoc = part.GetXDocument(); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TableofFigures") .FirstOrDefault(), @" "); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Hyperlink") .FirstOrDefault(), @" "); part.PutXDocument(); } private static void UpdateStylesPartForTof(WordprocessingDocument doc) { StylesPart stylesPart = doc.MainDocumentPart.StyleDefinitionsPart; if (stylesPart == null) return; UpdatePartForTof(stylesPart); } private static void UpdateStylesWithEffectsPartForTof(WordprocessingDocument doc) { StylesWithEffectsPart stylesWithEffectsPart = doc.MainDocumentPart.StylesWithEffectsPart; if (stylesWithEffectsPart == null) return; UpdatePartForTof(stylesWithEffectsPart); } private static void UpdatePartForToa(OpenXmlPart part) { XDocument xDoc = part.GetXDocument(); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TableofAuthorities") .FirstOrDefault(), @" "); AddElementIfMissing( xDoc, xDoc.Root.Elements(W.style) .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOAHeading") .FirstOrDefault(), @" "); part.PutXDocument(); } private static void UpdateStylesPartForToa(WordprocessingDocument doc) { StylesPart stylesPart = doc.MainDocumentPart.StyleDefinitionsPart; if (stylesPart == null) return; UpdatePartForToa(stylesPart); } private static void UpdateStylesWithEffectsPartForToa(WordprocessingDocument doc) { StylesWithEffectsPart stylesWithEffectsPart = doc.MainDocumentPart.StylesWithEffectsPart; if (stylesWithEffectsPart == null) return; UpdatePartForToa(stylesWithEffectsPart); } } }