blob: 59376e7bf9bc8163899410f8abdd663a77da9d35 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}
}
}