| /******************************************************************************* | |
| * You may amend and distribute as you like, but don't remove this header! | |
| * | |
| * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. | |
| * See http://www.codeplex.com/EPPlus for details. | |
| * | |
| * Copyright (C) 2011 Jan K�llman | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
| * See the GNU Lesser General Public License for more details. | |
| * | |
| * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php | |
| * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html | |
| * | |
| * 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 | |
| * ****************************************************************************** | |
| * Jan K�llman Initial Release 2009-10-01 | |
| * Jan K�llman Total rewrite 2010-03-01 | |
| * Jan K�llman License changed GPL-->LGPL 2011-12-27 | |
| * Raziq York Added Created & Modified 2014-08-20 | |
| *******************************************************************************/ | |
| using System; | |
| using System.Globalization; | |
| using System.IO; | |
| using System.Xml; | |
| using OfficeOpenXml.Packaging; | |
| using OfficeOpenXml.Utils; | |
| namespace OfficeOpenXml; | |
| /// <summary> | |
| /// Provides access to the properties bag of the package | |
| /// </summary> | |
| public sealed class OfficeProperties : XmlHelper { | |
| private XmlDocument _xmlPropertiesCore; | |
| private XmlDocument _xmlPropertiesExtended; | |
| private XmlDocument _xmlPropertiesCustom; | |
| private readonly Uri _uriPropertiesCore = new("/docProps/core.xml", UriKind.Relative); | |
| private readonly Uri _uriPropertiesExtended = new("/docProps/app.xml", UriKind.Relative); | |
| private readonly Uri _uriPropertiesCustom = new("/docProps/custom.xml", UriKind.Relative); | |
| private readonly XmlHelper _coreHelper; | |
| private readonly XmlHelper _extendedHelper; | |
| private XmlHelper _customHelper; | |
| private readonly ExcelPackage _package; | |
| /// <summary> | |
| /// Provides access to all the office document properties. | |
| /// </summary> | |
| /// <param name="package"></param> | |
| /// <param name="ns"></param> | |
| internal OfficeProperties(ExcelPackage package, XmlNamespaceManager ns) | |
| : base(ns) { | |
| _package = package; | |
| _coreHelper = XmlHelperFactory.Create( | |
| ns, | |
| CorePropertiesXml.SelectSingleNode("cp:coreProperties", NameSpaceManager)); | |
| _extendedHelper = XmlHelperFactory.Create(ns, ExtendedPropertiesXml); | |
| _customHelper = XmlHelperFactory.Create(ns, CustomPropertiesXml); | |
| } | |
| /// <summary> | |
| /// Provides access to the XML document that holds all the code | |
| /// document properties. | |
| /// </summary> | |
| public XmlDocument CorePropertiesXml { | |
| get { | |
| if (_xmlPropertiesCore == null) { | |
| string xml = string.Format( | |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><cp:coreProperties xmlns:cp=\"{0}\" xmlns:dc=\"{1}\" xmlns:dcterms=\"{2}\" xmlns:dcmitype=\"{3}\" xmlns:xsi=\"{4}\"></cp:coreProperties>", | |
| ExcelPackage._schemaCore, | |
| ExcelPackage._schemaDc, | |
| ExcelPackage._schemaDcTerms, | |
| ExcelPackage._schemaDcmiType, | |
| ExcelPackage._schemaXsi); | |
| _xmlPropertiesCore = GetXmlDocument( | |
| xml, | |
| _uriPropertiesCore, | |
| "application/vnd.openxmlformats-package.core-properties+xml", | |
| "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"); | |
| } | |
| return (_xmlPropertiesCore); | |
| } | |
| } | |
| private XmlDocument GetXmlDocument( | |
| string startXml, | |
| Uri uri, | |
| string contentType, | |
| string relationship) { | |
| XmlDocument xmlDoc; | |
| if (_package.Package.PartExists(uri)) { | |
| xmlDoc = _package.GetXmlFromUri(uri); | |
| } else { | |
| xmlDoc = new(); | |
| xmlDoc.LoadXml(startXml); | |
| // Create a the part and add to the package | |
| ZipPackagePart part = _package.Package.CreatePart(uri, contentType); | |
| // Save it to the package | |
| StreamWriter stream = new StreamWriter(part.GetStream(FileMode.Create, FileAccess.Write)); | |
| xmlDoc.Save(stream); | |
| //stream.Close(); | |
| // create the relationship between the workbook and the new shared strings part | |
| _package.Package.CreateRelationship( | |
| UriHelper.GetRelativeUri(new("/xl", UriKind.Relative), uri), | |
| TargetMode.Internal, | |
| relationship); | |
| } | |
| return xmlDoc; | |
| } | |
| private const string _titlePath = "dc:title"; | |
| /// <summary> | |
| /// Gets/sets the title property of the document (core property) | |
| /// </summary> | |
| public string Title { | |
| get => _coreHelper.GetXmlNodeString(_titlePath); | |
| set => _coreHelper.SetXmlNodeString(_titlePath, value); | |
| } | |
| private const string _subjectPath = "dc:subject"; | |
| /// <summary> | |
| /// Gets/sets the subject property of the document (core property) | |
| /// </summary> | |
| public string Subject { | |
| get => _coreHelper.GetXmlNodeString(_subjectPath); | |
| set => _coreHelper.SetXmlNodeString(_subjectPath, value); | |
| } | |
| private const string _authorPath = "dc:creator"; | |
| /// <summary> | |
| /// Gets/sets the author property of the document (core property) | |
| /// </summary> | |
| public string Author { | |
| get => _coreHelper.GetXmlNodeString(_authorPath); | |
| set => _coreHelper.SetXmlNodeString(_authorPath, value); | |
| } | |
| private const string _commentsPath = "dc:description"; | |
| /// <summary> | |
| /// Gets/sets the comments property of the document (core property) | |
| /// </summary> | |
| public string Comments { | |
| get => _coreHelper.GetXmlNodeString(_commentsPath); | |
| set => _coreHelper.SetXmlNodeString(_commentsPath, value); | |
| } | |
| private const string _keywordsPath = "cp:keywords"; | |
| /// <summary> | |
| /// Gets/sets the keywords property of the document (core property) | |
| /// </summary> | |
| public string Keywords { | |
| get => _coreHelper.GetXmlNodeString(_keywordsPath); | |
| set => _coreHelper.SetXmlNodeString(_keywordsPath, value); | |
| } | |
| private const string _lastModifiedByPath = "cp:lastModifiedBy"; | |
| /// <summary> | |
| /// Gets/sets the lastModifiedBy property of the document (core property) | |
| /// </summary> | |
| public string LastModifiedBy { | |
| get => _coreHelper.GetXmlNodeString(_lastModifiedByPath); | |
| set => _coreHelper.SetXmlNodeString(_lastModifiedByPath, value); | |
| } | |
| private const string _lastPrintedPath = "cp:lastPrinted"; | |
| /// <summary> | |
| /// Gets/sets the lastPrinted property of the document (core property) | |
| /// </summary> | |
| public string LastPrinted { | |
| get => _coreHelper.GetXmlNodeString(_lastPrintedPath); | |
| set => _coreHelper.SetXmlNodeString(_lastPrintedPath, value); | |
| } | |
| private const string _createdPath = "dcterms:created"; | |
| /// <summary> | |
| /// Gets/sets the created property of the document (core property) | |
| /// </summary> | |
| public DateTime Created { | |
| get => | |
| DateTime.TryParse(_coreHelper.GetXmlNodeString(_createdPath), out var date) | |
| ? date | |
| : DateTime.MinValue; | |
| set { | |
| var dateString = value.ToUniversalTime().ToString("s", CultureInfo.InvariantCulture) + "Z"; | |
| _coreHelper.SetXmlNodeString(_createdPath, dateString); | |
| _coreHelper.SetXmlNodeString(_createdPath + "/@xsi:type", "dcterms:W3CDTF"); | |
| } | |
| } | |
| private const string _categoryPath = "cp:category"; | |
| /// <summary> | |
| /// Gets/sets the category property of the document (core property) | |
| /// </summary> | |
| public string Category { | |
| get => _coreHelper.GetXmlNodeString(_categoryPath); | |
| set => _coreHelper.SetXmlNodeString(_categoryPath, value); | |
| } | |
| private const string _contentStatusPath = "cp:contentStatus"; | |
| /// <summary> | |
| /// Gets/sets the status property of the document (core property) | |
| /// </summary> | |
| public string Status { | |
| get => _coreHelper.GetXmlNodeString(_contentStatusPath); | |
| set => _coreHelper.SetXmlNodeString(_contentStatusPath, value); | |
| } | |
| /// <summary> | |
| /// Provides access to the XML document that holds the extended properties of the document (app.xml) | |
| /// </summary> | |
| public XmlDocument ExtendedPropertiesXml { | |
| get { | |
| if (_xmlPropertiesExtended == null) { | |
| _xmlPropertiesExtended = GetXmlDocument( | |
| string.Format( | |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><Properties xmlns:vt=\"{0}\" xmlns=\"{1}\"></Properties>", | |
| ExcelPackage._schemaVt, | |
| ExcelPackage._schemaExtended), | |
| _uriPropertiesExtended, | |
| "application/vnd.openxmlformats-officedocument.extended-properties+xml", | |
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"); | |
| } | |
| return (_xmlPropertiesExtended); | |
| } | |
| } | |
| private const string _applicationPath = "xp:Properties/xp:Application"; | |
| /// <summary> | |
| /// Gets/Set the Application property of the document (extended property) | |
| /// </summary> | |
| public string Application { | |
| get => _extendedHelper.GetXmlNodeString(_applicationPath); | |
| set => _extendedHelper.SetXmlNodeString(_applicationPath, value); | |
| } | |
| private const string _hyperlinkBasePath = "xp:Properties/xp:HyperlinkBase"; | |
| /// <summary> | |
| /// Gets/sets the HyperlinkBase property of the document (extended property) | |
| /// </summary> | |
| public Uri HyperlinkBase { | |
| get => new(_extendedHelper.GetXmlNodeString(_hyperlinkBasePath), UriKind.Absolute); | |
| set => _extendedHelper.SetXmlNodeString(_hyperlinkBasePath, value.AbsoluteUri); | |
| } | |
| private const string _appVersionPath = "xp:Properties/xp:AppVersion"; | |
| /// <summary> | |
| /// Gets/Set the AppVersion property of the document (extended property) | |
| /// </summary> | |
| public string AppVersion { | |
| get => _extendedHelper.GetXmlNodeString(_appVersionPath); | |
| set => _extendedHelper.SetXmlNodeString(_appVersionPath, value); | |
| } | |
| private const string _companyPath = "xp:Properties/xp:Company"; | |
| /// <summary> | |
| /// Gets/sets the Company property of the document (extended property) | |
| /// </summary> | |
| public string Company { | |
| get => _extendedHelper.GetXmlNodeString(_companyPath); | |
| set => _extendedHelper.SetXmlNodeString(_companyPath, value); | |
| } | |
| private const string _managerPath = "xp:Properties/xp:Manager"; | |
| /// <summary> | |
| /// Gets/sets the Manager property of the document (extended property) | |
| /// </summary> | |
| public string Manager { | |
| get => _extendedHelper.GetXmlNodeString(_managerPath); | |
| set => _extendedHelper.SetXmlNodeString(_managerPath, value); | |
| } | |
| private const string _modifiedPath = "dcterms:modified"; | |
| /// <summary> | |
| /// Gets/sets the modified property of the document (core property) | |
| /// </summary> | |
| public DateTime Modified { | |
| get => | |
| DateTime.TryParse(_coreHelper.GetXmlNodeString(_modifiedPath), out var date) | |
| ? date | |
| : DateTime.MinValue; | |
| set { | |
| var dateString = value.ToUniversalTime().ToString("s", CultureInfo.InvariantCulture) + "Z"; | |
| _coreHelper.SetXmlNodeString(_modifiedPath, dateString); | |
| _coreHelper.SetXmlNodeString(_modifiedPath + "/@xsi:type", "dcterms:W3CDTF"); | |
| } | |
| } | |
| /// <summary> | |
| /// Provides access to the XML document which holds the document's custom properties | |
| /// </summary> | |
| public XmlDocument CustomPropertiesXml { | |
| get { | |
| if (_xmlPropertiesCustom == null) { | |
| _xmlPropertiesCustom = GetXmlDocument( | |
| string.Format( | |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><Properties xmlns:vt=\"{0}\" xmlns=\"{1}\"></Properties>", | |
| ExcelPackage._schemaVt, | |
| ExcelPackage._schemaCustom), | |
| _uriPropertiesCustom, | |
| "application/vnd.openxmlformats-officedocument.custom-properties+xml", | |
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"); | |
| } | |
| return (_xmlPropertiesCustom); | |
| } | |
| } | |
| /// <summary> | |
| /// Gets the value of a custom property | |
| /// </summary> | |
| /// <param name="propertyName">The name of the property</param> | |
| /// <returns>The current value of the property</returns> | |
| public object GetCustomPropertyValue(string propertyName) { | |
| string searchString = string.Format("ctp:Properties/ctp:property[@name='{0}']", propertyName); | |
| XmlElement node = | |
| CustomPropertiesXml.SelectSingleNode(searchString, NameSpaceManager) as XmlElement; | |
| if (node != null) { | |
| string value = node.LastChild.InnerText; | |
| switch (node.LastChild.LocalName) { | |
| case "filetime": | |
| if (DateTime.TryParse(value, out var dt)) { | |
| return dt; | |
| } | |
| return null; | |
| case "i4": | |
| if (int.TryParse(value, out var i)) { | |
| return i; | |
| } | |
| return null; | |
| case "r8": | |
| if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var d)) { | |
| return d; | |
| } | |
| return null; | |
| case "bool": | |
| if (value == "true") { | |
| return true; | |
| } | |
| if (value == "false") { | |
| return false; | |
| } | |
| return null; | |
| default: | |
| return value; | |
| } | |
| } | |
| return null; | |
| } | |
| /// <summary> | |
| /// Allows you to set the value of a current custom property or create your own custom property. | |
| /// </summary> | |
| /// <param name="propertyName">The name of the property</param> | |
| /// <param name="value">The value of the property</param> | |
| public void SetCustomPropertyValue(string propertyName, object value) { | |
| XmlNode allProps = CustomPropertiesXml.SelectSingleNode("ctp:Properties", NameSpaceManager); | |
| var prop = string.Format("ctp:Properties/ctp:property[@name='{0}']", propertyName); | |
| XmlElement node = CustomPropertiesXml.SelectSingleNode(prop, NameSpaceManager) as XmlElement; | |
| if (node == null) { | |
| int pid; | |
| var maxNode = CustomPropertiesXml.SelectSingleNode( | |
| "ctp:Properties/ctp:property[not(@pid <= preceding-sibling::ctp:property/@pid) and not(@pid <= following-sibling::ctp:property/@pid)]", | |
| NameSpaceManager); | |
| if (maxNode == null) { | |
| pid = 2; | |
| } else { | |
| if (!int.TryParse(maxNode.Attributes["pid"].Value, out pid)) { | |
| pid = 2; | |
| } | |
| pid++; | |
| } | |
| node = CustomPropertiesXml.CreateElement("property", ExcelPackage._schemaCustom); | |
| node.SetAttribute("fmtid", "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"); | |
| node.SetAttribute("pid", pid.ToString()); // custom property pid | |
| node.SetAttribute("name", propertyName); | |
| allProps.AppendChild(node); | |
| } else { | |
| while (node.ChildNodes.Count > 0) { | |
| node.RemoveChild(node.ChildNodes[0]); | |
| } | |
| } | |
| XmlElement valueElem; | |
| if (value is bool) { | |
| valueElem = CustomPropertiesXml.CreateElement("vt", "bool", ExcelPackage._schemaVt); | |
| valueElem.InnerText = value.ToString().ToLower(CultureInfo.InvariantCulture); | |
| } else if (value is DateTime time) { | |
| valueElem = CustomPropertiesXml.CreateElement("vt", "filetime", ExcelPackage._schemaVt); | |
| valueElem.InnerText = time.AddHours(-1).ToString("yyyy-MM-ddTHH:mm:ssZ"); | |
| } else if (value is short || value is int) { | |
| valueElem = CustomPropertiesXml.CreateElement("vt", "i4", ExcelPackage._schemaVt); | |
| valueElem.InnerText = value.ToString(); | |
| } else if (value is double || value is decimal || value is float || value is long) { | |
| valueElem = CustomPropertiesXml.CreateElement("vt", "r8", ExcelPackage._schemaVt); | |
| if (value is double d) { | |
| valueElem.InnerText = d.ToString(CultureInfo.InvariantCulture); | |
| } else if (value is float f) { | |
| valueElem.InnerText = f.ToString(CultureInfo.InvariantCulture); | |
| } else if (value is decimal value1) { | |
| valueElem.InnerText = value1.ToString(CultureInfo.InvariantCulture); | |
| } else { | |
| valueElem.InnerText = value.ToString(); | |
| } | |
| } else { | |
| valueElem = CustomPropertiesXml.CreateElement("vt", "lpwstr", ExcelPackage._schemaVt); | |
| valueElem.InnerText = value.ToString(); | |
| } | |
| node.AppendChild(valueElem); | |
| } | |
| /// <summary> | |
| /// Saves the document properties back to the package. | |
| /// </summary> | |
| internal void Save() { | |
| if (_xmlPropertiesCore != null) { | |
| _package.SavePart(_uriPropertiesCore, _xmlPropertiesCore); | |
| } | |
| if (_xmlPropertiesExtended != null) { | |
| _package.SavePart(_uriPropertiesExtended, _xmlPropertiesExtended); | |
| } | |
| if (_xmlPropertiesCustom != null) { | |
| _package.SavePart(_uriPropertiesCustom, _xmlPropertiesCustom); | |
| } | |
| } | |
| } |