Skip to content

Commit

Permalink
Fix application of multiple columns with filters
Browse files Browse the repository at this point in the history
Improve loading of custom filters
(Still needs work. Needs a good refactoring)
  • Loading branch information
igitur committed Nov 9, 2018
1 parent a161520 commit b84d88d
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 105 deletions.
71 changes: 37 additions & 34 deletions ClosedXML/Excel/AutoFilters/XLAutoFilter.cs
Expand Up @@ -62,58 +62,61 @@ public IXLAutoFilter Reapply()

// Recalculate shown / hidden rows
var rows = Range.Rows(2, Range.RowCount());
foreach (IXLRangeRow row in rows)
row.WorksheetRow().Unhide();
rows.ForEach(row =>
row.WorksheetRow().Unhide()
);

foreach (var kp in Filters)
foreach (IXLRangeRow row in rows)
{
Boolean firstFilter = true;
foreach (XLFilter filter in kp.Value)
var rowMatch = true;

foreach (var columnIndex in Filters.Keys)
{
var condition = filter.Condition;
var isText = filter.Value is String;
var isDateTime = filter.Value is DateTime;
var columnFilters = Filters[columnIndex];

var columnFilterMatch = true;

foreach (IXLRangeRow row in rows)
// If the first filter is an 'Or', we need to fudge the initial condition
if (columnFilters.Count > 0 && columnFilters.First().Connector == XLConnector.Or)
{
//TODO : clean up filter matching - it's done in different place
Boolean match;
columnFilterMatch = false;
}

foreach (var filter in columnFilters)
{
var condition = filter.Condition;
var isText = filter.Value is String;
var isDateTime = filter.Value is DateTime;

Boolean filterMatch;

if (isText)
match = condition(row.Cell(kp.Key).GetFormattedString());
filterMatch = condition(row.Cell(columnIndex).GetFormattedString());
else if (isDateTime)
match = row.Cell(kp.Key).DataType == XLDataType.DateTime &&
condition(row.Cell(kp.Key).GetDateTime());
filterMatch = row.Cell(columnIndex).DataType == XLDataType.DateTime &&
condition(row.Cell(columnIndex).GetDateTime());
else
match = row.Cell(kp.Key).DataType == XLDataType.Number &&
condition(row.Cell(kp.Key).GetDouble());
filterMatch = row.Cell(columnIndex).DataType == XLDataType.Number &&
condition(row.Cell(columnIndex).GetDouble());

if (firstFilter)
if (filter.Connector == XLConnector.And)
{
if (match)
row.WorksheetRow().Unhide();
else
row.WorksheetRow().Hide();
columnFilterMatch &= filterMatch;
if (!columnFilterMatch) break;
}
else
{
if (filter.Connector == XLConnector.And)
{
if (!row.WorksheetRow().IsHidden)
{
if (match)
row.WorksheetRow().Unhide();
else
row.WorksheetRow().Hide();
}
}
else if (match)
row.WorksheetRow().Unhide();
columnFilterMatch |= filterMatch;
if (columnFilterMatch) break;
}
}

firstFilter = false;
rowMatch &= columnFilterMatch;

if (!rowMatch) break;
}

if (!rowMatch) row.WorksheetRow().Hide();
}

ws.ResumeEvents();
Expand Down
47 changes: 13 additions & 34 deletions ClosedXML/Excel/AutoFilters/XLFilterColumn.cs
Expand Up @@ -179,40 +179,40 @@ public void BelowAverage()
LessThan(minValue).Or.GreaterThan(maxValue);
}

public static Func<String, Object, Boolean> BeginsWithFunction { get; } = (value, input) => ((string)input).StartsWith(value, StringComparison.InvariantCultureIgnoreCase);

public IXLFilterConnector BeginsWith(String value)
{
return ApplyCustomFilter(value + "*", XLFilterOperator.Equal,
s => ((string)s).StartsWith(value, StringComparison.InvariantCultureIgnoreCase));
return ApplyCustomFilter(value + "*", XLFilterOperator.Equal, s => BeginsWithFunction(value, s));
}

public IXLFilterConnector NotBeginsWith(String value)
{
return ApplyCustomFilter(value + "*", XLFilterOperator.NotEqual,
s => !((string)s).StartsWith(value, StringComparison.InvariantCultureIgnoreCase));
return ApplyCustomFilter(value + "*", XLFilterOperator.NotEqual, s => !BeginsWithFunction(value, s));
}

public static Func<String, Object, Boolean> EndsWithFunction { get; } = (value, input) => ((string)input).EndsWith(value, StringComparison.InvariantCultureIgnoreCase);

public IXLFilterConnector EndsWith(String value)
{
return ApplyCustomFilter("*" + value, XLFilterOperator.Equal,
s => ((string)s).EndsWith(value, StringComparison.InvariantCultureIgnoreCase));
return ApplyCustomFilter("*" + value, XLFilterOperator.Equal, s => EndsWithFunction(value, s));
}

public IXLFilterConnector NotEndsWith(String value)
{
return ApplyCustomFilter("*" + value, XLFilterOperator.NotEqual,
s => !((string)s).EndsWith(value, StringComparison.InvariantCultureIgnoreCase));
return ApplyCustomFilter("*" + value, XLFilterOperator.NotEqual, s => !EndsWithFunction(value, s));
}

public static Func<String, Object, Boolean> ContainsFunction { get; } = (value, input) => ((string)input).IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;

public IXLFilterConnector Contains(String value)
{
return ApplyCustomFilter("*" + value + "*", XLFilterOperator.Equal,
s => ((string)s).ToLower().Contains(value.ToLower()));
return ApplyCustomFilter("*" + value + "*", XLFilterOperator.Equal, s => ContainsFunction(value, s));
}

public IXLFilterConnector NotContains(String value)
{
return ApplyCustomFilter("*" + value + "*", XLFilterOperator.Equal,
s => !((string)s).ToLower().Contains(value.ToLower()));
return ApplyCustomFilter("*" + value + "*", XLFilterOperator.Equal, s => !ContainsFunction(value, s));
}

public XLFilterType FilterType { get; set; }
Expand Down Expand Up @@ -413,28 +413,7 @@ private IEnumerable<double> GetAverageValues(bool aboveAverage)
}
}
_autoFilter.Column(_column).FilterType = filterType;
Boolean isText = typeof(T) == typeof(String);
Boolean isDateTime = typeof(T) == typeof(DateTime);
var ws = _autoFilter.Range.Worksheet as XLWorksheet;
ws.SuspendEvents();
var rows = _autoFilter.Range.Rows(2, _autoFilter.Range.RowCount());
foreach (IXLRangeRow row in rows)
{
Boolean match;

if (isText)
match = condition(row.Cell(_column).GetFormattedString());
else if (isDateTime)
match = row.Cell(_column).DataType == XLDataType.DateTime && condition(row.Cell(_column).GetDateTime());
else
match = row.Cell(_column).DataType == XLDataType.Number && condition(row.Cell(_column).GetDouble());

if (match)
row.WorksheetRow().Unhide();
else
row.WorksheetRow().Hide();
}
ws.ResumeEvents();
_autoFilter.Reapply();
return new XLFilterConnector(_autoFilter, _column);
}

Expand Down
71 changes: 49 additions & 22 deletions ClosedXML/Excel/XLWorkbook_Load.cs
Expand Up @@ -1982,9 +1982,31 @@ private static void LoadAutoFilterColumns(AutoFilter af, XLAutoFilter autoFilter

foreach (var filter in filterColumn.CustomFilters.OfType<CustomFilter>())
{
var xlFilter = new XLFilter { Value = filter.Val.Value, Connector = connector };
var xlFilter = new XLFilter { Connector = connector };
if (isText)
xlFilter.Value = filter.Val.Value;
{
// TODO: Treat text BETWEEN functions better
if (filter.Val.Value.StartsWith("*") && filter.Val.Value.EndsWith("*"))
{
var value = filter.Val.Value.Substring(1, filter.Val.Value.Length - 2);
xlFilter.Value = filter.Val.Value;
xlFilter.Condition = s => XLFilterColumn.ContainsFunction(value, s);
}
else if (filter.Val.Value.StartsWith("*"))
{
var value = filter.Val.Value.Substring(1);
xlFilter.Value = filter.Val.Value;
xlFilter.Condition = s => XLFilterColumn.EndsWithFunction(value, s);
}
else if (filter.Val.Value.EndsWith("*"))
{
var value = filter.Val.Value.Substring(0, filter.Val.Value.Length - 1);
xlFilter.Value = filter.Val.Value;
xlFilter.Condition = s => XLFilterColumn.BeginsWithFunction(value, s);
}
else
xlFilter.Value = filter.Val.Value;
}
else
xlFilter.Value = Double.Parse(filter.Val.Value, CultureInfo.InvariantCulture);

Expand All @@ -1993,29 +2015,34 @@ private static void LoadAutoFilterColumns(AutoFilter af, XLAutoFilter autoFilter
else
xlFilter.Operator = XLFilterOperator.Equal;

Func<Object, Boolean> condition = null;
switch (xlFilter.Operator)
// Unhandled instances - we should actually improve this
if (xlFilter.Condition == null)
{
case XLFilterOperator.Equal:
if (isText)
condition = o => o.ToString().Equals(xlFilter.Value.ToString(), StringComparison.OrdinalIgnoreCase);
else
condition = o => (o as IComparable).CompareTo(xlFilter.Value) == 0;
break;

case XLFilterOperator.EqualOrGreaterThan: condition = o => (o as IComparable).CompareTo(xlFilter.Value) >= 0; break;
case XLFilterOperator.EqualOrLessThan: condition = o => (o as IComparable).CompareTo(xlFilter.Value) <= 0; break;
case XLFilterOperator.GreaterThan: condition = o => (o as IComparable).CompareTo(xlFilter.Value) > 0; break;
case XLFilterOperator.LessThan: condition = o => (o as IComparable).CompareTo(xlFilter.Value) < 0; break;
case XLFilterOperator.NotEqual:
if (isText)
condition = o => !o.ToString().Equals(xlFilter.Value.ToString(), StringComparison.OrdinalIgnoreCase);
else
condition = o => (o as IComparable).CompareTo(xlFilter.Value) != 0;
break;
Func<Object, Boolean> condition = null;
switch (xlFilter.Operator)
{
case XLFilterOperator.Equal:
if (isText)
condition = o => o.ToString().Equals(xlFilter.Value.ToString(), StringComparison.OrdinalIgnoreCase);
else
condition = o => (o as IComparable).CompareTo(xlFilter.Value) == 0;
break;

case XLFilterOperator.EqualOrGreaterThan: condition = o => (o as IComparable).CompareTo(xlFilter.Value) >= 0; break;
case XLFilterOperator.EqualOrLessThan: condition = o => (o as IComparable).CompareTo(xlFilter.Value) <= 0; break;
case XLFilterOperator.GreaterThan: condition = o => (o as IComparable).CompareTo(xlFilter.Value) > 0; break;
case XLFilterOperator.LessThan: condition = o => (o as IComparable).CompareTo(xlFilter.Value) < 0; break;
case XLFilterOperator.NotEqual:
if (isText)
condition = o => !o.ToString().Equals(xlFilter.Value.ToString(), StringComparison.OrdinalIgnoreCase);
else
condition = o => (o as IComparable).CompareTo(xlFilter.Value) != 0;
break;
}

xlFilter.Condition = condition;
}

xlFilter.Condition = condition;
filterList.Add(xlFilter);
}
}
Expand Down

0 comments on commit b84d88d

Please sign in to comment.