/*******************************************************************************
* 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
/****************************************************************************************/
}
}