/******************************************************************************* * 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 { /// /// /// public abstract class ExcelConditionalFormattingRule : XmlHelper, IExcelConditionalFormattingRule { /****************************************************************************************/ #region Private Properties private eExcelConditionalFormattingRuleType? _type; private ExcelWorksheet _worksheet; /// /// Sinalize that we are in a Cnaging Priorities opeartion so that we won't enter /// a recursive loop. /// private static bool _changingPriority = false; #endregion Private Properties /****************************************************************************************/ #region Constructors /// /// Initialize the /// /// /// /// Used also as the cfRule unique key /// /// /// 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 inside 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 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 } } /// /// Initialize the /// /// /// /// /// /// 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 /// /// Get the <cfRule> node /// public XmlNode Node { get { return TopNode; } } /// /// Address of the conditional formatting rule /// /// /// 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. /// 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 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 node to the new parent node TopNode = newParentNode.AppendChild(Node); // Check if the old parent node has node inside it if (!oldParentNode.HasChildNodes) { // Remove the old parent node oldParentNode.ParentNode.RemoveChild(oldParentNode); } } } } /// /// Type of conditional formatting rule. ST_CfType §18.18.12. /// 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); } } /// /// 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. /// 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); } } } /// /// If this flag is true, no rules with lower priority shall be applied over this rule, /// when this rule evaluates to true. /// public bool StopIfTrue { get { return GetXmlNodeBool( ExcelConditionalFormattingConstants.Paths.StopIfTrueAttribute); } set { SetXmlNodeString( ExcelConditionalFormattingConstants.Paths.StopIfTrueAttribute, (value == true) ? "1" : string.Empty, true); } } /// /// DxfId Style Attribute /// 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; } } /// /// StdDev (zero is not allowed and will be converted to 1) /// public UInt16 StdDev { get { return Convert.ToUInt16(GetXmlNodeInt( ExcelConditionalFormattingConstants.Paths.StdDevAttribute)); } set { SetXmlNodeString( ExcelConditionalFormattingConstants.Paths.StdDevAttribute, (value == 0) ? "1" : value.ToString(), true); } } /// /// Rank (zero is not allowed and will be converted to 1) /// 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 /// /// AboveAverage /// 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); } } /// /// EqualAverage /// 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); } } /// /// Bottom attribute /// 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); } } /// /// Percent attribute /// 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); } } /// /// TimePeriod /// internal protected eExcelConditionalFormattingTimePeriodType TimePeriod { get { return ExcelConditionalFormattingTimePeriodType.GetTypeByAttribute( GetXmlNodeString(ExcelConditionalFormattingConstants.Paths.TimePeriodAttribute)); } set { SetXmlNodeString( ExcelConditionalFormattingConstants.Paths.TimePeriodAttribute, ExcelConditionalFormattingTimePeriodType.GetAttributeByType(value), true); } } /// /// Operator /// internal protected eExcelConditionalFormattingOperatorType Operator { get { return ExcelConditionalFormattingOperatorType.GetTypeByAttribute( GetXmlNodeString(ExcelConditionalFormattingConstants.Paths.OperatorAttribute)); } set { SetXmlNodeString( ExcelConditionalFormattingConstants.Paths.OperatorAttribute, ExcelConditionalFormattingOperatorType.GetAttributeByType(value), true); } } /// /// Formula /// public string Formula { get { return GetXmlNodeString( ExcelConditionalFormattingConstants.Paths.Formula); } set { SetXmlNodeString( ExcelConditionalFormattingConstants.Paths.Formula, value); } } /// /// Formula2 /// public string Formula2 { get { return GetXmlNodeString( string.Format( "{0}[position()=2]", // {0} ExcelConditionalFormattingConstants.Paths.Formula)); } set { // Create/Get the first node (ensure that it exists) var firstNode = CreateComplexNode( TopNode, string.Format( "{0}[position()=1]", // {0} ExcelConditionalFormattingConstants.Paths.Formula)); // Create/Get the seconde node (ensure that it exists) var secondNode = CreateComplexNode( TopNode, string.Format( "{0}[position()=2]", // {0} ExcelConditionalFormattingConstants.Paths.Formula)); // Save the formula in the second node secondNode.InnerText = value; } } #endregion Internal Properties /****************************************************************************************/ } }