/* * You may amend and distribute as you like, but don't remove this header! * * ExcelPackage provides server-side generation of Excel 2007 spreadsheets. * See http://www.codeplex.com/ExcelPackage for details. * * Copyright 2007 © Dr John Tunnicliffe * mailto:dr.john.tunnicliffe@btinternet.com * All rights reserved. * * ExcelPackage is an Open Source project provided under the * GNU General Public License (GPL) as published by the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * The GNU General Public License can be viewed at http://www.opensource.org/licenses/gpl-license.php * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html * * The code for this project may be used and redistributed by any means PROVIDING it is * not sold for profit without the author's written consent, and providing that this notice * and the author's name and all copyright notices remain intact. * * All code and executables are provided "as is" with no warranty either express or implied. * The author accepts no liability for any damage or loss of business that this product may cause. */ /* * Code change notes: * * Author Change Date * ****************************************************************************** * John Tunnicliffe Initial Release 01-Jan-2007 * ****************************************************************************** */ using System; using System.IO; using System.IO.Packaging; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; namespace OfficeOpenXml.Core.ExcelPackage { /// /// Provides access to the properties bag of any office document (i.e. Word, Excel etc.) /// public class OfficeProperties { #region Private Properties private const string schemaCore = @"http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; private const string schemeExtended = @"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"; private const string schemaCustom = @"http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"; private const string schemaDc = @"http://purl.org/dc/elements/1.1/"; private const string schemaDcTerms = @"http://purl.org/dc/terms/"; private const string schemaDcmiType = @"http://purl.org/dc/dcmitype/"; private const string schemaXsi = @"http://www.w3.org/2001/XMLSchema-instance"; private const string schemaVt = @"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"; private Uri _uriPropertiesCore = new Uri("/docProps/core.xml", UriKind.Relative); private Uri _uriPropertiesExtended = new Uri("/docProps/app.xml", UriKind.Relative); private Uri _uriPropertiesCustom = new Uri("/docProps/custom.xml", UriKind.Relative); private XDocument _xmlPropertiesCore; private XDocument _xmlPropertiesExtended; private XDocument _xmlPropertiesCustom; private ExcelPackage _xlPackage; private XmlNamespaceManager _nsManager; #endregion #region ExcelProperties Constructor /// /// Provides access to all the office document properties. /// /// public OfficeProperties(ExcelPackage xlPackage) { _xlPackage = xlPackage; // Create a NamespaceManager to handle the default namespace, // and create a prefix for the default namespace: NameTable nt = new NameTable(); _nsManager = new XmlNamespaceManager(nt); // default namespace _nsManager.AddNamespace("d", ExcelPackage.schemaMain.NamespaceName); _nsManager.AddNamespace("vt", schemaVt); // extended properties (app.xml) _nsManager.AddNamespace("xp", schemeExtended); // custom properties _nsManager.AddNamespace("ctp", schemaCustom); // core properties _nsManager.AddNamespace("cp", schemaCore); // core property namespaces _nsManager.AddNamespace("dc", schemaDc); _nsManager.AddNamespace("dcterms", schemaDcTerms); _nsManager.AddNamespace("dcmitype", schemaDcmiType); _nsManager.AddNamespace("xsi", schemaXsi); } #endregion #region Protected Internal Properties /// /// The URI to the core properties component (core.xml) /// protected internal Uri CorePropertiesUri { get { return (_uriPropertiesCore); } } /// /// The URI to the extended properties component (app.xml) /// protected internal Uri ExtendedPropertiesUri { get { return (_uriPropertiesExtended); } } /// /// The URI to the custom properties component (custom.xml) /// protected internal Uri CustomPropertiesUri { get { return (_uriPropertiesCustom); } } #endregion #region Core Properties #region CorePropertiesXml /// /// Provides access to the XML document that holds all the code /// document properties. /// public XDocument CorePropertiesXml { get { if (_xmlPropertiesCore == null) { if (_xlPackage.Package.PartExists(CorePropertiesUri)) _xmlPropertiesCore = _xlPackage.GetXmlFromUri(CorePropertiesUri); else { // create a new document properties part and add to the package PackagePart partCore = _xlPackage.Package.CreatePart(CorePropertiesUri, @"application/vnd.openxmlformats-package.core-properties+xml", CompressionOption.Normal); // create the document properties XML (with no entries in it) _xmlPropertiesCore = new XDocument( new XElement("cp:coreProperties") .AddSchemaAttribute(schemaCore, "cp") .AddSchemaAttribute(schemaDc, "dc") .AddSchemaAttribute(schemaDcTerms, "dcterms") .AddSchemaAttribute(schemaDcmiType, "dcmitype") .AddSchemaAttribute(schemaXsi, "xsi") ); // save it to the package StreamWriter streamCore = new StreamWriter(partCore.GetStream(FileMode.Create, FileAccess.Write)); _xmlPropertiesCore.Save(streamCore); streamCore.Dispose(); _xlPackage.Package.Flush(); // create the relationship between the workbook and the new shared strings part _xlPackage.Package.CreateRelationship(CorePropertiesUri, TargetMode.Internal, @"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"); _xlPackage.Package.Flush(); } } return (_xmlPropertiesCore); } } #endregion /// /// Gets/sets the title property of the document (core property) /// public string Title { get { return GetCorePropertyValue("dc", "title"); } set { SetCorePropertyValue("dc", "title", value); } } /// /// Gets/sets the subject property of the document (core property) /// public string Subject { get { return GetCorePropertyValue("dc", "subject"); } set { SetCorePropertyValue("dc", "subject", value); } } /// /// Gets/sets the author property of the document (core property) /// public string Author { get { return GetCorePropertyValue("dc", "creator"); } set { SetCorePropertyValue("dc", "creator", value); } } /// /// Gets/sets the comments property of the document (core property) /// public string Comments { get { return GetCorePropertyValue("dc", "description"); } set { SetCorePropertyValue("dc", "description", value); } } /// /// Gets/sets the keywords property of the document (core property) /// public string Keywords { get { return GetCorePropertyValue("cp", "keywords"); } set { SetCorePropertyValue("cp", "keywords", value); } } /// /// Gets/sets the lastModifiedBy property of the document (core property) /// public string LastModifiedBy { get { return GetCorePropertyValue("cp", "lastModifiedBy"); } set { SetCorePropertyValue("cp", "lastModifiedBy", value); } } /// /// Gets/sets the lastPrinted property of the document (core property) /// public string LastPrinted { get { return GetCorePropertyValue("cp", "lastPrinted"); } set { SetCorePropertyValue("cp", "lastPrinted", value); } } /// /// Gets/sets the category property of the document (core property) /// public string Category { get { return GetCorePropertyValue("cp", "category"); } set { SetCorePropertyValue("cp", "category", value); } } /// /// Gets/sets the status property of the document (core property) /// public string Status { get { return GetCorePropertyValue("cp", "contentStatus"); } set { SetCorePropertyValue("cp", "contentStatus", value); } } #region Get and Set Core Properties /// /// Gets the value of a core property /// Private method, for internal use only! /// /// The namespace of the property /// The property name /// The current value of the property private string GetCorePropertyValue(string nameSpace, string propertyName) { string retValue = null; string searchString = string.Format("//cp:coreProperties/{0}:{1}", nameSpace, propertyName); var node = CorePropertiesXml.XPathSelectElement(searchString, _nsManager); if (node != null) { retValue = node.Value; } return retValue; } /// /// Sets a core property value. /// Private method, for internal use only! /// /// The property's namespace /// The name of the property /// The value of the property private void SetCorePropertyValue(string nameSpace, string propertyName, string propValue) { string searchString = string.Format("//cp:coreProperties/{0}:{1}", nameSpace, propertyName); var node = CorePropertiesXml.XPathSelectElement(searchString, _nsManager); if (node == null) { // the property does not exist, so create the XML node string schema = schemaCore; switch (nameSpace) { case "cp": schema = schemaCore; break; case "dc": schema = schemaDc; break; case "dcterms": schema = schemaDcTerms; break; case "dcmitype": schema = schemaDcmiType; break; case "xsi": schema = schemaXsi; break; } node = new XElement(propertyName) .AddSchemaAttribute(schema, nameSpace); CorePropertiesXml.Document.Add(node); } node.Value = propValue; } #endregion #endregion #region Extended Properties #region ExtendedPropertiesXml /// /// Provides access to the XML document that holds the extended properties of the document (app.xml) /// public XDocument ExtendedPropertiesXml { get { if (_xmlPropertiesExtended == null) { if (_xlPackage.Package.PartExists(ExtendedPropertiesUri)) _xmlPropertiesExtended = _xlPackage.GetXmlFromUri(ExtendedPropertiesUri); else { // create a new extended properties part and add to the package PackagePart partExtended = _xlPackage.Package.CreatePart(ExtendedPropertiesUri, @"application/vnd.openxmlformats-officedocument.extended-properties+xml", CompressionOption.Normal); // create the extended properties XML (with no entries in it) _xmlPropertiesExtended = new XDocument( new XElement("Properties") .AddSchemaAttribute(XNamespace.Xmlns, schemeExtended) .AddSchemaAttribute(schemaVt, "vt")); // save it to the package StreamWriter streamExtended = new StreamWriter(partExtended.GetStream(FileMode.Create, FileAccess.Write)); _xmlPropertiesExtended.Save(streamExtended); streamExtended.Dispose(); _xlPackage.Package.Flush(); // create the relationship between the workbook and the new shared strings part _xlPackage.Package.CreateRelationship(ExtendedPropertiesUri, TargetMode.Internal, @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"); _xlPackage.Package.Flush(); } } return (_xmlPropertiesExtended); } } #endregion /// /// Gets the Application property of the document (extended property) /// public string Application { get { return GetExtendedPropertyValue("Application"); } } /// /// Gets/sets the HyperlinkBase property of the document (extended property) /// public Uri HyperlinkBase { get { return new Uri(GetExtendedPropertyValue("HyperlinkBase")); } set { SetExtendedPropertyValue("HyperlinkBase", value.ToString()); } } /// /// Gets the AppVersion property of the document (extended property) /// public string AppVersion { get { return GetExtendedPropertyValue("AppVersion"); } } /// /// Gets/sets the Company property of the document (extended property) /// public string Company { get { return GetExtendedPropertyValue("Company"); } set { SetExtendedPropertyValue("Company", value); } } /// /// Gets/sets the Manager property of the document (extended property) /// public string Manager { get { return GetExtendedPropertyValue("Manager"); } set { SetExtendedPropertyValue("Manager", value); } } #region Get and Set Extended Properties private string GetExtendedPropertyValue(string propertyName) { string retValue = null; string searchString = string.Format("//xp:Properties/xp:{0}", propertyName); var node = ExtendedPropertiesXml.XPathSelectElement(searchString, _nsManager); if (node != null) { retValue = node.Value; } return retValue; } private void SetExtendedPropertyValue(string propertyName, string propValue) { string searchString = string.Format("//xp:Properties/xp:{0}", propertyName); var node = ExtendedPropertiesXml.XPathSelectElement(searchString, _nsManager); if (node == null) { // the property does not exist, so create the XML node node = new XElement(propertyName) .AddSchemaAttribute(XNamespace.Xmlns, schemeExtended); ExtendedPropertiesXml.Document.Add(node); } node.Value = propValue; } #endregion #endregion #region Custom Properties #region CustomPropertiesXml /// /// Provides access to the XML document which holds the document's custom properties /// public XDocument CustomPropertiesXml { get { if (_xmlPropertiesCustom == null) { if (_xlPackage.Package.PartExists(CustomPropertiesUri)) _xmlPropertiesCustom = _xlPackage.GetXmlFromUri(CustomPropertiesUri); else { // create a new extended properties part and add to the package PackagePart partCustom = _xlPackage.Package.CreatePart(CustomPropertiesUri, @"application/vnd.openxmlformats-officedocument.custom-properties+xml", CompressionOption.Normal); // create the extended properties XML (with no entries in it) _xmlPropertiesCustom = new XDocument( new XElement("Properties") .AddSchemaAttribute(XNamespace.Xmlns, schemaCustom) .AddSchemaAttribute(schemaVt, "vt")); // save it to the package StreamWriter streamCustom = new StreamWriter(partCustom.GetStream(FileMode.Create, FileAccess.Write)); _xmlPropertiesCustom.Save(streamCustom); streamCustom.Dispose(); _xlPackage.Package.Flush(); // create the relationship between the workbook and the new shared strings part _xlPackage.Package.CreateRelationship(CustomPropertiesUri, TargetMode.Internal, @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"); _xlPackage.Package.Flush(); } } return (_xmlPropertiesCustom); } } #endregion #region Get and Set Custom Properties /// /// Gets the value of a custom property /// /// The name of the property /// The current value of the property public string GetCustomPropertyValue(string propertyName) { string retValue = null; string searchString = string.Format("//ctp:Properties/ctp:property/@name[.='{0}']", propertyName); var node = CustomPropertiesXml.XPathSelectElement(searchString, _nsManager); if (node != null) { retValue = (node.LastNode as XElement).Value; } return retValue; } /// /// Allows you to set the value of a current custom property or create /// your own custom property. /// Currently only supports string values. /// /// The name of the property /// The value of the property public void SetCustomPropertyValue(string propertyName, string propValue) { // TODO: provide support for other custom property data types string searchString = @"//ctp:Properties"; var allProps = CustomPropertiesXml.XPathSelectElement(searchString, _nsManager); searchString = string.Format("//ctp:Properties/ctp:property/@ctp:name[.='{0}']", propertyName); var node = CustomPropertiesXml.XPathSelectElement(searchString, _nsManager); if (node == null) { // the property does not exist, so first find the max PID int pid = 4; foreach (var prop in allProps.Nodes()) { var attr = ((XElement)prop).Attribute("pid"); if (attr != null) { int attrValue = int.Parse(attr.Value); if (attrValue > pid) pid = attrValue; } } pid++; // the property does not exist, so create the XML node var element = new XElement("property") .AddSchemaAttribute(XNamespace.Xmlns, schemaCustom); element.SetAttribute("fmtid", "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"); element.SetAttribute("pid", pid.ToString()); // custom property pid element.SetAttribute("name", propertyName); XElement valueElement = new XElement("vt").AddSchemaAttribute("lpwstr", schemaVt); valueElement.Value = propValue; element.Add(valueElement); CustomPropertiesXml.Document.Add(element); } else { (node.LastNode as XElement).Value = propValue; } } #endregion #endregion #region Save // OfficeProperties save /// /// Saves the office document properties back to the package (if they exist!). /// protected internal void Save() { if (_xmlPropertiesCore != null) { _xlPackage.WriteDebugFile(_xmlPropertiesCore, "docProps", "core.xml"); _xlPackage.SavePart(CorePropertiesUri, _xmlPropertiesCore); } if (_xmlPropertiesExtended != null) { _xlPackage.WriteDebugFile(_xmlPropertiesExtended, "docProps", "app.xml"); _xlPackage.SavePart(ExtendedPropertiesUri, _xmlPropertiesExtended); } if (_xmlPropertiesCustom != null) { _xlPackage.WriteDebugFile(_xmlPropertiesCustom, "docProps", "custom.xml"); _xlPackage.SavePart(CustomPropertiesUri, _xmlPropertiesCustom); } } #endregion } }