diff --git a/ClosedXML/Excel/CalcEngine/CalcEngineHelpers.cs b/ClosedXML/Excel/CalcEngine/CalcEngineHelpers.cs
index c45be92..00d8a32 100644
--- a/ClosedXML/Excel/CalcEngine/CalcEngineHelpers.cs
+++ b/ClosedXML/Excel/CalcEngine/CalcEngineHelpers.cs
@@ -103,5 +103,27 @@
Debug.Assert(false, "failed to evaluate criteria in SumIf");
return false;
}
+
+ internal static bool ValueIsBlank(object value)
+ {
+ return
+ value == null ||
+ value is string && ((string)value).Length == 0;
+ }
+
+ ///
+ /// Get total count of cells in the specified range without initalizing them all
+ /// (which might cause serious performance issues on column-wide calculations).
+ ///
+ /// Expression referring to the cell range.
+ /// Total number of cells in the range.
+ internal static long GetTotalCellsCount(XObjectExpression rangeExpression)
+ {
+ var range = ((rangeExpression)?.Value as CellRangeReference)?.Range;
+ if (range == null)
+ return 0;
+ return (long)(range.LastColumn().ColumnNumber() - range.FirstColumn().ColumnNumber() + 1) *
+ (long)(range.LastRow().RowNumber() - range.FirstRow().RowNumber() + 1);
+ }
}
}
diff --git a/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs b/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs
index 233a7c8..4eb642e 100644
--- a/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs
+++ b/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs
@@ -340,20 +340,22 @@
{
rangeValues.Add(value);
}
- var sumRangeValues = new List();
- foreach (var cell in ((CellRangeReference)sumRange.Value).Range.Cells())
+ var sumRangeValues = new List();
+ foreach (var value in sumRange)
{
- sumRangeValues.Add(cell);
+ sumRangeValues.Add(value);
}
// compute total
var ce = new CalcEngine();
var tally = new Tally();
- for (var i = 0; i < Math.Min(rangeValues.Count, sumRangeValues.Count); i++)
+ for (var i = 0; i < Math.Max(rangeValues.Count, sumRangeValues.Count); i++)
{
- if (CalcEngineHelpers.ValueSatisfiesCriteria(rangeValues[i], criteria, ce))
+ var targetValue = i < rangeValues.Count ? rangeValues[i] : string.Empty;
+ if (CalcEngineHelpers.ValueSatisfiesCriteria(targetValue, criteria, ce))
{
- tally.AddValue(sumRangeValues[i].Value);
+ var value = i < sumRangeValues.Count ? sumRangeValues[i] : 0d;
+ tally.AddValue(value);
}
}
@@ -401,7 +403,7 @@
foreach (var criteriaPair in criteriaRanges)
{
if (!CalcEngineHelpers.ValueSatisfiesCriteria(
- criteriaPair.Item2[i],
+ i < criteriaPair.Item2.Count ? criteriaPair.Item2[i] : string.Empty,
criteriaPair.Item1,
ce))
{
@@ -1028,4 +1030,4 @@
return m.Invert().mat;
}
}
-}
\ No newline at end of file
+}
diff --git a/ClosedXML/Excel/CalcEngine/Functions/Statistical.cs b/ClosedXML/Excel/CalcEngine/Functions/Statistical.cs
index 958221e..06f720a 100644
--- a/ClosedXML/Excel/CalcEngine/Functions/Statistical.cs
+++ b/ClosedXML/Excel/CalcEngine/Functions/Statistical.cs
@@ -1,7 +1,7 @@
using ClosedXML.Excel.CalcEngine.Exceptions;
using System;
-using System.Collections;
using System.Collections.Generic;
+using System.Linq;
namespace ClosedXML.Excel.CalcEngine
{
@@ -121,38 +121,40 @@
if ((p[0] as XObjectExpression)?.Value as CellRangeReference == null)
throw new NoValueAvailableException("COUNTBLANK should have a single argument which is a range reference");
- var cnt = 0.0;
var e = p[0] as XObjectExpression;
+ long totalCount = CalcEngineHelpers.GetTotalCellsCount(e);
+ long nonBlankCount = 0;
foreach (var value in e)
{
- if (IsBlank(value))
- cnt++;
+ if (!CalcEngineHelpers.ValueIsBlank(value))
+ nonBlankCount++;
}
- return cnt;
- }
-
- internal static bool IsBlank(object value)
- {
- return
- value == null ||
- value is string && ((string)value).Length == 0;
+ return 0d + totalCount - nonBlankCount;
}
private static object CountIf(List p)
{
CalcEngine ce = new CalcEngine();
var cnt = 0.0;
- var ienum = p[0] as IEnumerable;
+ long processedCount = 0;
+ var ienum = p[0] as XObjectExpression;
if (ienum != null)
{
+ long totalCount = CalcEngineHelpers.GetTotalCellsCount(ienum);
var criteria = (string)p[1].Evaluate();
foreach (var value in ienum)
{
if (CalcEngineHelpers.ValueSatisfiesCriteria(value, criteria, ce))
cnt++;
+ processedCount++;
}
+
+ // Add count of empty cells outside the used range if they match criteria
+ if (CalcEngineHelpers.ValueSatisfiesCriteria(string.Empty, criteria, ce))
+ cnt += (totalCount - processedCount);
}
+
return cnt;
}
@@ -160,15 +162,16 @@
{
// get parameters
var ce = new CalcEngine();
- int count = 0;
+ long count = 0;
int numberOfCriteria = p.Count / 2;
+ long totalCount = 0;
// prepare criteria-parameters:
var criteriaRanges = new Tuple>[numberOfCriteria];
for (int criteriaPair = 0; criteriaPair < numberOfCriteria; criteriaPair++)
{
- var criteriaRange = p[criteriaPair * 2] as IEnumerable;
+ var criteriaRange = p[criteriaPair * 2] as XObjectExpression;
var criterion = p[(criteriaPair * 2) + 1].Evaluate();
var criteriaRangeValues = new List();
foreach (var value in criteriaRange)
@@ -179,26 +182,26 @@
criteriaRanges[criteriaPair] = new Tuple>(
criterion,
criteriaRangeValues);
+
+ if (totalCount == 0)
+ totalCount = CalcEngineHelpers.GetTotalCellsCount(criteriaRange);
}
+ long processedCount = 0;
for (var i = 0; i < criteriaRanges[0].Item2.Count; i++)
{
- bool shouldUseValue = true;
-
- foreach (var criteriaPair in criteriaRanges)
- {
- if (!CalcEngineHelpers.ValueSatisfiesCriteria(
- criteriaPair.Item2[i],
- criteriaPair.Item1,
- ce))
- {
- shouldUseValue = false;
- break; // we're done with the inner loop as we can't ever get true again.
- }
- }
-
- if (shouldUseValue)
+ if (criteriaRanges.All(criteriaPair => CalcEngineHelpers.ValueSatisfiesCriteria(
+ criteriaPair.Item2[i], criteriaPair.Item1, ce)))
count++;
+
+ processedCount++;
+ }
+
+ // Add count of empty cells outside the used range if they match criteria
+ if (criteriaRanges.All(criteriaPair => CalcEngineHelpers.ValueSatisfiesCriteria(
+ string.Empty, criteriaPair.Item1, ce)))
+ {
+ count += (totalCount - processedCount);
}
// done
diff --git a/ClosedXML/Excel/CalcEngine/Functions/Tally.cs b/ClosedXML/Excel/CalcEngine/Functions/Tally.cs
index 8796b84..93abf67 100644
--- a/ClosedXML/Excel/CalcEngine/Functions/Tally.cs
+++ b/ClosedXML/Excel/CalcEngine/Functions/Tally.cs
@@ -80,7 +80,7 @@
if (numbersOnly)
return NumericValuesInternal().Length;
else
- return _list.Count(o => !Statistical.IsBlank(o));
+ return _list.Count(o => !CalcEngineHelpers.ValueIsBlank(o));
}
private IEnumerable NumericValuesEnumerable()
diff --git a/ClosedXML/Excel/CalcEngine/XLCalcEngine.cs b/ClosedXML/Excel/CalcEngine/XLCalcEngine.cs
index 108ea40..da2f83b 100644
--- a/ClosedXML/Excel/CalcEngine/XLCalcEngine.cs
+++ b/ClosedXML/Excel/CalcEngine/XLCalcEngine.cs
@@ -82,7 +82,10 @@
// ** IEnumerable
public IEnumerator GetEnumerator()
{
- return _range.Cells().Select(GetValue).GetEnumerator();
+ var maxRow = Math.Min(_range.RangeAddress.LastAddress.RowNumber, _range.Worksheet.LastCellUsed().Address.RowNumber);
+ var maxCol = Math.Min(_range.RangeAddress.LastAddress.ColumnNumber, _range.Worksheet.LastCellUsed().Address.ColumnNumber);
+ using (var trimmedRange = (XLRangeBase)_range.Worksheet.Range(_range.FirstCell().Address, new XLAddress(maxRow, maxCol, false, false)))
+ return trimmedRange.CellValues().GetEnumerator();
}
private Boolean _evaluating;
@@ -90,9 +93,9 @@
// ** implementation
private object GetValue(IXLCell cell)
{
- if (_evaluating)
+ if (_evaluating || (cell as XLCell).IsEvaluating)
{
- throw new InvalidOperationException("Circular Reference");
+ throw new InvalidOperationException($"Circular Reference occured during evaluation. Cell: {cell.Address.ToString(XLReferenceStyle.Default, true)}");
}
try
{
diff --git a/ClosedXML/Excel/Cells/IXLCell.cs b/ClosedXML/Excel/Cells/IXLCell.cs
index b46bf38..274a50f 100644
--- a/ClosedXML/Excel/Cells/IXLCell.cs
+++ b/ClosedXML/Excel/Cells/IXLCell.cs
@@ -319,6 +319,8 @@
Boolean IsMerged();
+ IXLRange MergedRange();
+
Boolean IsEmpty();
Boolean IsEmpty(Boolean includeFormats);
diff --git a/ClosedXML/Excel/Cells/XLCell.cs b/ClosedXML/Excel/Cells/XLCell.cs
index 094119f..8a4a708 100644
--- a/ClosedXML/Excel/Cells/XLCell.cs
+++ b/ClosedXML/Excel/Cells/XLCell.cs
@@ -359,6 +359,11 @@
return cValue;
}
+ ///
+ /// Flag showing that the cell is in formula evaluation state.
+ ///
+ internal bool IsEvaluating { get; private set; }
+
public object Value
{
get
@@ -366,6 +371,9 @@
var fA1 = FormulaA1;
if (!String.IsNullOrWhiteSpace(fA1))
{
+ if (IsEvaluating)
+ throw new InvalidOperationException("Circular Reference");
+
if (fA1[0] == '{')
fA1 = fA1.Substring(1, fA1.Length - 2);
@@ -385,19 +393,31 @@
cAddress = fA1;
}
- if (_worksheet.Workbook.WorksheetsInternal.Any(
- w => String.Compare(w.Name, sName, true) == 0)
- && XLHelper.IsValidA1Address(cAddress)
- )
+ object retVal;
+ try
{
- var referenceCell = _worksheet.Workbook.Worksheet(sName).Cell(cAddress);
- if (referenceCell.IsEmpty(false))
- return 0;
- else
- return referenceCell.Value;
+ IsEvaluating = true;
+
+ if (_worksheet
+ .Workbook
+ .WorksheetsInternal
+ .Any(w => String.Compare(w.Name, sName, true) == 0)
+ && XLHelper.IsValidA1Address(cAddress))
+ {
+ var referenceCell = _worksheet.Workbook.Worksheet(sName).Cell(cAddress);
+ if (referenceCell.IsEmpty(false))
+ return 0;
+ else
+ return referenceCell.Value;
+ }
+
+ retVal = Worksheet.Evaluate(fA1);
+ }
+ finally
+ {
+ IsEvaluating = false;
}
- var retVal = Worksheet.Evaluate(fA1);
var retValEnumerable = retVal as IEnumerable;
if (retValEnumerable != null && !(retVal is String))
@@ -1217,6 +1237,11 @@
return Worksheet.Internals.MergedRanges.Contains(this);
}
+ public IXLRange MergedRange()
+ {
+ return Worksheet.Internals.MergedRanges.FirstOrDefault(r => r.Contains(this));
+ }
+
public Boolean IsEmpty()
{
return IsEmpty(false);
diff --git a/ClosedXML/Excel/Columns/XLColumn.cs b/ClosedXML/Excel/Columns/XLColumn.cs
index 5bb1f50..9521d1d 100644
--- a/ClosedXML/Excel/Columns/XLColumn.cs
+++ b/ClosedXML/Excel/Columns/XLColumn.cs
@@ -623,16 +623,19 @@
private void WorksheetRangeShiftedColumns(XLRange range, int columnsShifted)
{
- if (range.RangeAddress.FirstAddress.ColumnNumber <= ColumnNumber())
+ if (range.RangeAddress.IsValid &&
+ RangeAddress.IsValid &&
+ range.RangeAddress.FirstAddress.ColumnNumber <= ColumnNumber())
SetColumnNumber(ColumnNumber() + columnsShifted);
}
private void SetColumnNumber(int column)
{
if (column <= 0)
- RangeAddress.IsInvalid = false;
+ RangeAddress.IsValid = false;
else
{
+ RangeAddress.IsValid = true;
RangeAddress.FirstAddress = new XLAddress(Worksheet,
1,
column,
diff --git a/ClosedXML/Excel/ConditionalFormats/IXLConditionalFormats.cs b/ClosedXML/Excel/ConditionalFormats/IXLConditionalFormats.cs
index 8ccfbdd..cae5f8c 100644
--- a/ClosedXML/Excel/ConditionalFormats/IXLConditionalFormats.cs
+++ b/ClosedXML/Excel/ConditionalFormats/IXLConditionalFormats.cs
@@ -1,14 +1,14 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
namespace ClosedXML.Excel
{
- public interface IXLConditionalFormats: IEnumerable
+ public interface IXLConditionalFormats : IEnumerable
{
void Add(IXLConditionalFormat conditionalFormat);
+
void RemoveAll();
+
void Remove(Predicate predicate);
}
}
diff --git a/ClosedXML/Excel/ConditionalFormats/Save/XLCFColorScaleConverter.cs b/ClosedXML/Excel/ConditionalFormats/Save/XLCFColorScaleConverter.cs
index e7fabb8..5fc548c 100644
--- a/ClosedXML/Excel/ConditionalFormats/Save/XLCFColorScaleConverter.cs
+++ b/ClosedXML/Excel/ConditionalFormats/Save/XLCFColorScaleConverter.cs
@@ -24,7 +24,22 @@
for (Int32 i = 1; i <= cf.Colors.Count; i++)
{
- Color color = new Color { Rgb = cf.Colors[i].Color.ToHex() };
+ var xlColor = cf.Colors[i];
+ var color = new Color();
+ switch (xlColor.ColorType)
+ {
+ case XLColorType.Color:
+ color.Rgb = xlColor.Color.ToHex();
+ break;
+ case XLColorType.Theme:
+ color.Theme = System.Convert.ToUInt32(xlColor.ThemeColor);
+ break;
+
+ case XLColorType.Indexed:
+ color.Indexed = System.Convert.ToUInt32(xlColor.Indexed);
+ break;
+ }
+
colorScale.Append(color);
}
diff --git a/ClosedXML/Excel/ConditionalFormats/XLConditionalFormat.cs b/ClosedXML/Excel/ConditionalFormats/XLConditionalFormat.cs
index 7263e7e..0140281 100644
--- a/ClosedXML/Excel/ConditionalFormats/XLConditionalFormat.cs
+++ b/ClosedXML/Excel/ConditionalFormats/XLConditionalFormat.cs
@@ -138,6 +138,7 @@
}
public Guid Id { get; internal set; }
+ internal Int32 OriginalPriority { get; set; }
public Boolean CopyDefaultModify { get; set; }
public override IEnumerable Styles
diff --git a/ClosedXML/Excel/ConditionalFormats/XLConditionalFormats.cs b/ClosedXML/Excel/ConditionalFormats/XLConditionalFormats.cs
index 940bd22..f986db8 100644
--- a/ClosedXML/Excel/ConditionalFormats/XLConditionalFormats.cs
+++ b/ClosedXML/Excel/ConditionalFormats/XLConditionalFormats.cs
@@ -110,5 +110,15 @@
_conditionalFormats.ForEach(cf => cf.Range.Dispose());
_conditionalFormats.Clear();
}
+
+ ///
+ /// Reorders the according to original priority. Done during load process
+ ///
+ public void ReorderAccordingToOriginalPriority()
+ {
+ var reorderedFormats = _conditionalFormats.OrderBy(cf => (cf as XLConditionalFormat).OriginalPriority).ToList();
+ _conditionalFormats.Clear();
+ _conditionalFormats.AddRange(reorderedFormats);
+ }
}
}
diff --git a/ClosedXML/Excel/PivotTables/XLPivotField.cs b/ClosedXML/Excel/PivotTables/XLPivotField.cs
index dbdfac9..1f8ac67 100644
--- a/ClosedXML/Excel/PivotTables/XLPivotField.cs
+++ b/ClosedXML/Excel/PivotTables/XLPivotField.cs
@@ -5,7 +5,7 @@
namespace ClosedXML.Excel
{
[DebuggerDisplay("{SourceName}")]
- public class XLPivotField : IXLPivotField
+ internal class XLPivotField : IXLPivotField
{
public XLPivotField(string sourceName)
{
diff --git a/ClosedXML/Excel/Ranges/IXLRangeAddress.cs b/ClosedXML/Excel/Ranges/IXLRangeAddress.cs
index 86ed2b1..751f1a4 100644
--- a/ClosedXML/Excel/Ranges/IXLRangeAddress.cs
+++ b/ClosedXML/Excel/Ranges/IXLRangeAddress.cs
@@ -13,12 +13,12 @@
IXLAddress FirstAddress { get; set; }
///
- /// Gets or sets a value indicating whether this range is invalid.
+ /// Gets or sets a value indicating whether this range is valid.
///
///
- /// true if this instance is invalid; otherwise, false .
+ /// true if this instance is valid; otherwise, false .
///
- Boolean IsInvalid { get; set; }
+ Boolean IsValid { get; }
///
/// Gets or sets the last address in the range.
diff --git a/ClosedXML/Excel/Ranges/IXLRangeBase.cs b/ClosedXML/Excel/Ranges/IXLRangeBase.cs
index 4707327..0932cbf 100644
--- a/ClosedXML/Excel/Ranges/IXLRangeBase.cs
+++ b/ClosedXML/Excel/Ranges/IXLRangeBase.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Globalization;
namespace ClosedXML.Excel
diff --git a/ClosedXML/Excel/Ranges/XLRangeAddress.cs b/ClosedXML/Excel/Ranges/XLRangeAddress.cs
index 177677e..d900347 100644
--- a/ClosedXML/Excel/Ranges/XLRangeAddress.cs
+++ b/ClosedXML/Excel/Ranges/XLRangeAddress.cs
@@ -84,7 +84,7 @@
{
get
{
- if (IsInvalid)
+ if (!IsValid)
throw new InvalidOperationException("Range is invalid.");
return _firstAddress;
@@ -96,7 +96,7 @@
{
get
{
- if (IsInvalid)
+ if (!IsValid)
throw new InvalidOperationException("Range is an invalid state.");
return _lastAddress;
@@ -123,7 +123,7 @@
set { LastAddress = value as XLAddress; }
}
- public bool IsInvalid { get; set; }
+ public bool IsValid { get; set; } = true;
#endregion Public properties
diff --git a/ClosedXML/Excel/Ranges/XLRangeBase.cs b/ClosedXML/Excel/Ranges/XLRangeBase.cs
index 9923a91..f2a9831 100644
--- a/ClosedXML/Excel/Ranges/XLRangeBase.cs
+++ b/ClosedXML/Excel/Ranges/XLRangeBase.cs
@@ -1,6 +1,7 @@
using ClosedXML.Excel.Misc;
using ClosedXML.Extensions;
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -276,6 +277,20 @@
return Cells(true);
}
+ ///
+ /// Return the collection of cell values not initializing empty cells.
+ ///
+ public IEnumerable CellValues()
+ {
+ for (int ro = RangeAddress.FirstAddress.RowNumber; ro <= RangeAddress.LastAddress.RowNumber; ro++)
+ {
+ for (int co = RangeAddress.FirstAddress.ColumnNumber; co <= RangeAddress.LastAddress.ColumnNumber; co++)
+ {
+ yield return Worksheet.GetCellValue(ro, co);
+ }
+ }
+ }
+
public IXLRange Merge()
{
return Merge(true);
@@ -434,7 +449,7 @@
public bool Intersects(IXLRangeBase range)
{
- if (range.RangeAddress.IsInvalid || RangeAddress.IsInvalid)
+ if (!range.RangeAddress.IsValid || !RangeAddress.IsValid)
return false;
var ma = range.RangeAddress;
var ra = RangeAddress;
@@ -1519,7 +1534,7 @@
protected void ShiftColumns(IXLRangeAddress thisRangeAddress, XLRange shiftedRange, int columnsShifted)
{
- if (thisRangeAddress.IsInvalid || shiftedRange.RangeAddress.IsInvalid) return;
+ if (!thisRangeAddress.IsValid || !shiftedRange.RangeAddress.IsValid) return;
bool allRowsAreCovered = thisRangeAddress.FirstAddress.RowNumber >= shiftedRange.RangeAddress.FirstAddress.RowNumber &&
thisRangeAddress.LastAddress.RowNumber <= shiftedRange.RangeAddress.LastAddress.RowNumber;
@@ -1549,7 +1564,7 @@
if (destroyedByShift)
{
- thisRangeAddress.IsInvalid = true;
+ (thisRangeAddress as XLRangeAddress).IsValid = false;
return;
}
@@ -1570,7 +1585,7 @@
protected void ShiftRows(IXLRangeAddress thisRangeAddress, XLRange shiftedRange, int rowsShifted)
{
- if (thisRangeAddress.IsInvalid || shiftedRange.RangeAddress.IsInvalid) return;
+ if (!thisRangeAddress.IsValid || !shiftedRange.RangeAddress.IsValid) return;
bool allColumnsAreCovered = thisRangeAddress.FirstAddress.ColumnNumber >= shiftedRange.RangeAddress.FirstAddress.ColumnNumber &&
thisRangeAddress.LastAddress.ColumnNumber <= shiftedRange.RangeAddress.LastAddress.ColumnNumber;
@@ -1600,7 +1615,7 @@
if (destroyedByShift)
{
- thisRangeAddress.IsInvalid = true;
+ (thisRangeAddress as XLRangeAddress).IsValid = false;
return;
}
diff --git a/ClosedXML/Excel/Ranges/XLRanges.cs b/ClosedXML/Excel/Ranges/XLRanges.cs
index a41fd8b..1acc4a7 100644
--- a/ClosedXML/Excel/Ranges/XLRanges.cs
+++ b/ClosedXML/Excel/Ranges/XLRanges.cs
@@ -61,12 +61,12 @@
public Boolean Contains(IXLCell cell)
{
- return _ranges.Any(r => !r.RangeAddress.IsInvalid && r.Contains(cell));
+ return _ranges.Any(r => r.RangeAddress.IsValid && r.Contains(cell));
}
public Boolean Contains(IXLRange range)
{
- return _ranges.Any(r => !r.RangeAddress.IsInvalid && r.Contains(range));
+ return _ranges.Any(r => r.RangeAddress.IsValid && r.Contains(range));
}
public IEnumerable DataValidation
diff --git a/ClosedXML/Excel/Rows/XLRow.cs b/ClosedXML/Excel/Rows/XLRow.cs
index 12da17a..53e4008 100644
--- a/ClosedXML/Excel/Rows/XLRow.cs
+++ b/ClosedXML/Excel/Rows/XLRow.cs
@@ -552,16 +552,19 @@
private void WorksheetRangeShiftedRows(XLRange range, int rowsShifted)
{
- if (range.RangeAddress.FirstAddress.RowNumber <= RowNumber())
+ if (range.RangeAddress.IsValid &&
+ RangeAddress.IsValid &&
+ range.RangeAddress.FirstAddress.RowNumber <= RowNumber())
SetRowNumber(RowNumber() + rowsShifted);
}
private void SetRowNumber(Int32 row)
{
if (row <= 0)
- RangeAddress.IsInvalid = false;
+ RangeAddress.IsValid = false;
else
{
+ RangeAddress.IsValid = true;
RangeAddress.FirstAddress = new XLAddress(Worksheet, row, 1, RangeAddress.FirstAddress.FixedRow,
RangeAddress.FirstAddress.FixedColumn);
RangeAddress.LastAddress = new XLAddress(Worksheet,
diff --git a/ClosedXML/Excel/XLWorkbook_Load.cs b/ClosedXML/Excel/XLWorkbook_Load.cs
index 0d88e30..5e311d9 100644
--- a/ClosedXML/Excel/XLWorkbook_Load.cs
+++ b/ClosedXML/Excel/XLWorkbook_Load.cs
@@ -276,6 +276,8 @@
reader.Close();
}
+ (ws.ConditionalFormats as XLConditionalFormats).ReorderAccordingToOriginalPriority();
+
#region LoadTables
foreach (var tableDefinitionPart in worksheetPart.TableDefinitionParts)
@@ -2145,6 +2147,7 @@
// The conditional formatting type is compulsory. If it doesn't exist, skip the entire rule.
if (fr.Type == null) continue;
conditionalFormat.ConditionalFormatType = fr.Type.Value.ToClosedXml();
+ conditionalFormat.OriginalPriority = fr.Priority?.Value ?? Int32.MaxValue;
if (conditionalFormat.ConditionalFormatType == XLConditionalFormatType.CellIs && fr.Operator != null)
conditionalFormat.Operator = fr.Operator.Value.ToClosedXml();
diff --git a/ClosedXML/Excel/XLWorkbook_Save.cs b/ClosedXML/Excel/XLWorkbook_Save.cs
index ebce78e..c360c17 100644
--- a/ClosedXML/Excel/XLWorkbook_Save.cs
+++ b/ClosedXML/Excel/XLWorkbook_Save.cs
@@ -3360,6 +3360,11 @@
AddDifferentialFormats(workbookStylesPart, context);
}
+ ///
+ /// Populates the differential formats that are currently in the file to the SaveContext
+ ///
+ /// The workbook styles part.
+ /// The context.
private void AddDifferentialFormats(WorkbookStylesPart workbookStylesPart, SaveContext context)
{
if (workbookStylesPart.Stylesheet.DifferentialFormats == null)
@@ -3424,7 +3429,11 @@
SaveContext context)
{
var differentialFormat = new DifferentialFormat();
- differentialFormat.Append(GetNewFont(new FontInfo { Font = cf.Style.Value.Font }, false));
+
+ var diffFont = GetNewFont(new FontInfo { Font = cf.Style.Font as XLFont }, false);
+ if (diffFont?.HasChildren ?? false)
+ differentialFormat.Append(diffFont);
+
if (!String.IsNullOrWhiteSpace(cf.Style.NumberFormat.Format))
{
var numberFormat = new NumberingFormat
@@ -3434,8 +3443,14 @@
};
differentialFormat.Append(numberFormat);
}
- differentialFormat.Append(GetNewFill(new FillInfo { Fill = cf.Style.Value.Fill }, differentialFillFormat: true, ignoreMod: false));
- differentialFormat.Append(GetNewBorder(new BorderInfo { Border = cf.Style.Value.Border }, false));
+
+ var diffFill = GetNewFill(new FillInfo { Fill = cf.Style.Value.Fill }, differentialFillFormat: true, ignoreMod: false);
+ if (diffFill?.HasChildren ?? false)
+ differentialFormat.Append(diffFill);
+
+ var diffBorder = GetNewBorder(new BorderInfo { Border = cf.Style.Value.Border }, false);
+ if (diffBorder?.HasChildren ?? false)
+ differentialFormat.Append(diffBorder);
differentialFormats.Append(differentialFormat);
@@ -3447,8 +3462,8 @@
{
var differentialFormat = new DifferentialFormat();
- var diffFont = GetNewFont(new FontInfo {Font = style.Font}, false);
- if (diffFont.HasChildren)
+ var diffFont = GetNewFont(new FontInfo { Font = style.Font as XLFont }, false);
+ if (diffFont?.HasChildren ?? false)
differentialFormat.Append(diffFont);
if (!String.IsNullOrWhiteSpace(style.NumberFormat.Format) || style.NumberFormat.NumberFormatId != 0)
@@ -3476,11 +3491,11 @@
}
var diffFill = GetNewFill(new FillInfo {Fill = style.Fill}, differentialFillFormat: true, ignoreMod: false);
- if (diffFill.HasChildren)
+ if (diffFill?.HasChildren ?? false)
differentialFormat.Append(diffFill);
var diffBorder = GetNewBorder(new BorderInfo {Border = style.Border}, false);
- if (diffBorder.HasChildren)
+ if (diffBorder?.HasChildren ?? false)
differentialFormat.Append(diffBorder);
differentialFormats.Append(differentialFormat);
@@ -3893,7 +3908,9 @@
break;
case XLColorType.Indexed:
- backgroundColor.Indexed = (UInt32)fillInfo.Fill.BackgroundColor.Indexed;
+ // 64 is 'transparent' and should be ignored for differential formats
+ if (fillInfo.Fill.BackgroundColor.Indexed != 64)
+ backgroundColor.Indexed = (UInt32)fillInfo.Fill.BackgroundColor.Indexed;
break;
case XLColorType.Theme:
@@ -4971,8 +4988,9 @@
worksheetPart.Worksheet.RemoveAllChildren();
var previousElement = cm.GetPreviousElementFor(XLWSContentManager.XLWSContents.ConditionalFormatting);
- var priority = 1; // priority is 1 origin in Microsoft Excel
- foreach (var cfGroup in xlWorksheet.ConditionalFormats
+ var conditionalFormats = xlWorksheet.ConditionalFormats.ToList(); // Required for IndexOf method
+
+ foreach (var cfGroup in conditionalFormats
.GroupBy(
c => c.Range.RangeAddress.ToStringRelative(false),
c => c,
@@ -4987,8 +5005,8 @@
};
foreach (var cf in cfGroup.CfList)
{
+ var priority = conditionalFormats.IndexOf(cf) + 1;
conditionalFormatting.Append(XLCFConverters.Convert(cf, priority, context));
- priority++;
}
worksheetPart.Worksheet.InsertAfter(conditionalFormatting, previousElement);
previousElement = conditionalFormatting;
diff --git a/ClosedXML/Excel/XLWorksheet.cs b/ClosedXML/Excel/XLWorksheet.cs
index c47cc79..b0648cd 100644
--- a/ClosedXML/Excel/XLWorksheet.cs
+++ b/ClosedXML/Excel/XLWorksheet.cs
@@ -1213,7 +1213,7 @@
cfAddress.LastAddress.RowNumber,
cfAddress.LastAddress.ColumnNumber + columnsShifted);
}
- if (cf.Range.RangeAddress.IsInvalid ||
+ if (!cf.Range.RangeAddress.IsValid ||
cf.Range.RangeAddress.FirstAddress.ColumnNumber > cf.Range.RangeAddress.LastAddress.ColumnNumber)
ConditionalFormats.Remove(f => f == cf);
}
@@ -1287,7 +1287,7 @@
cfAddress.LastAddress.RowNumber + rowsShifted,
cfAddress.LastAddress.ColumnNumber);
}
- if (cf.Range.RangeAddress.IsInvalid ||
+ if (!cf.Range.RangeAddress.IsValid ||
cf.Range.RangeAddress.FirstAddress.RowNumber > cf.Range.RangeAddress.LastAddress.RowNumber)
ConditionalFormats.Remove(f => f == cf);
}
@@ -1549,5 +1549,25 @@
else
this.Cell(ro, co).SetValue(value);
}
+
+ ///
+ /// Get a cell value not initializing it if it has not been initialized yet.
+ ///
+ /// Row number
+ /// Column number
+ /// Current value of the specified cell. Empty string for non-initialized cells.
+ internal object GetCellValue(int ro, int co)
+ {
+ if (Internals.CellsCollection.MaxRowUsed < ro ||
+ Internals.CellsCollection.MaxColumnUsed < co ||
+ !Internals.CellsCollection.Contains(ro, co))
+ return string.Empty;
+
+ var cell = Worksheet.Internals.CellsCollection.GetCell(ro, co);
+ if (cell.IsEvaluating)
+ return string.Empty;
+
+ return cell.Value;
+ }
}
}
diff --git a/ClosedXML/XLHelper.cs b/ClosedXML/XLHelper.cs
index dd08d44..1c1081d 100644
--- a/ClosedXML/XLHelper.cs
+++ b/ClosedXML/XLHelper.cs
@@ -175,7 +175,7 @@
public static Boolean IsValidRangeAddress(IXLRangeAddress rangeAddress)
{
- return !rangeAddress.IsInvalid
+ return rangeAddress.IsValid
&& rangeAddress.FirstAddress.RowNumber >= 1 && rangeAddress.LastAddress.RowNumber <= MaxRowNumber
&& rangeAddress.FirstAddress.ColumnNumber >= 1 && rangeAddress.LastAddress.ColumnNumber <= MaxColumnNumber
&& rangeAddress.FirstAddress.RowNumber <= rangeAddress.LastAddress.RowNumber
diff --git a/ClosedXML_Tests/ClosedXML_Tests.csproj b/ClosedXML_Tests/ClosedXML_Tests.csproj
index 56ecb40..43cb231 100644
--- a/ClosedXML_Tests/ClosedXML_Tests.csproj
+++ b/ClosedXML_Tests/ClosedXML_Tests.csproj
@@ -93,6 +93,7 @@
+
@@ -312,6 +313,10 @@
+
+
+
+
diff --git a/ClosedXML_Tests/Excel/AutoFilters/AutoFilterTests.cs b/ClosedXML_Tests/Excel/AutoFilters/AutoFilterTests.cs
index 6b68014..20d17f0 100644
--- a/ClosedXML_Tests/Excel/AutoFilters/AutoFilterTests.cs
+++ b/ClosedXML_Tests/Excel/AutoFilters/AutoFilterTests.cs
@@ -125,7 +125,7 @@
}
}
}
-
+
[Test]
[TestCase("A1:A4")]
[TestCase("A1:B4")]
@@ -152,7 +152,7 @@
range.InsertColumnsBefore(1);
//Assert
- Assert.IsFalse(ws.AutoFilter.Range.RangeAddress.IsInvalid);
+ Assert.IsTrue(ws.AutoFilter.Range.RangeAddress.IsValid);
}
}
}
diff --git a/ClosedXML_Tests/Excel/CalcEngine/StatisticalTests.cs b/ClosedXML_Tests/Excel/CalcEngine/StatisticalTests.cs
index 7a432e1..3f451f1 100644
--- a/ClosedXML_Tests/Excel/CalcEngine/StatisticalTests.cs
+++ b/ClosedXML_Tests/Excel/CalcEngine/StatisticalTests.cs
@@ -176,7 +176,7 @@
ws.Cell(4, 3).Value = "Yes";
ws.Cell(4, 4).Value = "Yes";
- Assert.AreEqual(expectedOutcome, (int)ws.Evaluate(formula));
+ Assert.AreEqual(expectedOutcome, ws.Evaluate(formula));
}
}
@@ -329,6 +329,36 @@
Assert.AreEqual(2189.430863, value, tolerance);
}
+ [Test]
+ [TestCase("COUNT(G:I,G:G,H:I)", 258d, Description = "COUNT overlapping columns")]
+ [TestCase("COUNT(6:8,6:6,7:8)", 30d, Description = "COUNT overlapping rows")]
+ [TestCase("COUNTBLANK(H:J)", 3145640d, Description = "COUNTBLANK columns")]
+ [TestCase("COUNTBLANK(7:9)", 49128d, Description = "COUNTBLANK rows")]
+ [TestCase("COUNT(1:1048576)", 216d, Description = "COUNT worksheet")]
+ [TestCase("COUNTBLANK(1:1048576)", 17179868831d, Description = "COUNTBLANK worksheet")]
+ [TestCase("SUM(H:J)", 20501.15d, Description = "SUM columns")]
+ [TestCase("SUM(4:5)", 85366.12d, Description = "SUM rows")]
+ [TestCase("SUMIF(G:G,50,H:H)", 24.98d, Description = "SUMIF columns")]
+ [TestCase("SUMIF(G23:G52,\"\",H3:H32)", 53.24d, Description = "SUMIF ranges")]
+ [TestCase("SUMIFS(H:H,G:G,50,I:I,\">900\")", 19.99d, Description = "SUMIFS columns")]
+ public void TallySkipsEmptyCells(string formulaA1, double expectedResult)
+ {
+ using (var wb = SetupWorkbook())
+ {
+ var ws = wb.Worksheets.First();
+ //Let's pre-initialize cells we need so they didn't affect the result
+ ws.Range("A1:J45").Style.Fill.BackgroundColor = XLColor.Amber;
+ ws.Cell("ZZ1000").Value = 1;
+ int initialCount = (ws as XLWorksheet).Internals.CellsCollection.Count;
+
+ var actualResult = (double)ws.Evaluate(formulaA1);
+ int cellsCount = (ws as XLWorksheet).Internals.CellsCollection.Count;
+
+ Assert.AreEqual(expectedResult, actualResult, tolerance);
+ Assert.AreEqual(initialCount, cellsCount);
+ }
+ }
+
private XLWorkbook SetupWorkbook()
{
var wb = new XLWorkbook();
diff --git a/ClosedXML_Tests/Excel/Cells/XLCellTests.cs b/ClosedXML_Tests/Excel/Cells/XLCellTests.cs
index a6daf62..2173c3e 100644
--- a/ClosedXML_Tests/Excel/Cells/XLCellTests.cs
+++ b/ClosedXML_Tests/Excel/Cells/XLCellTests.cs
@@ -524,6 +524,7 @@
{
Assert.AreEqual("B1:E3", c.CurrentRegion.RangeAddress.ToString());
}
+
Assert.AreEqual("B1:E4", ws.Cell("E4").CurrentRegion.RangeAddress.ToString());
//// SECOND REGION
@@ -531,6 +532,7 @@
{
Assert.AreEqual("F1:H4", c.CurrentRegion.RangeAddress.ToString());
}
+
Assert.AreEqual("F1:H5", ws.Cell("F5").CurrentRegion.RangeAddress.ToString());
//// DIAGONAL
@@ -613,5 +615,37 @@
Assert.AreEqual("B2", cell.FormulaA1);
}
}
+
+ [Test]
+ public void FormulaWithCircularReferenceFails()
+ {
+ using (var wb = new XLWorkbook())
+ {
+ var ws = wb.AddWorksheet("Sheet1");
+ var A1 = ws.Cell("A1");
+ var A2 = ws.Cell("A2");
+ A1.FormulaA1 = "A2 + 1";
+ A2.FormulaA1 = "A1 + 1";
+
+ Assert.Throws(() =>
+ {
+ var _ = A1.Value;
+ });
+ Assert.Throws(() =>
+ {
+ var _ = A2.Value;
+ });
+ }
+ }
+
+ public void FormulaWithCircularReferenceFails2()
+ {
+ var cell = new XLWorkbook().Worksheets.Add("Sheet1").FirstCell();
+ cell.FormulaA1 = "A1";
+ Assert.Throws(() =>
+ {
+ var _ = cell.Value;
+ });
+ }
}
}
diff --git a/ClosedXML_Tests/Excel/Columns/ColumnTests.cs b/ClosedXML_Tests/Excel/Columns/ColumnTests.cs
index 5410863..0d13b93 100644
--- a/ClosedXML_Tests/Excel/Columns/ColumnTests.cs
+++ b/ClosedXML_Tests/Excel/Columns/ColumnTests.cs
@@ -237,5 +237,14 @@
Assert.AreEqual(2, lastCoUsed);
}
+ [Test]
+ public void NegativeColumnNumberIsInvalid()
+ {
+ var ws = new XLWorkbook().AddWorksheet("Sheet1") as XLWorksheet;
+
+ var column = new XLColumn(-1, new XLColumnParameters(ws, 0, false));
+
+ Assert.IsFalse(column.RangeAddress.IsValid);
+ }
}
}
diff --git a/ClosedXML_Tests/Excel/ConditionalFormats/ConditionalFormatTests.cs b/ClosedXML_Tests/Excel/ConditionalFormats/ConditionalFormatTests.cs
new file mode 100644
index 0000000..983a5d6
--- /dev/null
+++ b/ClosedXML_Tests/Excel/ConditionalFormats/ConditionalFormatTests.cs
@@ -0,0 +1,31 @@
+using ClosedXML.Excel;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ClosedXML_Tests.Excel.ConditionalFormats
+{
+ [TestFixture]
+ public class ConditionalFormatTests
+ {
+ [Test]
+ public void MaintainConditionalFormattingOrder()
+ {
+ using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"StyleReferenceFiles\ConditionalFormattingOrder\inputfile.xlsx")))
+ using (var ms = new MemoryStream())
+ {
+ TestHelper.CreateAndCompare(() =>
+ {
+ var wb = new XLWorkbook(stream);
+ wb.SaveAs(ms);
+ return wb;
+ }, @"StyleReferenceFiles\ConditionalFormattingOrder\ConditionalFormattingOrder.xlsx");
+ }
+ }
+
+ }
+}
diff --git a/ClosedXML_Tests/Excel/Ranges/InsertingRangesTests.cs b/ClosedXML_Tests/Excel/Ranges/InsertingRangesTests.cs
index 87e90ed..57092e6 100644
--- a/ClosedXML_Tests/Excel/Ranges/InsertingRangesTests.cs
+++ b/ClosedXML_Tests/Excel/Ranges/InsertingRangesTests.cs
@@ -139,7 +139,7 @@
thisRange.WorksheetRangeShiftedColumns(shiftedRange, shiftedColumns);
- Assert.IsFalse(thisRange.RangeAddress.IsInvalid);
+ Assert.IsTrue(thisRange.RangeAddress.IsValid);
Assert.AreEqual(expectedRange, thisRange.RangeAddress.ToString());
}
}
@@ -157,7 +157,7 @@
thisRange.WorksheetRangeShiftedColumns(shiftedRange, shiftedColumns);
- Assert.IsTrue(thisRange.RangeAddress.IsInvalid);
+ Assert.IsFalse(thisRange.RangeAddress.IsValid);
}
}
@@ -192,7 +192,7 @@
thisRange.WorksheetRangeShiftedRows(shiftedRange, shiftedRows);
- Assert.IsFalse(thisRange.RangeAddress.IsInvalid);
+ Assert.IsTrue(thisRange.RangeAddress.IsValid);
Assert.AreEqual(expectedRange, thisRange.RangeAddress.ToString());
}
}
@@ -209,7 +209,7 @@
thisRange.WorksheetRangeShiftedRows(shiftedRange, shiftedRows);
- Assert.IsTrue(thisRange.RangeAddress.IsInvalid);
+ Assert.IsFalse(thisRange.RangeAddress.IsValid);
}
}
}
diff --git a/ClosedXML_Tests/Excel/Rows/RowTests.cs b/ClosedXML_Tests/Excel/Rows/RowTests.cs
index bb74d4f..6453623 100644
--- a/ClosedXML_Tests/Excel/Rows/RowTests.cs
+++ b/ClosedXML_Tests/Excel/Rows/RowTests.cs
@@ -253,5 +253,15 @@
ws.Rows(1, 2).Group();
ws.Rows(1, 2).Ungroup(true);
}
+
+ [Test]
+ public void NegativeRowNumberIsInvalid()
+ {
+ var ws = new XLWorkbook().AddWorksheet("Sheet1") as XLWorksheet;
+
+ var row = new XLRow(-1, new XLRowParameters(ws, 0, false));
+
+ Assert.IsFalse(row.RangeAddress.IsValid);
+ }
}
}
diff --git a/ClosedXML_Tests/Excel/Styles/XLFillTests.cs b/ClosedXML_Tests/Excel/Styles/XLFillTests.cs
index c858731..90183cb 100644
--- a/ClosedXML_Tests/Excel/Styles/XLFillTests.cs
+++ b/ClosedXML_Tests/Excel/Styles/XLFillTests.cs
@@ -1,5 +1,6 @@
using ClosedXML.Excel;
using NUnit.Framework;
+using System.IO;
namespace ClosedXML_Tests.Excel
{
@@ -12,30 +13,30 @@
[Test]
public void BackgroundColorSetsPattern()
{
- var fill = new XLFill {BackgroundColor = XLColor.Blue};
+ var fill = new XLFill { BackgroundColor = XLColor.Blue };
Assert.AreEqual(XLFillPatternValues.Solid, fill.PatternType);
}
[Test]
public void BackgroundNoColorSetsPatternNone()
{
- var fill = new XLFill {BackgroundColor = XLColor.NoColor};
+ var fill = new XLFill { BackgroundColor = XLColor.NoColor };
Assert.AreEqual(XLFillPatternValues.None, fill.PatternType);
}
[Test]
public void BackgroundPatternEqualCheck()
{
- var fill1 = new XLFill {BackgroundColor = XLColor.Blue};
- var fill2 = new XLFill {BackgroundColor = XLColor.Blue};
+ var fill1 = new XLFill { BackgroundColor = XLColor.Blue };
+ var fill2 = new XLFill { BackgroundColor = XLColor.Blue };
Assert.IsTrue(fill1.Equals(fill2));
}
[Test]
public void BackgroundPatternNotEqualCheck()
{
- var fill1 = new XLFill {BackgroundColor = XLColor.Blue};
- var fill2 = new XLFill {BackgroundColor = XLColor.Red};
+ var fill1 = new XLFill { BackgroundColor = XLColor.Blue };
+ var fill2 = new XLFill { BackgroundColor = XLColor.Red };
Assert.IsFalse(fill1.Equals(fill2));
}
@@ -61,5 +62,20 @@
Assert.AreEqual(style.Border.LeftBorderColor, XLColor.Blue);
Assert.AreEqual(style.Border.RightBorderColor, XLColor.Blue);
}
+
+ [Test]
+ public void LoadAndSaveTransparentBackgroundFill()
+ {
+ using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"StyleReferenceFiles\TransparentBackgroundFill\inputfile.xlsx")))
+ using (var ms = new MemoryStream())
+ {
+ TestHelper.CreateAndCompare(() =>
+ {
+ var wb = new XLWorkbook(stream);
+ wb.SaveAs(ms);
+ return wb;
+ }, @"StyleReferenceFiles\TransparentBackgroundFill\TransparentBackgroundFill.xlsx");
+ }
+ }
}
}
diff --git a/ClosedXML_Tests/Excel/Tables/TablesTests.cs b/ClosedXML_Tests/Excel/Tables/TablesTests.cs
index f8fcddc..ca50c3f 100644
--- a/ClosedXML_Tests/Excel/Tables/TablesTests.cs
+++ b/ClosedXML_Tests/Excel/Tables/TablesTests.cs
@@ -455,22 +455,19 @@
{
var ws = wb.AddWorksheet("Sheet1");
ws.FirstCell().InsertTable(l);
- //wb.SaveAs(ms);
-
- wb.SaveAs(@"c:\temp\deletetable1.xlsx");
+ wb.SaveAs(ms);
}
ms.Seek(0, SeekOrigin.Begin);
- using (var wb = new XLWorkbook(@"c:\temp\deletetable1.xlsx"))
+ using (var wb = new XLWorkbook(ms))
{
var ws = wb.Worksheets.First();
var table = ws.Tables.First();
ws.Tables.Remove(table.Name);
Assert.AreEqual(0, ws.Tables.Count());
- //wb.Save();
- wb.SaveAs(@"c:\temp\deletetable2.xlsx");
+ wb.Save();
}
}
}
diff --git a/ClosedXML_Tests/Excel/Worksheets/XLWorksheetTests.cs b/ClosedXML_Tests/Excel/Worksheets/XLWorksheetTests.cs
index 3a41da8..f87f008 100644
--- a/ClosedXML_Tests/Excel/Worksheets/XLWorksheetTests.cs
+++ b/ClosedXML_Tests/Excel/Worksheets/XLWorksheetTests.cs
@@ -110,6 +110,11 @@
Assert.AreEqual(2, ws.MergedRanges.Count);
Assert.AreEqual("A1:B2", ws.MergedRanges.First().RangeAddress.ToStringRelative());
Assert.AreEqual("D2:E2", ws.MergedRanges.Last().RangeAddress.ToStringRelative());
+
+ Assert.AreEqual("A1:B2", ws.Cell("A2").MergedRange().RangeAddress.ToStringRelative());
+ Assert.AreEqual("D2:E2", ws.Cell("D2").MergedRange().RangeAddress.ToStringRelative());
+
+ Assert.AreEqual(null, ws.Cell("Z10").MergedRange());
}
[Test]
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFContains.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFContains.xlsx
index a0c3791..8e4adf1 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFContains.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFContains.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFDatesOccurring.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFDatesOccurring.xlsx
index 6ec8da4..94f5caa 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFDatesOccurring.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFDatesOccurring.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEndsWith.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEndsWith.xlsx
index bfb06cd..80d08b7 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEndsWith.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEndsWith.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEqualsNumber.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEqualsNumber.xlsx
index bbfa6d4..13f828d 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEqualsNumber.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEqualsNumber.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEqualsString.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEqualsString.xlsx
index 4b6a481..8a25637 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEqualsString.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFEqualsString.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsBlank.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsBlank.xlsx
index 84f5931..e6fa5ec 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsBlank.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsBlank.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsError.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsError.xlsx
index 0677026..ad1c538 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsError.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsError.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFMultipleConditions.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFMultipleConditions.xlsx
index d396c20..565af0c 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFMultipleConditions.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFMultipleConditions.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotBlank.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotBlank.xlsx
index f2a8f5b..8861be4 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotBlank.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotBlank.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotContains.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotContains.xlsx
index 22026b6..8656701 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotContains.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotContains.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotEqualsNumber.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotEqualsNumber.xlsx
index 4eabc7c..6e23b6a 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotEqualsNumber.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotEqualsNumber.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotEqualsString.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotEqualsString.xlsx
index 72e5631..d39adb0 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotEqualsString.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotEqualsString.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotError.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotError.xlsx
index c808164..1ca0131 100644
--- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotError.xlsx
+++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotError.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/StyleReferenceFiles/ConditionalFormattingOrder/ConditionalFormattingOrder.xlsx b/ClosedXML_Tests/Resource/StyleReferenceFiles/ConditionalFormattingOrder/ConditionalFormattingOrder.xlsx
new file mode 100644
index 0000000..5f27c54
--- /dev/null
+++ b/ClosedXML_Tests/Resource/StyleReferenceFiles/ConditionalFormattingOrder/ConditionalFormattingOrder.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/StyleReferenceFiles/ConditionalFormattingOrder/inputfile.xlsx b/ClosedXML_Tests/Resource/StyleReferenceFiles/ConditionalFormattingOrder/inputfile.xlsx
new file mode 100644
index 0000000..28a7fcd
--- /dev/null
+++ b/ClosedXML_Tests/Resource/StyleReferenceFiles/ConditionalFormattingOrder/inputfile.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/StyleReferenceFiles/TransparentBackgroundFill/TransparentBackgroundFill.xlsx b/ClosedXML_Tests/Resource/StyleReferenceFiles/TransparentBackgroundFill/TransparentBackgroundFill.xlsx
new file mode 100644
index 0000000..3a12637
--- /dev/null
+++ b/ClosedXML_Tests/Resource/StyleReferenceFiles/TransparentBackgroundFill/TransparentBackgroundFill.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/Resource/StyleReferenceFiles/TransparentBackgroundFill/inputfile.xlsx b/ClosedXML_Tests/Resource/StyleReferenceFiles/TransparentBackgroundFill/inputfile.xlsx
new file mode 100644
index 0000000..6d6f1a8
--- /dev/null
+++ b/ClosedXML_Tests/Resource/StyleReferenceFiles/TransparentBackgroundFill/inputfile.xlsx
Binary files differ
diff --git a/ClosedXML_Tests/TestHelper.cs b/ClosedXML_Tests/TestHelper.cs
index 646d9d0..5c1acf8 100644
--- a/ClosedXML_Tests/TestHelper.cs
+++ b/ClosedXML_Tests/TestHelper.cs
@@ -22,16 +22,17 @@
{
get
{
- return Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
+ return Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "Generated");
}
}
public const string ActualTestResultPostFix = "";
- public static readonly string TestsExampleOutputDirectory = Path.Combine(TestsOutputDirectory, "Examples");
+ public static readonly string ExampleTestsOutputDirectory = Path.Combine(TestsOutputDirectory, "Examples");
+ public static readonly string OtherTestsOutputDirectory = Path.Combine(TestsOutputDirectory, "Other");
private const bool CompareWithResources = true;
- private static readonly ResourceFileExtractor _extractor = new ResourceFileExtractor(null, ".Resource.Examples.");
+ private static readonly ResourceFileExtractor _extractor = new ResourceFileExtractor(null, ".Resource.");
public static void SaveWorkbook(XLWorkbook workbook, params string[] fileNameParts)
{
@@ -59,7 +60,7 @@
var example = new T();
string[] pathParts = filePartName.Split(new char[] { '\\' });
- string filePath1 = Path.Combine(new List() { TestsExampleOutputDirectory }.Concat(pathParts).ToArray());
+ string filePath1 = Path.Combine(new List() { ExampleTestsOutputDirectory }.Concat(pathParts).ToArray());
var extension = Path.GetExtension(filePath1);
var directory = Path.GetDirectoryName(filePath1);
@@ -76,9 +77,46 @@
wb.SaveAs(filePath2, true, evaluateFormulae);
if (CompareWithResources)
-
{
- string resourcePath = filePartName.Replace('\\', '.').TrimStart('.');
+ string resourcePath = "Examples." + filePartName.Replace('\\', '.').TrimStart('.');
+ using (var streamExpected = _extractor.ReadFileFromResToStream(resourcePath))
+ using (var streamActual = File.OpenRead(filePath2))
+ {
+ string message;
+ var success = ExcelDocsComparer.Compare(streamActual, streamExpected, TestHelper.IsRunningOnUnix, out message);
+ var formattedMessage =
+ String.Format(
+ "Actual file '{0}' is different than the expected file '{1}'. The difference is: '{2}'",
+ filePath2, resourcePath, message);
+
+ Assert.IsTrue(success, formattedMessage);
+ }
+ }
+ }
+
+ public static void CreateAndCompare(Func workbookGenerator, string referenceResource, bool evaluateFormulae = false)
+ {
+ Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
+
+ string[] pathParts = referenceResource.Split(new char[] { '\\' });
+ string filePath1 = Path.Combine(new List() { OtherTestsOutputDirectory }.Concat(pathParts).ToArray());
+
+ var extension = Path.GetExtension(filePath1);
+ var directory = Path.GetDirectoryName(filePath1);
+
+ var fileName = Path.GetFileNameWithoutExtension(filePath1);
+ fileName += ActualTestResultPostFix;
+ fileName = Path.ChangeExtension(fileName, extension);
+
+ filePath1 = Path.Combine(directory, "z" + fileName);
+ var filePath2 = Path.Combine(directory, fileName);
+
+ using (var wb = workbookGenerator.Invoke())
+ wb.SaveAs(filePath2, true, evaluateFormulae);
+
+ if (CompareWithResources)
+ {
+ string resourcePath = referenceResource.Replace('\\', '.').TrimStart('.');
using (var streamExpected = _extractor.ReadFileFromResToStream(resourcePath))
using (var streamActual = File.OpenRead(filePath2))
{
diff --git a/ClosedXML_Tests/Utils/PackageHelper.cs b/ClosedXML_Tests/Utils/PackageHelper.cs
index 3def760..60bc482 100644
--- a/ClosedXML_Tests/Utils/PackageHelper.cs
+++ b/ClosedXML_Tests/Utils/PackageHelper.cs
@@ -89,7 +89,7 @@
throw new ArgumentNullException("dest");
}
- #endregion
+ #endregion Check
if (dest.PartExists(uri))
{
@@ -130,7 +130,7 @@
throw new ArgumentNullException("serializeAction");
}
- #endregion
+ #endregion Check
if (package.PartExists(descriptor.Uri))
{
@@ -160,7 +160,7 @@
throw new ArgumentNullException("serializeAction");
}
- #endregion
+ #endregion Check
if (package.PartExists(descriptor.Uri))
{
@@ -190,7 +190,7 @@
throw new ArgumentNullException("deserializeFunc");
}
- #endregion
+ #endregion Check
if (!package.PartExists(uri))
{
@@ -220,7 +220,7 @@
throw new ArgumentNullException("deserializeAction");
}
- #endregion
+ #endregion Check
if (!package.PartExists(uri))
{
@@ -250,7 +250,7 @@
throw new ArgumentNullException("deserializeAction");
}
- #endregion
+ #endregion Check
if (!package.PartExists(uri))
{
@@ -301,7 +301,7 @@
throw new ArgumentNullException("right");
}
- #endregion
+ #endregion Check
excludeMethod = excludeMethod ?? (uri => false);
PackagePartCollection leftParts = left.GetParts();
@@ -353,7 +353,10 @@
leftPart.ContentType == @"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" &&
rightPart.ContentType == @"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
- if (!StreamHelper.Compare(oneStream, otherStream, stripColumnWidthsFromSheet))
+ var tuple1 = new Tuple(pair.Uri, oneStream);
+ var tuple2 = new Tuple(pair.Uri, otherStream);
+
+ if (!StreamHelper.Compare(tuple1, tuple2, stripColumnWidthsFromSheet))
{
pair.Status = CompareStatus.NonEqual;
if (compareToFirstDifference)
@@ -387,11 +390,16 @@
{
#region Private fields
- [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly CompressionOption _compressOption;
- [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly string _contentType;
- [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Uri _uri;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly CompressionOption _compressOption;
- #endregion
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly string _contentType;
+
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly Uri _uri;
+
+ #endregion Private fields
#region Constructor
@@ -414,33 +422,36 @@
throw new ArgumentNullException("contentType");
}
- #endregion
+ #endregion Check
_uri = uri;
_contentType = contentType;
_compressOption = compressOption;
}
- #endregion
+ #endregion Constructor
#region Public properties
public Uri Uri
{
- [DebuggerStepThrough] get { return _uri; }
+ [DebuggerStepThrough]
+ get { return _uri; }
}
public string ContentType
{
- [DebuggerStepThrough] get { return _contentType; }
+ [DebuggerStepThrough]
+ get { return _contentType; }
}
public CompressionOption CompressOption
{
- [DebuggerStepThrough] get { return _compressOption; }
+ [DebuggerStepThrough]
+ get { return _compressOption; }
}
- #endregion
+ #endregion Public properties
#region Public methods
@@ -449,10 +460,10 @@
return string.Format("Uri:{0} ContentType: {1}, Compression: {2}", _uri, _contentType, _compressOption);
}
- #endregion
+ #endregion Public methods
}
- #endregion
+ #endregion Nested type: PackagePartDescriptor
#region Nested type: CompareStatus
@@ -464,7 +475,7 @@
NonEqual
}
- #endregion
+ #endregion Nested type: CompareStatus
#region Nested type: PartPair
@@ -472,10 +483,13 @@
{
#region Private fields
- [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Uri _uri;
- [DebuggerBrowsable(DebuggerBrowsableState.Never)] private CompareStatus _status;
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private readonly Uri _uri;
- #endregion
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private CompareStatus _status;
+
+ #endregion Private fields
#region Constructor
@@ -485,26 +499,29 @@
_status = status;
}
- #endregion
+ #endregion Constructor
#region Public properties
public Uri Uri
{
- [DebuggerStepThrough] get { return _uri; }
+ [DebuggerStepThrough]
+ get { return _uri; }
}
public CompareStatus Status
{
- [DebuggerStepThrough] get { return _status; }
- [DebuggerStepThrough] set { _status = value; }
+ [DebuggerStepThrough]
+ get { return _status; }
+ [DebuggerStepThrough]
+ set { _status = value; }
}
- #endregion
+ #endregion Public properties
}
- #endregion
+ #endregion Nested type: PartPair
//--
}
-}
\ No newline at end of file
+}
diff --git a/ClosedXML_Tests/Utils/StreamHelper.cs b/ClosedXML_Tests/Utils/StreamHelper.cs
index 8ecb4f9..bf89911 100644
--- a/ClosedXML_Tests/Utils/StreamHelper.cs
+++ b/ClosedXML_Tests/Utils/StreamHelper.cs
@@ -114,36 +114,39 @@
///
/// ///
///
- public static bool Compare(Stream one, Stream other, bool stripColumnWidths)
+ public static bool Compare(Tuple tuple1, Tuple tuple2, bool stripColumnWidths)
{
#region Check
- if (one == null)
+ if (tuple1 == null || tuple1.Item1 == null || tuple1.Item2 == null)
{
throw new ArgumentNullException("one");
}
- if (other == null)
+ if (tuple2 == null || tuple2.Item1 == null || tuple2.Item2 == null)
{
throw new ArgumentNullException("other");
}
- if (one.Position != 0)
+ if (tuple1.Item2.Position != 0)
{
throw new ArgumentException("Must be in position 0", "one");
}
- if (other.Position != 0)
+ if (tuple1.Item2.Position != 0)
{
throw new ArgumentException("Must be in position 0", "other");
}
#endregion Check
- var stringOne = new StreamReader(one).ReadToEnd().RemoveIgnoredParts(stripColumnWidths, ignoreGuids: true);
- var stringOther = new StreamReader(other).ReadToEnd().RemoveIgnoredParts(stripColumnWidths, ignoreGuids: true);
+ var stringOne = new StreamReader(tuple1.Item2).ReadToEnd().RemoveIgnoredParts(tuple1.Item1, stripColumnWidths, ignoreGuids: true);
+ var stringOther = new StreamReader(tuple2.Item2).ReadToEnd().RemoveIgnoredParts(tuple2.Item1, stripColumnWidths, ignoreGuids: true);
return stringOne == stringOther;
}
- private static string RemoveIgnoredParts(this string s, Boolean ignoreColumnWidths, Boolean ignoreGuids)
+ private static string RemoveIgnoredParts(this string s, Uri uri, Boolean ignoreColumnWidths, Boolean ignoreGuids)
{
+ foreach (var pair in uriSpecificIgnores.Where(p => p.Key.Equals(uri.OriginalString)))
+ s = pair.Value.Replace(s, "");
+
if (ignoreColumnWidths)
s = RemoveColumnWidths(s);
@@ -153,6 +156,12 @@
return s;
}
+ private static IEnumerable> uriSpecificIgnores = new List>()
+ {
+ // Remove dcterms elements
+ new KeyValuePair("/docProps/core.xml", new Regex(@"", RegexOptions.Compiled))
+ };
+
private static Regex columnRegex = new Regex(" ", RegexOptions.Compiled);
private static Regex widthRegex = new Regex("width=\"\\d+(\\.\\d+)?\"\\s+", RegexOptions.Compiled);