| /******************************************************************************* | |
| * 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 | |
| * ****************************************************************************** | |
| * Eyal Seagull Added 2012-04-03 | |
| *******************************************************************************/ | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Text; | |
| using System.Drawing; | |
| using System.Xml; | |
| using OfficeOpenXml.Utils; | |
| using System.Text.RegularExpressions; | |
| using OfficeOpenXml.ConditionalFormatting.Contracts; | |
| using OfficeOpenXml.Style.Dxf; | |
| namespace OfficeOpenXml.ConditionalFormatting | |
| { | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| public abstract class ExcelConditionalFormattingRule | |
| : XmlHelper, | |
| IExcelConditionalFormattingRule | |
| { | |
| /****************************************************************************************/ | |
| #region Private Properties | |
| private eExcelConditionalFormattingRuleType? _type; | |
| private ExcelWorksheet _worksheet; | |
| /// <summary> | |
| /// Sinalize that we are in a Cnaging Priorities opeartion so that we won't enter | |
| /// a recursive loop. | |
| /// </summary> | |
| private static bool _changingPriority = false; | |
| #endregion Private Properties | |
| /****************************************************************************************/ | |
| #region Constructors | |
| /// <summary> | |
| /// Initialize the <see cref="ExcelConditionalFormattingRule"/> | |
| /// </summary> | |
| /// <param name="type"></param> | |
| /// <param name="address"></param> | |
| /// <param name="priority">Used also as the cfRule unique key</param> | |
| /// <param name="worksheet"></param> | |
| /// <param name="itemElementNode"></param> | |
| /// <param name="namespaceManager"></param> | |
| internal ExcelConditionalFormattingRule( | |
| eExcelConditionalFormattingRuleType type, | |
| ExcelAddress address, | |
| int priority, | |
| ExcelWorksheet worksheet, | |
| XmlNode itemElementNode, | |
| XmlNamespaceManager namespaceManager) | |
| : base( | |
| namespaceManager, | |
| itemElementNode) | |
| { | |
| Require.Argument(address).IsNotNull("address"); | |
| Require.Argument(priority).IsInRange(1, int.MaxValue, "priority"); | |
| Require.Argument(worksheet).IsNotNull("worksheet"); | |
| _type = type; | |
| _worksheet = worksheet; | |
| SchemaNodeOrder = _worksheet.SchemaNodeOrder; | |
| if (itemElementNode == null) | |
| { | |
| // Create/Get the <cfRule> inside <conditionalFormatting> | |
| itemElementNode = CreateComplexNode( | |
| _worksheet.WorksheetXml.DocumentElement, | |
| string.Format( | |
| "{0}[{1}='{2}']/{1}='{2}'/{3}[{4}='{5}']/{4}='{5}'", | |
| //{0} | |
| ExcelConditionalFormattingConstants.Paths.ConditionalFormatting, | |
| // {1} | |
| ExcelConditionalFormattingConstants.Paths.SqrefAttribute, | |
| // {2} | |
| address.AddressSpaceSeparated, //CF node don't what to have comma between multi addresses, use space instead. | |
| // {3} | |
| ExcelConditionalFormattingConstants.Paths.CfRule, | |
| //{4} | |
| ExcelConditionalFormattingConstants.Paths.PriorityAttribute, | |
| //{5} | |
| priority)); | |
| } | |
| // Point to <cfRule> | |
| TopNode = itemElementNode; | |
| Address = address; | |
| Priority = priority; | |
| Type = type; | |
| if (DxfId >= 0) | |
| { | |
| worksheet.Workbook.Styles.Dxfs[DxfId].AllowChange = true; //This Id is referenced by CF, so we can use it when we save. | |
| _style = worksheet.Workbook.Styles.Dxfs[DxfId].Clone(); //Clone, so it can be altered without effecting other dxf styles | |
| } | |
| } | |
| /// <summary> | |
| /// Initialize the <see cref="ExcelConditionalFormattingRule"/> | |
| /// </summary> | |
| /// <param name="type"></param> | |
| /// <param name="address"></param> | |
| /// <param name="priority"></param> | |
| /// <param name="worksheet"></param> | |
| /// <param name="namespaceManager"></param> | |
| internal ExcelConditionalFormattingRule( | |
| eExcelConditionalFormattingRuleType type, | |
| ExcelAddress address, | |
| int priority, | |
| ExcelWorksheet worksheet, | |
| XmlNamespaceManager namespaceManager) | |
| : this( | |
| type, | |
| address, | |
| priority, | |
| worksheet, | |
| null, | |
| namespaceManager) | |
| { | |
| } | |
| #endregion Constructors | |
| /****************************************************************************************/ | |
| #region Methods | |
| #endregion Methods | |
| /****************************************************************************************/ | |
| #region Exposed Properties | |
| /// <summary> | |
| /// Get the <cfRule> node | |
| /// </summary> | |
| public XmlNode Node | |
| { | |
| get { return TopNode; } | |
| } | |
| /// <summary> | |
| /// Address of the conditional formatting rule | |
| /// </summary> | |
| /// <remarks> | |
| /// The address is stores in a parent node called <conditionalFormatting> in the | |
| /// @sqref attribute. Excel groups rules that have the same address inside one node. | |
| /// </remarks> | |
| public ExcelAddress Address | |
| { | |
| get | |
| { | |
| return new ExcelAddress( | |
| Node.ParentNode.Attributes[ExcelConditionalFormattingConstants.Attributes.Sqref].Value); | |
| } | |
| set | |
| { | |
| // Check if the address is to be changed | |
| if (Address.Address != value.Address) | |
| { | |
| // Save the old parente node | |
| XmlNode oldNode = Node; | |
| XmlNode oldParentNode = Node.ParentNode; | |
| // Create/Get the new <conditionalFormatting> parent node | |
| XmlNode newParentNode = CreateComplexNode( | |
| _worksheet.WorksheetXml.DocumentElement, | |
| string.Format( | |
| "{0}[{1}='{2}']/{1}='{2}'", | |
| //{0} | |
| ExcelConditionalFormattingConstants.Paths.ConditionalFormatting, | |
| // {1} | |
| ExcelConditionalFormattingConstants.Paths.SqrefAttribute, | |
| // {2} | |
| value.AddressSpaceSeparated)); | |
| // Move the <cfRule> node to the new <conditionalFormatting> parent node | |
| TopNode = newParentNode.AppendChild(Node); | |
| // Check if the old <conditionalFormatting> parent node has <cfRule> node inside it | |
| if (!oldParentNode.HasChildNodes) | |
| { | |
| // Remove the old parent node | |
| oldParentNode.ParentNode.RemoveChild(oldParentNode); | |
| } | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Type of conditional formatting rule. ST_CfType §18.18.12. | |
| /// </summary> | |
| public eExcelConditionalFormattingRuleType Type | |
| { | |
| get | |
| { | |
| // Transform the @type attribute to EPPlus Rule Type (slighty diferente) | |
| if(_type==null) | |
| { | |
| _type = ExcelConditionalFormattingRuleType.GetTypeByAttrbiute( | |
| GetXmlNodeString(ExcelConditionalFormattingConstants.Paths.TypeAttribute), | |
| TopNode, | |
| _worksheet.NameSpaceManager); | |
| } | |
| return (eExcelConditionalFormattingRuleType)_type; | |
| } | |
| internal set | |
| { | |
| _type = value; | |
| // Transform the EPPlus Rule Type to @type attribute (slighty diferente) | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.TypeAttribute, | |
| ExcelConditionalFormattingRuleType.GetAttributeByType(value), | |
| true); | |
| } | |
| } | |
| /// <summary> | |
| /// The priority of this conditional formatting rule. This value is used to determine | |
| /// which format should be evaluated and rendered. Lower numeric values are higher | |
| /// priority than higher numeric values, where 1 is the highest priority. | |
| /// </summary> | |
| public int Priority | |
| { | |
| get | |
| { | |
| return GetXmlNodeInt( | |
| ExcelConditionalFormattingConstants.Paths.PriorityAttribute); | |
| } | |
| set | |
| { | |
| // Save the current CF rule priority | |
| int priority = Priority; | |
| // Check if the @priority is to be changed | |
| if (priority != value) | |
| { | |
| // Check if we are not already inside a "Change Priority" operation | |
| if (!_changingPriority) | |
| { | |
| if (value < 1) | |
| { | |
| throw new IndexOutOfRangeException( | |
| ExcelConditionalFormattingConstants.Errors.InvalidPriority); | |
| } | |
| // Sinalize that we are already changing cfRules priorities | |
| _changingPriority = true; | |
| // Check if we lowered the priority | |
| if (priority > value) | |
| { | |
| for (int i = priority - 1; i >= value; i--) | |
| { | |
| var cfRule = _worksheet.ConditionalFormatting.RulesByPriority(i); | |
| if (cfRule != null) | |
| { | |
| cfRule.Priority++; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| for (int i = priority + 1; i <= value; i++) | |
| { | |
| var cfRule = _worksheet.ConditionalFormatting.RulesByPriority(i); | |
| if (cfRule != null) | |
| { | |
| cfRule.Priority--; | |
| } | |
| } | |
| } | |
| // Sinalize that we are no longer changing cfRules priorities | |
| _changingPriority = false; | |
| } | |
| // Change the priority in the XML | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.PriorityAttribute, | |
| value.ToString(), | |
| true); | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// If this flag is true, no rules with lower priority shall be applied over this rule, | |
| /// when this rule evaluates to true. | |
| /// </summary> | |
| public bool StopIfTrue | |
| { | |
| get | |
| { | |
| return GetXmlNodeBool( | |
| ExcelConditionalFormattingConstants.Paths.StopIfTrueAttribute); | |
| } | |
| set | |
| { | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.StopIfTrueAttribute, | |
| (value == true) ? "1" : string.Empty, | |
| true); | |
| } | |
| } | |
| /// <summary> | |
| /// DxfId Style Attribute | |
| /// </summary> | |
| internal int DxfId | |
| { | |
| get | |
| { | |
| return GetXmlNodeInt( | |
| ExcelConditionalFormattingConstants.Paths.DxfIdAttribute); | |
| } | |
| set | |
| { | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.DxfIdAttribute, | |
| (value == int.MinValue) ? string.Empty : value.ToString(), | |
| true); | |
| } | |
| } | |
| internal ExcelDxfStyleConditionalFormatting _style = null; | |
| public ExcelDxfStyleConditionalFormatting Style | |
| { | |
| get | |
| { | |
| if (_style == null) | |
| { | |
| _style = new ExcelDxfStyleConditionalFormatting(NameSpaceManager, null, _worksheet.Workbook.Styles); | |
| } | |
| return _style; | |
| } | |
| } | |
| /// <summary> | |
| /// StdDev (zero is not allowed and will be converted to 1) | |
| /// </summary> | |
| public UInt16 StdDev | |
| { | |
| get | |
| { | |
| return Convert.ToUInt16(GetXmlNodeInt( | |
| ExcelConditionalFormattingConstants.Paths.StdDevAttribute)); | |
| } | |
| set | |
| { | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.StdDevAttribute, | |
| (value == 0) ? "1" : value.ToString(), | |
| true); | |
| } | |
| } | |
| /// <summary> | |
| /// Rank (zero is not allowed and will be converted to 1) | |
| /// </summary> | |
| public UInt16 Rank | |
| { | |
| get | |
| { | |
| return Convert.ToUInt16(GetXmlNodeInt( | |
| ExcelConditionalFormattingConstants.Paths.RankAttribute)); | |
| } | |
| set | |
| { | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.RankAttribute, | |
| (value == 0) ? "1" : value.ToString(), | |
| true); | |
| } | |
| } | |
| #endregion Exposed Properties | |
| /****************************************************************************************/ | |
| #region Internal Properties | |
| /// <summary> | |
| /// AboveAverage | |
| /// </summary> | |
| internal protected bool? AboveAverage | |
| { | |
| get | |
| { | |
| bool? aboveAverage = GetXmlNodeBoolNullable( | |
| ExcelConditionalFormattingConstants.Paths.AboveAverageAttribute); | |
| // Above Avarege if TRUE or if attribute does not exists | |
| return (aboveAverage == true) || (aboveAverage == null); | |
| } | |
| set | |
| { | |
| string aboveAverageValue = string.Empty; | |
| // Only the types that needs the @AboveAverage | |
| if ((_type == eExcelConditionalFormattingRuleType.BelowAverage) | |
| || (_type == eExcelConditionalFormattingRuleType.BelowOrEqualAverage) | |
| || (_type == eExcelConditionalFormattingRuleType.BelowStdDev)) | |
| { | |
| aboveAverageValue = "0"; | |
| } | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.AboveAverageAttribute, | |
| aboveAverageValue, | |
| true); | |
| } | |
| } | |
| /// <summary> | |
| /// EqualAverage | |
| /// </summary> | |
| internal protected bool? EqualAverage | |
| { | |
| get | |
| { | |
| bool? equalAverage = GetXmlNodeBoolNullable( | |
| ExcelConditionalFormattingConstants.Paths.EqualAverageAttribute); | |
| // Equal Avarege only if TRUE | |
| return (equalAverage == true); | |
| } | |
| set | |
| { | |
| string equalAverageValue = string.Empty; | |
| // Only the types that needs the @EqualAverage | |
| if ((_type == eExcelConditionalFormattingRuleType.AboveOrEqualAverage) | |
| || (_type == eExcelConditionalFormattingRuleType.BelowOrEqualAverage)) | |
| { | |
| equalAverageValue = "1"; | |
| } | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.EqualAverageAttribute, | |
| equalAverageValue, | |
| true); | |
| } | |
| } | |
| /// <summary> | |
| /// Bottom attribute | |
| /// </summary> | |
| internal protected bool? Bottom | |
| { | |
| get | |
| { | |
| bool? bottom = GetXmlNodeBoolNullable( | |
| ExcelConditionalFormattingConstants.Paths.BottomAttribute); | |
| // Bottom if TRUE | |
| return (bottom == true); | |
| } | |
| set | |
| { | |
| string bottomValue = string.Empty; | |
| // Only the types that needs the @Bottom | |
| if ((_type == eExcelConditionalFormattingRuleType.Bottom) | |
| || (_type == eExcelConditionalFormattingRuleType.BottomPercent)) | |
| { | |
| bottomValue = "1"; | |
| } | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.BottomAttribute, | |
| bottomValue, | |
| true); | |
| } | |
| } | |
| /// <summary> | |
| /// Percent attribute | |
| /// </summary> | |
| internal protected bool? Percent | |
| { | |
| get | |
| { | |
| bool? percent = GetXmlNodeBoolNullable( | |
| ExcelConditionalFormattingConstants.Paths.PercentAttribute); | |
| // Bottom if TRUE | |
| return (percent == true); | |
| } | |
| set | |
| { | |
| string percentValue = string.Empty; | |
| // Only the types that needs the @Bottom | |
| if ((_type == eExcelConditionalFormattingRuleType.BottomPercent) | |
| || (_type == eExcelConditionalFormattingRuleType.TopPercent)) | |
| { | |
| percentValue = "1"; | |
| } | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.PercentAttribute, | |
| percentValue, | |
| true); | |
| } | |
| } | |
| /// <summary> | |
| /// TimePeriod | |
| /// </summary> | |
| internal protected eExcelConditionalFormattingTimePeriodType TimePeriod | |
| { | |
| get | |
| { | |
| return ExcelConditionalFormattingTimePeriodType.GetTypeByAttribute( | |
| GetXmlNodeString(ExcelConditionalFormattingConstants.Paths.TimePeriodAttribute)); | |
| } | |
| set | |
| { | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.TimePeriodAttribute, | |
| ExcelConditionalFormattingTimePeriodType.GetAttributeByType(value), | |
| true); | |
| } | |
| } | |
| /// <summary> | |
| /// Operator | |
| /// </summary> | |
| internal protected eExcelConditionalFormattingOperatorType Operator | |
| { | |
| get | |
| { | |
| return ExcelConditionalFormattingOperatorType.GetTypeByAttribute( | |
| GetXmlNodeString(ExcelConditionalFormattingConstants.Paths.OperatorAttribute)); | |
| } | |
| set | |
| { | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.OperatorAttribute, | |
| ExcelConditionalFormattingOperatorType.GetAttributeByType(value), | |
| true); | |
| } | |
| } | |
| /// <summary> | |
| /// Formula | |
| /// </summary> | |
| public string Formula | |
| { | |
| get | |
| { | |
| return GetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.Formula); | |
| } | |
| set | |
| { | |
| SetXmlNodeString( | |
| ExcelConditionalFormattingConstants.Paths.Formula, | |
| value); | |
| } | |
| } | |
| /// <summary> | |
| /// Formula2 | |
| /// </summary> | |
| public string Formula2 | |
| { | |
| get | |
| { | |
| return GetXmlNodeString( | |
| string.Format( | |
| "{0}[position()=2]", | |
| // {0} | |
| ExcelConditionalFormattingConstants.Paths.Formula)); | |
| } | |
| set | |
| { | |
| // Create/Get the first <formula> node (ensure that it exists) | |
| var firstNode = CreateComplexNode( | |
| TopNode, | |
| string.Format( | |
| "{0}[position()=1]", | |
| // {0} | |
| ExcelConditionalFormattingConstants.Paths.Formula)); | |
| // Create/Get the seconde <formula> node (ensure that it exists) | |
| var secondNode = CreateComplexNode( | |
| TopNode, | |
| string.Format( | |
| "{0}[position()=2]", | |
| // {0} | |
| ExcelConditionalFormattingConstants.Paths.Formula)); | |
| // Save the formula in the second <formula> node | |
| secondNode.InnerText = value; | |
| } | |
| } | |
| #endregion Internal Properties | |
| /****************************************************************************************/ | |
| } | |
| } |