/***************************************************************************
Copyright (c) Microsoft Corporation 2011.
This code is licensed using the Microsoft Public License (Ms-PL). The text of the license can be found here:
http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx
***************************************************************************/
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml;
using DocumentFormat.OpenXml.Packaging;
using System.Collections.ObjectModel;
using System;
namespace OpenXmlPowerTools
{
///
/// Provides access to style operations
///
public class StyleAccessor
{
//private XDocument xmlStylesDefinitionDocument;
private static XNamespace ns;
///
/// newStyleNameSuffic variable
///
public static readonly string newStyleNameSuffix = "_1";
static StyleAccessor()
{
ns = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
}
public static void CreateIndexStyles(WordprocessingDocument document, string stylesSourceFile, bool addDefaultStyles)
{
if (stylesSourceFile == "")
{
if (addDefaultStyles)
{
XElement Index1 = new XElement(ns + "style",
new XAttribute(ns + "type", "paragraph"),
new XAttribute(ns + "styleId", "Index1"),
new XElement(ns + "name",
new XAttribute(ns + "val", "index 1")),
new XElement(ns + "basedOn",
new XAttribute(ns + "val", "Normal")),
new XElement(ns + "next",
new XAttribute(ns + "val", "Normal")),
new XElement(ns + "autoRedefine"),
new XElement(ns + "semiHidden"),
new XElement(ns + "unhideWhenUsed"),
new XElement(ns + "pPr",
new XElement(ns + "spacing",
new XAttribute(ns + "after", "0"),
new XAttribute(ns + "line", "240"),
new XAttribute(ns + "lineRule", "auto")),
new XElement(ns + "ind",
new XAttribute(ns + "left", "220"),
new XAttribute(ns + "hanging", "220"))));
AddStyleDefinition(document, Index1);
}
}
else
{
// add the styles from the styles source file
XDocument StyleXmlPart = GetStylesDocument(document);
// the preffix must be empty, because the styles need to be recognized by the TOC
XDocument stylesSource = XDocument.Load(stylesSourceFile);
IEnumerable IndexStyles = GetStyleHierarchy("Index1", stylesSource, string.Empty);
AddStyleDefinition(document, IndexStyles);
}
document.MainDocumentPart.StyleDefinitionsPart.PutXDocument();
}
public static void CreateTOAStyles(WordprocessingDocument document, string stylesSourceFile, bool addDefaultStyles)
{
if (stylesSourceFile == "")
{
if (addDefaultStyles)
{
XElement TOAHeading = new XElement(ns + "style",
new XAttribute(ns + "type", "paragraph"),
new XAttribute(ns + "styleId", "TOAHeading"),
new XElement(ns + "name",
new XAttribute(ns + "val", "toa heading")),
new XElement(ns + "basedOn",
new XAttribute(ns + "val", "Normal")),
new XElement(ns + "next",
new XAttribute(ns + "val", "Normal")),
new XElement(ns + "semiHidden"),
new XElement(ns + "unhideWhenUsed"),
new XElement(ns + "pPr",
new XElement(ns + "spacing",
new XAttribute(ns + "before", "120"))),
new XElement(ns + "rPr",
new XElement(ns + "b"),
new XElement(ns + "bCs"),
new XElement(ns + "sz",
new XAttribute(ns + "val", 24)),
new XElement(ns + "szCs",
new XAttribute(ns + "val", 24))));
AddStyleDefinition(document, TOAHeading);
XElement tableOfAuthorities = new XElement(ns + "style",
new XAttribute(ns + "type", "paragraph"),
new XAttribute(ns + "styleId", "TableofAuthorities"),
new XElement(ns + "name",
new XAttribute(ns + "val", "table of authorities")),
new XElement(ns + "basedOn",
new XAttribute(ns + "val", "Normal")),
new XElement(ns + "next",
new XAttribute(ns + "val", "Normal")),
new XElement(ns + "semiHidden"),
new XElement(ns + "unhideWhenUsed"),
new XElement(ns + "pPr",
new XElement(ns + "spacing",
new XAttribute(ns + "after", "0")),
new XElement(ns + "ind",
new XAttribute(ns + "left", "220"),
new XAttribute(ns + "hanging", "220"))));
AddStyleDefinition(document, tableOfAuthorities);
}
}
else
{
// add the styles from the styles source file
XDocument StyleXmlPart = GetStylesDocument(document);
// the prefix must be empty, because the styles need to be recognized by the TOC
XDocument stylesSource = XDocument.Load(stylesSourceFile);
IEnumerable TOAStyles = GetStyleHierarchy("TOAHeading", stylesSource, string.Empty);
TOAStyles = TOAStyles.Concat(GetStyleHierarchy("TableofAuthorities", stylesSource, string.Empty));
AddStyleDefinition(document, TOAStyles);
}
document.MainDocumentPart.StyleDefinitionsPart.PutXDocument();
}
private static XDocument GetStylesDocument(WordprocessingDocument document)
{
if (document.MainDocumentPart.StyleDefinitionsPart != null)
return document.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
return null;
}
public static XDocument GetStylesDocument(WmlDocument doc)
{
using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
{
return GetStylesDocument(document);
}
}
///
/// Gets the style hierarchy (link styles, next styles and basedOn styles) associated
/// to the specified style, in a XElement collection
///
/// Name of the style from where to get the hierarchy
/// File from where styles are taken
/// Suffix of style name.
/// a collection containing the specified style and all the styles associated with it
private static Collection GetStyleHierarchy(string styleName, XDocument stylesFile, string styleNameSuffix)
{
try
{
Collection stylesCollection = new Collection();
GetStyleHierarchyProcess(styleName, stylesFile, stylesCollection);
return stylesCollection;
}
catch (XmlException ex)
{
throw new ArgumentException("File specified is not a valid XML file", ex);
}
}
///
/// Gets the xml of a specific style definition
///
/// Name of the style to get from the styles file
/// Style definitions
/// Collection of styles
private static void GetStyleHierarchyProcess(string styleName, XDocument xmlStyleDefinitions, Collection stylesCollection)
{
XName style = ns + "style";
XName styleId = ns + "styleId";
// the style name can come empty, because the given style could not have a link, basedOn or next style.
// In those cases the stylename will come empty
if (styleName != "")
{
// Creates a copy of the xmlStyleDefinition variable so the original xml will not be altered
XElement actualStyle = new XElement(xmlStyleDefinitions.Root);
actualStyle = actualStyle.Descendants().Where
(
tag =>
(tag.Name == style) && (tag.Attribute(styleId).Value.ToUpper() == styleName.ToUpper())
).ToList().FirstOrDefault();
if (actualStyle != null)
{
// Looks in the stylesCollection if the style has already been added
IEnumerable insertedStyles =
stylesCollection.Where
(
tag =>
(tag.Name == style) && (tag.Attribute(styleId).Value.ToUpper() == styleName.ToUpper())
);
// If the style has not been inserted
if (insertedStyles.Count() == 0)
{
stylesCollection.Add(actualStyle);
GetStyleHierarchyProcess(GetLinkStyleId(actualStyle), xmlStyleDefinitions, stylesCollection);
GetStyleHierarchyProcess(GetNextStyleId(actualStyle), xmlStyleDefinitions, stylesCollection);
GetStyleHierarchyProcess(GetBasedOnStyleId(actualStyle), xmlStyleDefinitions, stylesCollection);
}
// Changes the name of the style, so there would be no conflict with the original styles definition
actualStyle.Attribute(styleId).Value = actualStyle.Attribute(styleId).Value + newStyleNameSuffix;
}
else
throw new InvalidOperationException("Style or dependencies not found in the given style library.");
}
}
///
/// Gets the name of the 'link' style associated to the given style
///
/// Xml to search for linked style
/// Name of the style
private static string GetLinkStyleId(XElement xmlStyle)
{
XName val = ns + "val";
string linkStyleId = "";
XElement linkStyle = xmlStyle.Descendants(ns + "link").FirstOrDefault();
if (linkStyle != null)
{
linkStyleId = linkStyle.Attribute(val).Value;
// Changes the name of the attribute, because the new added style is being renamed
linkStyle.Attribute(val).Value = linkStyle.Attribute(val).Value + newStyleNameSuffix;
}
return linkStyleId;
}
///
/// Gets the name of the style tagged as 'next' associated to a given style
///
/// Xml to search for next style
/// Name of the style
private static string GetNextStyleId(XElement xmlStyle)
{
XName val = ns + "val";
string nextStyleId = "";
XElement nextStyle = xmlStyle.Descendants(ns + "next").FirstOrDefault();
if (nextStyle != null)
{
nextStyleId = nextStyle.Attribute(val).Value;
// Changes the name of the attribute, because the new added style is being renamed
nextStyle.Attribute(val).Value = nextStyle.Attribute(val).Value + newStyleNameSuffix;
}
return nextStyleId;
}
///
/// Gets the name of the style tagged as 'basedOn' associated to a given style
///
/// Xml to search for basedOn style
/// Name of the style
private static string GetBasedOnStyleId(XElement xmlStyle)
{
XName val = ns + "val";
string basedOnStyleId = "";
XElement basedOnStyle = xmlStyle.Descendants(ns + "basedOn").FirstOrDefault();
if (basedOnStyle != null)
{
basedOnStyleId = basedOnStyle.Attribute(val).Value;
// Change the name of the attribute, because the new added style is being renamed
basedOnStyle.Attribute(val).Value = basedOnStyle.Attribute(val).Value + newStyleNameSuffix;
}
return basedOnStyleId;
}
///
/// Sets a new styles part inside the document
///
/// Path of styles definition file
public static OpenXmlPowerToolsDocument SetStylePart(WmlDocument doc, XDocument newStylesDocument)
{
using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
{
using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
{
// Replaces XDocument with the style file to transfer
if (document.MainDocumentPart.StyleDefinitionsPart == null)
document.MainDocumentPart.AddNewPart();
document.MainDocumentPart.StyleDefinitionsPart.PutXDocument(newStylesDocument);
}
return streamDoc.GetModifiedDocument();
}
}
///
/// Adds a new style definition
///
/// Style definition
public static void AddStyleDefinition(WordprocessingDocument document, XElement xmlStyleDefinition)
{
// Inserts the new style
XDocument stylesPart = GetStylesDocument(document);
stylesPart.Root.Add(xmlStyleDefinition);
}
///
/// Adds a set of styles in the styles.xml file
///
/// Collection of style definitions
public static void AddStyleDefinition(WordprocessingDocument document, IEnumerable xmlStyleDefinitions)
{
XDocument stylesPart = GetStylesDocument(document);
foreach (XElement xmlStyleDefinition in xmlStyleDefinitions)
{
stylesPart.Root.Add(xmlStyleDefinition);
}
}
///
/// Insert a style into a given xmlpath inside the document part
///
/// place where we are going to put the style
/// name of the style
/// XDocument containing styles
public static OpenXmlPowerToolsDocument Insert(WmlDocument doc, string xpathInsertionPoint, string styleValue, XDocument stylesSource)
{
using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
{
using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
{
XDocument xDocument = document.MainDocumentPart.GetXDocument();
XmlDocument xmlMainDocument = new XmlDocument();
xmlMainDocument.Load(xDocument.CreateReader());
// create the style element to add in the document, based upon the style name.
// this is an example of an style element
//
// so, in order to construct this, we have to know already if the style will be placed inside
// a run or inside a paragraph. to know this we have to verify against the xpath, and know if
// the query want to access a 'run' or a paragraph
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xmlMainDocument.NameTable);
namespaceManager.AddNamespace("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
XmlNodeList insertionPoints = xmlMainDocument.SelectNodes(xpathInsertionPoint, namespaceManager);
if (insertionPoints.Count == 0)
throw new ArgumentException("The xpath query did not return a valid location.");
foreach (XmlNode insertionPoint in insertionPoints)
{
XmlElement xmlStyle = null;
if (insertionPoint.LocalName == "r" || insertionPoint.LocalName == "p")
{
XmlNode propertiesElement = insertionPoint.SelectSingleNode(@"w:pPr|w:rPr", namespaceManager);
//if (propertiesElement != null)
//{
if (insertionPoint.Name == "w:p")
{
xmlStyle = xmlMainDocument.CreateElement("w", "pStyle", namespaceManager.LookupNamespace("w"));
// retrieve the suffix from the styleAccesor class
xmlStyle.SetAttribute("val", namespaceManager.LookupNamespace("w"), styleValue + newStyleNameSuffix);
// check if the rPr or pPr element exist, if so, then add the style xml element
// inside, if not, then add a new rPr or pPr element
if (propertiesElement != null)
{
// check if there is already a style node and remove it
XmlNodeList xmlStyleList = propertiesElement.SelectNodes("w:pStyle", namespaceManager);
for (int i = 0; i < xmlStyleList.Count; i++)
{
propertiesElement.RemoveChild(xmlStyleList[i]);
}
propertiesElement.PrependChild(xmlStyle);
}
else
{
propertiesElement = xmlMainDocument.CreateElement("w", "pPr", namespaceManager.LookupNamespace("w"));
propertiesElement.PrependChild(xmlStyle);
insertionPoint.PrependChild(propertiesElement);
}
}
if (insertionPoint.Name == "w:r")
{
xmlStyle = xmlMainDocument.CreateElement("w", "rStyle", namespaceManager.LookupNamespace("w"));
xmlStyle.SetAttribute("val", namespaceManager.LookupNamespace("w"), styleValue + newStyleNameSuffix);
if (propertiesElement != null)
{
// check if there is already a style node and remove it
XmlNodeList xmlStyleList = propertiesElement.SelectNodes("w:rStyle", namespaceManager);
for (int i = 0; i < xmlStyleList.Count; i++)
{
propertiesElement.RemoveChild(xmlStyleList[i]);
}
propertiesElement.PrependChild(xmlStyle);
}
else
{
propertiesElement = xmlMainDocument.CreateElement("w", "rPr", namespaceManager.LookupNamespace("w"));
propertiesElement.PrependChild(xmlStyle);
insertionPoint.PrependChild(propertiesElement);
}
}
//}
}
else
{
throw new ArgumentException("The xpath query did not return a valid location.");
}
XDocument xNewDocument = new XDocument();
using (XmlWriter xWriter = xNewDocument.CreateWriter())
xmlMainDocument.WriteTo(xWriter);
document.MainDocumentPart.PutXDocument(xNewDocument);
// the style has been added in the main document part, but now we have to add the
// style definition in the styles definitions part. the style definition need to be
// extracted from the given inputStyle file.
// Because a style can be linked with other styles and
// can also be based on other styles, all the complete hierarchy of styles has
// to be added
Collection styleHierarchy = StyleAccessor.GetStyleHierarchy(styleValue, stylesSource, newStyleNameSuffix);
// open the styles file in the document
XDocument xmlStylesDefinitionDocument = StyleAccessor.GetStylesDocument(document);
XDocument xElem = new XDocument();
xElem.Add(xmlStylesDefinitionDocument.Root);
//insert the new style
foreach (XElement xmlStyleDefinition in styleHierarchy)
{
xElem.Root.Add(xmlStyleDefinition);
}
document.MainDocumentPart.StyleDefinitionsPart.PutXDocument(xElem);
}
}
return streamDoc.GetModifiedDocument();
}
}
}
}