| /******************************************************************************* | 
 |  * 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.Xml; | 
 | using OfficeOpenXml.ConditionalFormatting.Contracts; | 
 | using OfficeOpenXml.Style.Dxf; | 
 | using OfficeOpenXml.Utils; | 
 |  | 
 | namespace OfficeOpenXml.ConditionalFormatting; | 
 |  | 
 | /// <summary> | 
 | /// | 
 | /// </summary> | 
 | public abstract class ExcelConditionalFormattingRule : XmlHelper, IExcelConditionalFormattingRule { | 
 |   private eExcelConditionalFormattingRuleType? _type; | 
 |   private readonly 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; | 
 |  | 
 |   /// <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) {} | 
 |  | 
 |   /// <summary> | 
 |   /// Get the <cfRule> node | 
 |   /// </summary> | 
 |   public XmlNode Node => 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 => | 
 |       new(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 => 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 => GetXmlNodeBool(ExcelConditionalFormattingConstants.Paths._stopIfTrueAttribute); | 
 |     set => | 
 |       SetXmlNodeString( | 
 |           ExcelConditionalFormattingConstants.Paths._stopIfTrueAttribute, | 
 |           value ? "1" : string.Empty, | 
 |           true); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// DxfId Style Attribute | 
 |   /// </summary> | 
 |   internal int DxfId { | 
 |     get => GetXmlNodeInt(ExcelConditionalFormattingConstants.Paths._dxfIdAttribute); | 
 |     set => | 
 |       SetXmlNodeString( | 
 |           ExcelConditionalFormattingConstants.Paths._dxfIdAttribute, | 
 |           (value == int.MinValue) ? string.Empty : value.ToString(), | 
 |           true); | 
 |   } | 
 |  | 
 |   internal ExcelDxfStyleConditionalFormatting _style; | 
 |  | 
 |   public ExcelDxfStyleConditionalFormatting Style { | 
 |     get { | 
 |       if (_style == null) { | 
 |         _style = new(NameSpaceManager, null, _worksheet.Workbook.Styles); | 
 |       } | 
 |       return _style; | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// StdDev (zero is not allowed and will be converted to 1) | 
 |   /// </summary> | 
 |   public UInt16 StdDev { | 
 |     get => | 
 |       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 => | 
 |       Convert.ToUInt16(GetXmlNodeInt(ExcelConditionalFormattingConstants.Paths._rankAttribute)); | 
 |     set => | 
 |       SetXmlNodeString( | 
 |           ExcelConditionalFormattingConstants.Paths._rankAttribute, | 
 |           (value == 0) ? "1" : value.ToString(), | 
 |           true); | 
 |   } | 
 |  | 
 |   /// <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 => | 
 |       ExcelConditionalFormattingTimePeriodType.GetTypeByAttribute( | 
 |           GetXmlNodeString(ExcelConditionalFormattingConstants.Paths._timePeriodAttribute)); | 
 |     set => | 
 |       SetXmlNodeString( | 
 |           ExcelConditionalFormattingConstants.Paths._timePeriodAttribute, | 
 |           ExcelConditionalFormattingTimePeriodType.GetAttributeByType(value), | 
 |           true); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Operator | 
 |   /// </summary> | 
 |   internal protected eExcelConditionalFormattingOperatorType Operator { | 
 |     get => | 
 |       ExcelConditionalFormattingOperatorType.GetTypeByAttribute( | 
 |           GetXmlNodeString(ExcelConditionalFormattingConstants.Paths._operatorAttribute)); | 
 |     set => | 
 |       SetXmlNodeString( | 
 |           ExcelConditionalFormattingConstants.Paths._operatorAttribute, | 
 |           ExcelConditionalFormattingOperatorType.GetAttributeByType(value), | 
 |           true); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Formula | 
 |   /// </summary> | 
 |   public string Formula { | 
 |     get => GetXmlNodeString(ExcelConditionalFormattingConstants.Paths._formula); | 
 |     set => SetXmlNodeString(ExcelConditionalFormattingConstants.Paths._formula, value); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Formula2 | 
 |   /// </summary> | 
 |   public string Formula2 { | 
 |     get => | 
 |       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; | 
 |     } | 
 |   } | 
 | } |