| /******************************************************************************* |
| * 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.Immutable; |
| 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; |
| |
| protected override ImmutableArray<string> SchemaNodeOrder => |
| ExcelWorksheet.WorksheetSchemaNodeOrder; |
| |
| /// <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; |
| |
| 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; |
| } |
| } |
| } |