| /******************************************************************************* | |
| * 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; | |
| namespace AppsheetEpplus; | |
| /// <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) { | |
| ArgumentNullException.ThrowIfNull(address); | |
| ArgumentOutOfRangeException.ThrowIfLessThan(priority, 1); | |
| ArgumentNullException.ThrowIfNull(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; | |
| } | |
| } | |
| } |