diff --git a/ClosedXML/ClosedXML.csproj b/ClosedXML/ClosedXML.csproj index 81d47c1..4121264 100644 --- a/ClosedXML/ClosedXML.csproj +++ b/ClosedXML/ClosedXML.csproj @@ -81,6 +81,9 @@ + + + @@ -95,6 +98,7 @@ + diff --git a/ClosedXML/Excel/ConditionalFormats/IXLConditionalFormat.cs b/ClosedXML/Excel/ConditionalFormats/IXLConditionalFormat.cs index 962cc21..8428f7e 100644 --- a/ClosedXML/Excel/ConditionalFormats/IXLConditionalFormat.cs +++ b/ClosedXML/Excel/ConditionalFormats/IXLConditionalFormat.cs @@ -68,7 +68,7 @@ IXLStyle WhenNotBlank(); IXLStyle WhenIsError(); IXLStyle WhenNotError(); - IXLStyle WhenDateIs(XLTimePeriod timePeriod ); + IXLStyle WhenDateIs(XLTimePeriod timePeriod); IXLStyle WhenContains(String value); IXLStyle WhenNotContains(String value); IXLStyle WhenStartsWith(String value); @@ -99,6 +99,7 @@ IXLCFColorScaleMin ColorScale(); IXLCFDataBarMin DataBar(XLColor color, Boolean showBarOnly = false); + IXLCFDataBarMin DataBar(XLColor positiveColor, XLColor negativeColor, Boolean showBarOnly = false); IXLCFIconSet IconSet(XLIconSetStyle iconSetStyle, Boolean reverseIconOrder = false, Boolean showIconOnly = false); XLConditionalFormatType ConditionalFormatType { get; } @@ -113,12 +114,11 @@ XLDictionary Colors { get; } XLDictionary ContentTypes { get; } XLDictionary IconSetOperators { get; } - + XLCFOperator Operator { get; } Boolean Bottom { get; } Boolean Percent { get; } - - + + } } - \ No newline at end of file diff --git a/ClosedXML/Excel/ConditionalFormats/Save/IXLCFConverterExtension.cs b/ClosedXML/Excel/ConditionalFormats/Save/IXLCFConverterExtension.cs new file mode 100644 index 0000000..82b11a0 --- /dev/null +++ b/ClosedXML/Excel/ConditionalFormats/Save/IXLCFConverterExtension.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using DocumentFormat.OpenXml.Office2010.Excel; + +namespace ClosedXML.Excel +{ + internal interface IXLCFConverterExtension + { + ConditionalFormattingRule Convert(IXLConditionalFormat cf, XLWorkbook.SaveContext context); + } +} diff --git a/ClosedXML/Excel/ConditionalFormats/Save/XLCFConvertersExtension.cs b/ClosedXML/Excel/ConditionalFormats/Save/XLCFConvertersExtension.cs new file mode 100644 index 0000000..7b85951 --- /dev/null +++ b/ClosedXML/Excel/ConditionalFormats/Save/XLCFConvertersExtension.cs @@ -0,0 +1,28 @@ +using DocumentFormat.OpenXml.Office2010.Excel; +using System; +using System.Collections.Generic; + +namespace ClosedXML.Excel +{ + internal class XLCFConvertersExtension + { + private readonly static Dictionary Converters; + + static XLCFConvertersExtension() + { + XLCFConvertersExtension.Converters = new Dictionary() + { + { XLConditionalFormatType.DataBar, new XLCFDataBarConverterExtension() } + }; + } + + public XLCFConvertersExtension() + { + } + + public static ConditionalFormattingRule Convert(IXLConditionalFormat conditionalFormat, XLWorkbook.SaveContext context) + { + return XLCFConvertersExtension.Converters[conditionalFormat.ConditionalFormatType].Convert(conditionalFormat, context); + } + } +} \ No newline at end of file diff --git a/ClosedXML/Excel/ConditionalFormats/Save/XLCFDataBarConverter.cs b/ClosedXML/Excel/ConditionalFormats/Save/XLCFDataBarConverter.cs index ff2d3c0..7edeb9c 100644 --- a/ClosedXML/Excel/ConditionalFormats/Save/XLCFDataBarConverter.cs +++ b/ClosedXML/Excel/ConditionalFormats/Save/XLCFDataBarConverter.cs @@ -4,6 +4,7 @@ using System.Text; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Spreadsheet; +using ClosedXML.Extensions; namespace ClosedXML.Excel { @@ -14,6 +15,7 @@ var conditionalFormattingRule = new ConditionalFormattingRule { Type = cf.ConditionalFormatType.ToOpenXml(), Priority = priority }; var dataBar = new DataBar { ShowValue = !cf.ShowBarOnly }; + var conditionalFormatValueObject1 = new ConditionalFormatValueObject { Type = cf.ContentTypes[1].ToOpenXml() }; if (cf.Values.Any() && cf.Values[1]?.Value != null) conditionalFormatValueObject1.Val = cf.Values[1].Value; @@ -38,8 +40,25 @@ dataBar.Append(conditionalFormatValueObject2); dataBar.Append(color); + conditionalFormattingRule.Append(dataBar); + if (cf.Colors.Count > 1) + { + ConditionalFormattingRuleExtensionList conditionalFormattingRuleExtensionList = new ConditionalFormattingRuleExtensionList(); + + ConditionalFormattingRuleExtension conditionalFormattingRuleExtension = new ConditionalFormattingRuleExtension { Uri = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}" }; + conditionalFormattingRuleExtension.AddNamespaceDeclaration("x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"); + DocumentFormat.OpenXml.Office2010.Excel.Id id = new DocumentFormat.OpenXml.Office2010.Excel.Id + { + Text = (cf as XLConditionalFormat).Id.WrapInBraces() + }; + conditionalFormattingRuleExtension.Append(id); + + conditionalFormattingRuleExtensionList.Append(conditionalFormattingRuleExtension); + + conditionalFormattingRule.Append(conditionalFormattingRuleExtensionList); + } return conditionalFormattingRule; } } diff --git a/ClosedXML/Excel/ConditionalFormats/Save/XLCFDataBarConverterExtension.cs b/ClosedXML/Excel/ConditionalFormats/Save/XLCFDataBarConverterExtension.cs new file mode 100644 index 0000000..703d006 --- /dev/null +++ b/ClosedXML/Excel/ConditionalFormats/Save/XLCFDataBarConverterExtension.cs @@ -0,0 +1,50 @@ +using ClosedXML.Extensions; +using DocumentFormat.OpenXml.Office2010.Excel; + +namespace ClosedXML.Excel +{ + internal class XLCFDataBarConverterExtension : IXLCFConverterExtension + { + public XLCFDataBarConverterExtension() + { + } + + public ConditionalFormattingRule Convert(IXLConditionalFormat cf, XLWorkbook.SaveContext context) + { + ConditionalFormattingRule conditionalFormattingRule = new ConditionalFormattingRule() + { + Type = DocumentFormat.OpenXml.Spreadsheet.ConditionalFormatValues.DataBar, + Id = (cf as XLConditionalFormat).Id.WrapInBraces() + }; + + DataBar dataBar = new DataBar() + { + MinLength = 0, + MaxLength = 100, + Gradient = false, + AxisPosition = DataBarAxisPositionValues.Middle + }; + + var cfMin = new ConditionalFormattingValueObject { Type = ConditionalFormattingValueObjectTypeValues.AutoMin }; + var cfMax = new ConditionalFormattingValueObject() { Type = ConditionalFormattingValueObjectTypeValues.AutoMax }; + + var barAxisColor = new BarAxisColor { Rgb = XLColor.Black.Color.ToHex() }; + + var negativeFillColor = new NegativeFillColor { Rgb = cf.Colors[1].Color.ToHex() }; + if (cf.Colors.Count == 2) + { + negativeFillColor = new NegativeFillColor { Rgb = cf.Colors[2].Color.ToHex() }; + } + + dataBar.Append(cfMin); + dataBar.Append(cfMax); + + dataBar.Append(negativeFillColor); + dataBar.Append(barAxisColor); + + conditionalFormattingRule.Append(dataBar); + + return conditionalFormattingRule; + } + } +} diff --git a/ClosedXML/Excel/ConditionalFormats/XLConditionalFormat.cs b/ClosedXML/Excel/ConditionalFormats/XLConditionalFormat.cs index 96847d6..b42905d 100644 --- a/ClosedXML/Excel/ConditionalFormats/XLConditionalFormat.cs +++ b/ClosedXML/Excel/ConditionalFormats/XLConditionalFormat.cs @@ -5,11 +5,11 @@ namespace ClosedXML.Excel { - internal class XLConditionalFormat: IXLConditionalFormat, IXLStylized + internal class XLConditionalFormat : IXLConditionalFormat, IXLStylized { - public XLConditionalFormat(XLRange range, Boolean copyDefaultModify = false) { + Id = Guid.NewGuid(); Range = range; Style = new XLStyle(this, range.Worksheet.Style); Values = new XLDictionary(); @@ -18,31 +18,34 @@ IconSetOperators = new XLDictionary(); CopyDefaultModify = copyDefaultModify; } - public XLConditionalFormat(XLConditionalFormat other) + + public XLConditionalFormat(XLConditionalFormat conditionalFormat) { - Range = other.Range; - Style = new XLStyle(this, other.Style); - Values = new XLDictionary(other.Values); - Colors = new XLDictionary(other.Colors); - ContentTypes = new XLDictionary(other.ContentTypes); - IconSetOperators = new XLDictionary(other.IconSetOperators); + Id = Guid.NewGuid(); + Range = conditionalFormat.Range; + Style = new XLStyle(this, conditionalFormat.Style); + Values = new XLDictionary(conditionalFormat.Values); + Colors = new XLDictionary(conditionalFormat.Colors); + ContentTypes = new XLDictionary(conditionalFormat.ContentTypes); + IconSetOperators = new XLDictionary(conditionalFormat.IconSetOperators); - ConditionalFormatType = other.ConditionalFormatType; - TimePeriod = other.TimePeriod; - IconSetStyle = other.IconSetStyle; - Operator = other.Operator; - Bottom = other.Bottom; - Percent = other.Percent; - ReverseIconOrder = other.ReverseIconOrder; - ShowIconOnly = other.ShowIconOnly; - ShowBarOnly = other.ShowBarOnly; + ConditionalFormatType = conditionalFormat.ConditionalFormatType; + TimePeriod = conditionalFormat.TimePeriod; + IconSetStyle = conditionalFormat.IconSetStyle; + Operator = conditionalFormat.Operator; + Bottom = conditionalFormat.Bottom; + Percent = conditionalFormat.Percent; + ReverseIconOrder = conditionalFormat.ReverseIconOrder; + ShowIconOnly = conditionalFormat.ShowIconOnly; + ShowBarOnly = conditionalFormat.ShowBarOnly; } + public Guid Id { get; internal set; } public Boolean CopyDefaultModify { get; set; } private IXLStyle _style; private Int32 _styleCacheId; - public IXLStyle Style{ get { return GetStyle(); } set { SetStyle(value); } } + public IXLStyle Style { get { return GetStyle(); } set { SetStyle(value); } } private IXLStyle GetStyle() { //return _style; @@ -88,12 +91,12 @@ public XLConditionalFormatType ConditionalFormatType { get; set; } public XLTimePeriod TimePeriod { get; set; } public XLIconSetStyle IconSetStyle { get; set; } - public XLCFOperator Operator { get; set; } - public Boolean Bottom { get; set; } - public Boolean Percent { get; set; } - public Boolean ReverseIconOrder { get; set; } - public Boolean ShowIconOnly { get; set; } - public Boolean ShowBarOnly { get; set; } + public XLCFOperator Operator { get; set; } + public Boolean Bottom { get; set; } + public Boolean Percent { get; set; } + public Boolean ReverseIconOrder { get; set; } + public Boolean ShowIconOnly { get; set; } + public Boolean ShowBarOnly { get; set; } public void CopyFrom(IXLConditionalFormat other) { @@ -279,8 +282,8 @@ return Style; } public IXLStyle WhenBetween(Double minValue, Double maxValue) - { - Values.Initialize(new XLFormula (minValue)); + { + Values.Initialize(new XLFormula(minValue)); Values.Add(new XLFormula(maxValue)); Operator = XLCFOperator.Between; ConditionalFormatType = XLConditionalFormatType.CellIs; @@ -308,7 +311,7 @@ public IXLStyle WhenIsTrue(String formula) { String f = formula.TrimStart()[0] == '=' ? formula : "=" + formula; - Values.Initialize(new XLFormula {Value = f}); + Values.Initialize(new XLFormula { Value = f }); ConditionalFormatType = XLConditionalFormatType.Expression; return Style; } @@ -328,7 +331,7 @@ Bottom = true; return Style; } - + public IXLCFColorScaleMin ColorScale() { ConditionalFormatType = XLConditionalFormatType.ColorScale; @@ -341,6 +344,14 @@ ConditionalFormatType = XLConditionalFormatType.DataBar; return new XLCFDataBarMin(this); } + public IXLCFDataBarMin DataBar(XLColor positiveColor, XLColor negativeColor, Boolean showBarOnly = false) + { + Colors.Initialize(positiveColor); + Colors.Add(negativeColor); + ShowBarOnly = showBarOnly; + ConditionalFormatType = XLConditionalFormatType.DataBar; + return new XLCFDataBarMin(this); + } public IXLCFIconSet IconSet(XLIconSetStyle iconSetStyle, Boolean reverseIconOrder = false, Boolean showIconOnly = false) { IconSetOperators.Clear(); diff --git a/ClosedXML/Excel/ConditionalFormats/XLConditionalFormats.cs b/ClosedXML/Excel/ConditionalFormats/XLConditionalFormats.cs index 5d1ea42..dc00b56 100644 --- a/ClosedXML/Excel/ConditionalFormats/XLConditionalFormats.cs +++ b/ClosedXML/Excel/ConditionalFormats/XLConditionalFormats.cs @@ -5,7 +5,7 @@ namespace ClosedXML.Excel { - internal class XLConditionalFormats: IXLConditionalFormats + internal class XLConditionalFormats : IXLConditionalFormats { private readonly List _conditionalFormats = new List(); public void Add(IXLConditionalFormat conditionalFormat) diff --git a/ClosedXML/Excel/XLWorkbook_Load.cs b/ClosedXML/Excel/XLWorkbook_Load.cs index be0d818..d00a525 100644 --- a/ClosedXML/Excel/XLWorkbook_Load.cs +++ b/ClosedXML/Excel/XLWorkbook_Load.cs @@ -1,5 +1,6 @@ #region +using ClosedXML.Extensions; using ClosedXML.Utils; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; @@ -263,6 +264,8 @@ LoadRowBreaks((RowBreaks)reader.LoadCurrentElement(), ws); else if (reader.ElementType == typeof(ColumnBreaks)) LoadColumnBreaks((ColumnBreaks)reader.LoadCurrentElement(), ws); + else if (reader.ElementType == typeof(WorksheetExtensionList)) + LoadExtensions((WorksheetExtensionList)reader.LoadCurrentElement(), ws); else if (reader.ElementType == typeof(LegacyDrawing)) ws.LegacyDrawingId = (reader.LoadCurrentElement() as LegacyDrawing).Id.Value; } @@ -1873,6 +1876,11 @@ var dataBar = fr.Elements().First(); if (dataBar.ShowValue != null) conditionalFormat.ShowBarOnly = !dataBar.ShowValue.Value; + + var id = fr.Descendants().FirstOrDefault(); + if (id != null && id.Text != null && !String.IsNullOrWhiteSpace(id.Text)) + conditionalFormat.Id = Guid.Parse(id.Text.Substring(1, id.Text.Length - 2)); + ExtractConditionalFormatValueObjects(conditionalFormat, dataBar); } else if (fr.Elements().Any()) @@ -1907,6 +1915,32 @@ } } + private void LoadExtensions(WorksheetExtensionList extensions, XLWorksheet ws) + { + if (extensions == null) + { + return; + } + + foreach (var conditionalFormattingRule in extensions + .Descendants() + .Where(cf => + cf.Type != null + && cf.Type.HasValue + && cf.Type.Value == ConditionalFormatValues.DataBar)) + { + var xlConditionalFormat = ws.ConditionalFormats + .Cast() + .SingleOrDefault(cf => cf.Id.WrapInBraces() == conditionalFormattingRule.Id); + if (xlConditionalFormat != null) + { + var negativeFillColor = conditionalFormattingRule.Descendants().SingleOrDefault(); + var color = new DocumentFormat.OpenXml.Spreadsheet.Color { Rgb = negativeFillColor.Rgb }; + xlConditionalFormat.Colors.Add(this.GetColor(color)); + } + } + } + private static XLFormula GetFormula(String value) { var formula = new XLFormula(); diff --git a/ClosedXML/Excel/XLWorkbook_Save.cs b/ClosedXML/Excel/XLWorkbook_Save.cs index aa9a77d..9979eec 100644 --- a/ClosedXML/Excel/XLWorkbook_Save.cs +++ b/ClosedXML/Excel/XLWorkbook_Save.cs @@ -4504,6 +4504,55 @@ } } + + var exlst = from c in xlWorksheet.ConditionalFormats where c.ConditionalFormatType == XLConditionalFormatType.DataBar && c.Colors.Count > 1 && typeof(IXLConditionalFormat).IsAssignableFrom(c.GetType()) select c; + if (exlst != null && exlst.Count() > 0) + { + if (!worksheetPart.Worksheet.Elements().Any()) + { + var previousElement = cm.GetPreviousElementFor(XLWSContentManager.XLWSContents.WorksheetExtensionList); + worksheetPart.Worksheet.InsertAfter(new WorksheetExtensionList(), previousElement); + } + + WorksheetExtensionList worksheetExtensionList = worksheetPart.Worksheet.Elements().First(); + cm.SetElement(XLWSContentManager.XLWSContents.WorksheetExtensionList, worksheetExtensionList); + + foreach (var cfGroup in exlst + .GroupBy( + c => c.Range.RangeAddress.ToStringRelative(false), + c => c, + (key, g) => new { RangeId = key, CfList = g.ToList() } + ) + ) + { + foreach (var xlConditionalFormat in cfGroup.CfList.Cast()) + { + var conditionalFormattingRule = worksheetExtensionList.Descendants() + .SingleOrDefault(r => r.Id == xlConditionalFormat.Id.WrapInBraces()); + if (conditionalFormattingRule != null) + { + WorksheetExtension worksheetExtension = conditionalFormattingRule.Ancestors().SingleOrDefault(); + worksheetExtensionList.RemoveChild(worksheetExtension); + } + WorksheetExtension worksheetExtension1 = new WorksheetExtension { Uri = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}" }; + worksheetExtension1.AddNamespaceDeclaration("x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"); + var conditionalFormattings = new DocumentFormat.OpenXml.Office2010.Excel.ConditionalFormattings(); + + var conditionalFormatting = new DocumentFormat.OpenXml.Office2010.Excel.ConditionalFormatting(); + conditionalFormatting.AddNamespaceDeclaration("xm", "http://schemas.microsoft.com/office/excel/2006/main"); + conditionalFormatting.Append(XLCFConvertersExtension.Convert(xlConditionalFormat, context)); + var referenceSequence = new DocumentFormat.OpenXml.Office.Excel.ReferenceSequence { Text = cfGroup.RangeId }; + conditionalFormatting.Append(referenceSequence); + + conditionalFormattings.Append(conditionalFormatting); + worksheetExtension1.Append(conditionalFormattings); + + worksheetExtensionList.Append(worksheetExtension1); + } + } + } + + #endregion Conditional Formatting #region DataValidations diff --git a/ClosedXML/Extensions/GuidExtensions.cs b/ClosedXML/Extensions/GuidExtensions.cs new file mode 100644 index 0000000..c9d2eba --- /dev/null +++ b/ClosedXML/Extensions/GuidExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace ClosedXML.Extensions +{ + internal static class GuidExtensions + { + internal static String WrapInBraces(this Guid guid) + { + return string.Concat('{', guid.ToString(), '}'); + } + } +} diff --git a/ClosedXML_Examples/ConditionalFormatting/ConditionalFormatting.cs b/ClosedXML_Examples/ConditionalFormatting/ConditionalFormatting.cs index a87bf0c..70d8f24 100644 --- a/ClosedXML_Examples/ConditionalFormatting/ConditionalFormatting.cs +++ b/ClosedXML_Examples/ConditionalFormatting/ConditionalFormatting.cs @@ -1,6 +1,5 @@ -using System; using ClosedXML.Excel; - +using System; namespace ClosedXML_Examples { @@ -27,7 +26,6 @@ public class CFColorScaleLowHigh : IXLExample { - public void Create(String filePath) { var workbook = new XLWorkbook(); @@ -221,7 +219,6 @@ } } - public class CFEqualsString : IXLExample { public void Create(String filePath) @@ -527,6 +524,28 @@ } } + public class CFDataBarNegative : IXLExample + { + public void Create(String filePath) + { + var workbook = new XLWorkbook(); + var ws = workbook.AddWorksheet("Sheet1"); + + ws.FirstCell().SetValue(-1) + .CellBelow().SetValue(1) + .CellBelow().SetValue(2) + .CellBelow().SetValue(3); + + ws.RangeUsed() + .AddConditionalFormat() + .DataBar(XLColor.Green, XLColor.Red, showBarOnly: false) + .LowestValue() + .Maximum(XLCFContentType.Percent, "100"); + + workbook.SaveAs(filePath); + } + } + public class CFIconSet : IXLExample { public void Create(String filePath) @@ -579,7 +598,7 @@ var workbook = new XLWorkbook(); var ws = workbook.AddWorksheet("Sheet1"); - ws.Cell(2,1).SetValue(1) + ws.Cell(2, 1).SetValue(1) .CellRight().SetValue(1) .CellRight().SetValue(2) .CellRight().SetValue(3); @@ -588,7 +607,6 @@ range.AddConditionalFormat().WhenEquals("1").Font.SetBold(); range.InsertRowsAbove(1); - workbook.SaveAs(filePath); } } @@ -606,7 +624,7 @@ .CellBelow().SetValue(3) .CellBelow().SetValue(4); - ws.RangeUsed().AddConditionalFormat().DataBar(XLColor.Red) + ws.RangeUsed().AddConditionalFormat().DataBar(XLColor.Red, XLColor.Green) .LowestValue() .HighestValue(); @@ -621,7 +639,7 @@ var workbook = new XLWorkbook(); var ws = workbook.AddWorksheet("Sheet1"); - using(var range = ws.Range("A1:A10")) + using (var range = ws.Range("A1:A10")) { range.AddConditionalFormat().WhenEquals("3") .Fill.SetBackgroundColor(XLColor.Blue); @@ -634,4 +652,4 @@ workbook.SaveAs(filePath); } } -} +} \ No newline at end of file diff --git a/ClosedXML_Tests/ClosedXML_Tests.csproj b/ClosedXML_Tests/ClosedXML_Tests.csproj index 28a64f7..7b6f453 100644 --- a/ClosedXML_Tests/ClosedXML_Tests.csproj +++ b/ClosedXML_Tests/ClosedXML_Tests.csproj @@ -296,6 +296,7 @@ + diff --git a/ClosedXML_Tests/Examples/ConditionalFormattingTests.cs b/ClosedXML_Tests/Examples/ConditionalFormattingTests.cs index 0760da0..8a298ae 100644 --- a/ClosedXML_Tests/Examples/ConditionalFormattingTests.cs +++ b/ClosedXML_Tests/Examples/ConditionalFormattingTests.cs @@ -37,6 +37,12 @@ } [Test] + public void CFDataBarNegative() + { + TestHelper.RunTestExample(@"ConditionalFormatting\CFDataBarNegative.xlsx"); + } + + [Test] public void CFEndsWith() { TestHelper.RunTestExample(@"ConditionalFormatting\CFEndsWith.xlsx"); diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFDataBarNegative.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFDataBarNegative.xlsx new file mode 100644 index 0000000..a3f79f6 --- /dev/null +++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFDataBarNegative.xlsx Binary files differ