| /******************************************************************************* | 
 |  * 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 | 
 |     /****************************************************************************************/ | 
 |   } | 
 | } |