blob: 1b0a6bee2dd50c7642268c0d52b5b554b902a2ec [file] [log] [blame]
/*******************************************************************************
* 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);
}
}
}