| /******************************************************************************* |
| * 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.Utils; |
| |
| namespace OfficeOpenXml.ConditionalFormatting; |
| |
| /// <summary> |
| /// 18.3.1.11 cfvo (Conditional Format Value Object) |
| /// Describes the values of the interpolation points in a gradient scale. |
| /// </summary> |
| public class ExcelConditionalFormattingColorScaleValue : XmlHelper { |
| private eExcelConditionalFormattingValueObjectPosition _position; |
| private eExcelConditionalFormattingRuleType _ruleType; |
| private readonly ExcelWorksheet _worksheet; |
| |
| protected override ImmutableArray<string> SchemaNodeOrder { get; } = [ |
| ExcelConditionalFormattingConstants.Nodes._cfvo, |
| ExcelConditionalFormattingConstants.Nodes._color, |
| ]; |
| |
| /// <summary> |
| /// Initialize the cfvo (§18.3.1.11) node |
| /// </summary> |
| /// <param name="position"></param> |
| /// <param name="type"></param> |
| /// <param name="color"></param> |
| /// <param name="value"></param> |
| /// <param name="formula"></param> |
| /// <param name="ruleType"></param> |
| /// <param name="address"></param> |
| /// <param name="priority"></param> |
| /// <param name="worksheet"></param> |
| /// <param name="itemElementNode">The cfvo (§18.3.1.11) node parent. Can be any of the following: |
| /// colorScale (§18.3.1.16); dataBar (§18.3.1.28); iconSet (§18.3.1.49)</param> |
| /// <param name="namespaceManager"></param> |
| internal ExcelConditionalFormattingColorScaleValue( |
| eExcelConditionalFormattingValueObjectPosition position, |
| eExcelConditionalFormattingValueObjectType type, |
| double value, |
| string formula, |
| eExcelConditionalFormattingRuleType ruleType, |
| ExcelAddress address, |
| int priority, |
| ExcelWorksheet worksheet, |
| XmlNode itemElementNode, |
| XmlNamespaceManager namespaceManager) |
| : base(namespaceManager, itemElementNode) { |
| Require.Argument(priority).IsInRange(1, int.MaxValue, "priority"); |
| Require.Argument(address).IsNotNull("address"); |
| Require.Argument(worksheet).IsNotNull("worksheet"); |
| |
| // Save the worksheet for private methods to use |
| _worksheet = worksheet; |
| |
| // Check if the parent does not exists |
| if (itemElementNode == null) { |
| // Get the parent node path by the rule type |
| string parentNodePath = ExcelConditionalFormattingValueObjectType.GetParentPathByRuleType( |
| ruleType); |
| |
| // Check for en error (rule type does not have <cfvo>) |
| if (parentNodePath == string.Empty) { |
| throw new(ExcelConditionalFormattingConstants.Errors._missingCfvoParentNode); |
| } |
| |
| // Point to the <cfvo> parent node |
| itemElementNode = _worksheet.WorksheetXml.SelectSingleNode( |
| string.Format( |
| "//{0}[{1}='{2}']/{3}[{4}='{5}']/{6}", |
| // {0} |
| ExcelConditionalFormattingConstants.Paths._conditionalFormatting, |
| // {1} |
| ExcelConditionalFormattingConstants.Paths._sqrefAttribute, |
| // {2} |
| address.Address, |
| // {3} |
| ExcelConditionalFormattingConstants.Paths._cfRule, |
| // {4} |
| ExcelConditionalFormattingConstants.Paths._priorityAttribute, |
| // {5} |
| priority, |
| // {6} |
| parentNodePath), |
| _worksheet.NameSpaceManager); |
| |
| // Check for en error (rule type does not have <cfvo>) |
| if (itemElementNode == null) { |
| throw new(ExcelConditionalFormattingConstants.Errors._missingCfvoParentNode); |
| } |
| } |
| |
| // Point to the <cfvo> parent node (<colorScale>, <dataBar> or <iconSet>) |
| // This is different than normal, as TopNode does not point to the node itself but to |
| // its PARENT. Later, in the CreateNodeByOrdem method the TopNode will be updated. |
| TopNode = itemElementNode; |
| |
| // Save the attributes |
| Position = position; |
| RuleType = ruleType; |
| Type = type; |
| Value = value; |
| Formula = formula; |
| } |
| |
| /// <summary> |
| /// Initialize the <see cref="ExcelConditionalFormattingColorScaleValue"/> |
| /// </summary> |
| /// <param name="position"></param> |
| /// <param name="type"></param> |
| /// <param name="color"></param> |
| /// <param name="value"></param> |
| /// <param name="formula"></param> |
| /// <param name="ruleType"></param> |
| /// <param name="priority"></param> |
| /// <param name="address"></param> |
| /// <param name="worksheet"></param> |
| /// <param name="namespaceManager"></param> |
| internal ExcelConditionalFormattingColorScaleValue( |
| eExcelConditionalFormattingValueObjectPosition position, |
| eExcelConditionalFormattingValueObjectType type, |
| double value, |
| string formula, |
| eExcelConditionalFormattingRuleType ruleType, |
| ExcelAddress address, |
| int priority, |
| ExcelWorksheet worksheet, |
| XmlNamespaceManager namespaceManager) |
| : this( |
| position, |
| type, |
| value, |
| formula, |
| ruleType, |
| address, |
| priority, |
| worksheet, |
| null, |
| namespaceManager) {} |
| |
| /// <summary> |
| /// Initialize the <see cref="ExcelConditionalFormattingColorScaleValue"/> |
| /// </summary> |
| /// <param name="position"></param> |
| /// <param name="type"></param> |
| /// <param name="color"></param> |
| /// <param name="ruleType"></param> |
| /// <param name="priority"></param> |
| /// <param name="address"></param> |
| /// <param name="worksheet"></param> |
| /// <param name="namespaceManager"></param> |
| internal ExcelConditionalFormattingColorScaleValue( |
| eExcelConditionalFormattingValueObjectPosition position, |
| eExcelConditionalFormattingValueObjectType type, |
| eExcelConditionalFormattingRuleType ruleType, |
| ExcelAddress address, |
| int priority, |
| ExcelWorksheet worksheet, |
| XmlNamespaceManager namespaceManager) |
| : this( |
| position, |
| type, |
| 0, |
| null, |
| ruleType, |
| address, |
| priority, |
| worksheet, |
| null, |
| namespaceManager) {} |
| |
| /// <summary> |
| /// Get the node order (1, 2 ou 3) according to the Position (Low, Middle and High) |
| /// and the Rule Type (TwoColorScale ou ThreeColorScale). |
| /// </summary> |
| /// <returns></returns> |
| private int GetNodeOrder() { |
| return ExcelConditionalFormattingValueObjectType.GetOrderByPosition(Position, RuleType); |
| } |
| |
| /// <summary> |
| /// Create the 'cfvo'/'color' nodes in the right order. They should appear like this: |
| /// "cfvo" --> Low Value (value object) |
| /// "cfvo" --> Middle Value (value object) |
| /// "cfvo" --> High Value (value object) |
| /// "color" --> Low Value (color) |
| /// "color" --> Middle Value (color) |
| /// "color" --> High Value (color) |
| /// </summary> |
| /// <param name="nodeType"></param> |
| /// <param name="attributePath"></param> |
| /// <param name="attributeValue"></param> |
| private void CreateNodeByOrdem( |
| eExcelConditionalFormattingValueObjectNodeType nodeType, |
| string attributePath, |
| string attributeValue) { |
| // Save the current TopNode |
| XmlNode currentTopNode = TopNode; |
| |
| string nodePath = ExcelConditionalFormattingValueObjectType.GetNodePathByNodeType(nodeType); |
| int nodeOrder = GetNodeOrder(); |
| eNodeInsertOrder nodeInsertOrder = eNodeInsertOrder.SchemaOrder; |
| XmlNode referenceNode = null; |
| |
| if (nodeOrder > 1) { |
| // Find the node just before the one we need to include |
| referenceNode = TopNode.SelectSingleNode( |
| string.Format( |
| "{0}[position()={1}]", |
| // {0} |
| nodePath, |
| // {1} |
| nodeOrder - 1), |
| _worksheet.NameSpaceManager); |
| |
| // Only if the prepend node exists than insert after |
| if (referenceNode != null) { |
| nodeInsertOrder = eNodeInsertOrder.After; |
| } |
| } |
| |
| // Create the node in the right order |
| var node = CreateComplexNode( |
| TopNode, |
| string.Format( |
| "{0}[position()={1}]", |
| // {0} |
| nodePath, |
| // {1} |
| nodeOrder), |
| nodeInsertOrder, |
| referenceNode); |
| |
| // Point to the new node as the temporary TopNode (we need it for the XmlHelper functions) |
| TopNode = node; |
| |
| // Add/Remove the attribute (if the attributeValue is empty then it will be removed) |
| SetXmlNodeString(node, attributePath, attributeValue, true); |
| |
| // Point back to the <cfvo>/<color> parent node |
| TopNode = currentTopNode; |
| } |
| |
| /// <summary> |
| /// |
| /// </summary> |
| internal eExcelConditionalFormattingValueObjectPosition Position { |
| get => _position; |
| set => _position = value; |
| } |
| |
| /// <summary> |
| /// |
| /// </summary> |
| internal eExcelConditionalFormattingRuleType RuleType { |
| get => _ruleType; |
| set => _ruleType = value; |
| } |
| |
| /// <summary> |
| /// |
| /// </summary> |
| public eExcelConditionalFormattingValueObjectType Type { |
| get { |
| var typeAttribute = GetXmlNodeString( |
| string.Format( |
| "{0}[position()={1}]/{2}", |
| // {0} |
| ExcelConditionalFormattingConstants.Paths._cfvo, |
| // {1} |
| GetNodeOrder(), |
| // {2} |
| ExcelConditionalFormattingConstants.Paths._typeAttribute)); |
| |
| return ExcelConditionalFormattingValueObjectType.GetTypeByAttrbiute(typeAttribute); |
| } |
| set { |
| CreateNodeByOrdem( |
| eExcelConditionalFormattingValueObjectNodeType.Cfvo, |
| ExcelConditionalFormattingConstants.Paths._typeAttribute, |
| ExcelConditionalFormattingValueObjectType.GetAttributeByType(value)); |
| |
| bool removeValAttribute = false; |
| |
| // Make sure unnecessary attributes are removed (occures when we change |
| // the value object type) |
| switch (Type) { |
| case eExcelConditionalFormattingValueObjectType.Min: |
| case eExcelConditionalFormattingValueObjectType.Max: |
| removeValAttribute = true; |
| break; |
| } |
| |
| // Check if we need to remove the @val attribute |
| if (removeValAttribute) { |
| string nodePath = ExcelConditionalFormattingValueObjectType.GetNodePathByNodeType( |
| eExcelConditionalFormattingValueObjectNodeType.Cfvo); |
| int nodeOrder = GetNodeOrder(); |
| |
| // Remove the attribute (removed when the value = '') |
| CreateComplexNode( |
| TopNode, |
| string.Format( |
| "{0}[position()={1}]/{2}=''", |
| // {0} |
| nodePath, |
| // {1} |
| nodeOrder, |
| // {2} |
| ExcelConditionalFormattingConstants.Paths._valAttribute)); |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Get/Set the 'cfvo' node @val attribute |
| /// </summary> |
| public Double Value { |
| get => |
| GetXmlNodeDouble( |
| string.Format( |
| "{0}[position()={1}]/{2}", |
| // {0} |
| ExcelConditionalFormattingConstants.Paths._cfvo, |
| // {1} |
| GetNodeOrder(), |
| // {2} |
| ExcelConditionalFormattingConstants.Paths._valAttribute)); |
| set { |
| string valueToStore = string.Empty; |
| |
| // Only some types use the @val attribute |
| if ((Type == eExcelConditionalFormattingValueObjectType.Num) |
| || (Type == eExcelConditionalFormattingValueObjectType.Percent) |
| || (Type == eExcelConditionalFormattingValueObjectType.Percentile)) { |
| valueToStore = value.ToString(); |
| } |
| |
| CreateNodeByOrdem( |
| eExcelConditionalFormattingValueObjectNodeType.Cfvo, |
| ExcelConditionalFormattingConstants.Paths._valAttribute, |
| valueToStore); |
| } |
| } |
| |
| /// <summary> |
| /// Get/Set the Formula of the Object Value (uses the same attribute as the Value) |
| /// </summary> |
| public string Formula { |
| get { |
| // Return empty if the Object Value type is not Formula |
| if (Type != eExcelConditionalFormattingValueObjectType.Formula) { |
| return string.Empty; |
| } |
| |
| // Excel stores the formula in the @val attribute |
| return GetXmlNodeString( |
| string.Format( |
| "{0}[position()={1}]/{2}", |
| // {0} |
| ExcelConditionalFormattingConstants.Paths._cfvo, |
| // {1} |
| GetNodeOrder(), |
| // {2} |
| ExcelConditionalFormattingConstants.Paths._valAttribute)); |
| } |
| set { |
| // Only store the formula if the Object Value type is Formula |
| if (Type == eExcelConditionalFormattingValueObjectType.Formula) { |
| CreateNodeByOrdem( |
| eExcelConditionalFormattingValueObjectNodeType.Cfvo, |
| ExcelConditionalFormattingConstants.Paths._valAttribute, |
| value ?? string.Empty); |
| } |
| } |
| } |
| } |