Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor data validation handling to use indexes #1207

Merged
merged 11 commits into from Jul 8, 2019
12 changes: 6 additions & 6 deletions ClosedXML/Excel/Cells/XLCell.cs
Expand Up @@ -1590,9 +1590,8 @@ public Boolean HasDataValidation
/// <returns>The data validation rule applying to the current cell or null if there is no such rule.</returns>
private IXLDataValidation GetDataValidation()
{
return Worksheet
.DataValidations
.FirstOrDefault(dv => dv.Ranges.GetIntersectedRanges(this).Any());
Worksheet.DataValidations.TryGet(AsRange().RangeAddress, out var dataValidation);
return dataValidation;
}

public IXLDataValidation SetDataValidation()
Expand Down Expand Up @@ -2117,12 +2116,13 @@ private Boolean SetRange(Object rangeObject)
rangesToMerge.ForEach(r => r.Merge(false));

var dataValidations = asRange.Worksheet.DataValidations
.Where(dv => dv.Ranges.GetIntersectedRanges(asRange.RangeAddress).Any())
.GetAllInRange(asRange.RangeAddress)
.ToList();

foreach (var dataValidation in dataValidations)
{
XLDataValidation newDataValidation = null;
foreach (var dvRange in dataValidation.Ranges.GetIntersectedRanges(asRange.RangeAddress))
foreach (var dvRange in dataValidation.Ranges.Where(r => r.Intersects(asRange)))
{
var dvTargetAddress = dvRange.RangeAddress.Relative(asRange.RangeAddress, targetRange.RangeAddress);
var dvTargetRange = Worksheet.Range(dvTargetAddress);
Expand All @@ -2132,7 +2132,7 @@ private Boolean SetRange(Object rangeObject)
newDataValidation.CopyFrom(dataValidation);
}
else
newDataValidation.Ranges.Add(dvTargetRange);
newDataValidation.AddRange(dvTargetRange);
}
}

Expand Down
34 changes: 33 additions & 1 deletion ClosedXML/Excel/DataValidation/IXLDataValidation.cs
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace ClosedXML.Excel
{
Expand All @@ -7,7 +8,38 @@ public enum XLAllowedValues { AnyValue, WholeNumber, Decimal, Date, Time, TextLe
public enum XLOperator { EqualTo, NotEqualTo, GreaterThan, LessThan, EqualOrGreaterThan, EqualOrLessThan, Between, NotBetween }
public interface IXLDataValidation
{
IXLRanges Ranges { get; set; }
/// <summary>
/// A collection of ranges the data validation rule applies too.
/// </summary>
IEnumerable<IXLRange> Ranges { get; }

/// <summary>
/// Add a range to the collection of ranges this rule applies to.
/// If the specified range does not belong to the worksheet of the data validation
/// rule it is transferred to the target worksheet.
/// </summary>
/// <param name="range">A range to add.</param>
void AddRange(IXLRange range);

/// <summary>
/// Add a collection of ranges to the collection of ranges this rule applies to.
/// Ranges that do not belong to the worksheet of the data validation
/// rule are transferred to the target worksheet.
/// </summary>
/// <param name="ranges">Ranges to add.</param>
void AddRanges(IEnumerable<IXLRange> ranges);

/// <summary>
/// Detach data validation rule of all ranges it applies to.
/// </summary>
void ClearRanges();

/// <summary>
/// Remove the specified range from the collection of range this rule applies to.
/// </summary>
/// <param name="range">A range to remove.</param>
bool RemoveRange(IXLRange range);

//void Delete();
//void CopyFrom(IXLDataValidation dataValidation);
Boolean ShowInputMessage { get; set; }
Expand Down
29 changes: 28 additions & 1 deletion ClosedXML/Excel/DataValidation/IXLDataValidations.cs
Expand Up @@ -5,10 +5,37 @@ namespace ClosedXML.Excel
{
public interface IXLDataValidations : IEnumerable<IXLDataValidation>
{
void Add(IXLDataValidation dataValidation);
IXLWorksheet Worksheet { get; }

/// <summary>
/// Add data validation rule to the collection. If the specified rule refers to another
/// worksheet than the collection, the copy will be created and its ranges will refer
/// to the worksheet of the collection. Otherwise the original instance will be placed
/// in the collection.
/// </summary>
/// <param name="dataValidation">A data validation rule to add.</param>
/// <returns>The instance that has actually been added in the collection
/// (may be a copy of the specified one).</returns>
IXLDataValidation Add(IXLDataValidation dataValidation);

Boolean ContainsSingle(IXLRange range);

void Delete(Predicate<IXLDataValidation> predicate);

/// <summary>
/// Get the data validation rule for the range with the specified address if it exists.
/// </summary>
/// <param name="rangeAddress">A range address.</param>
/// <param name="dataValidation">Data validation rule which ranges collection includes the specified
/// address. The specified range should be fully covered with the data validation rule.
/// For example, if the rule is applied to ranges A1:A3,C1:C3 then this method will
/// return True for ranges A1:A3, C1:C2, A2:A3, and False for ranges A1:C3, A1:C1, etc.</param>
/// <returns>True is the data validation rule was found, false otherwise.</returns>
bool TryGet(IXLRangeAddress rangeAddress, out IXLDataValidation dataValidation);

/// <summary>
/// Get all data validation rules applied to ranges that intersect the specified range.
/// </summary>
IEnumerable<IXLDataValidation> GetAllInRange(IXLRangeAddress rangeAddress);
}
}
132 changes: 113 additions & 19 deletions ClosedXML/Excel/DataValidation/XLDataValidation.cs
@@ -1,28 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace ClosedXML.Excel
{
internal class XLDataValidation : IXLDataValidation
{
private XLDataValidation()
private readonly XLWorksheet _worksheet;
private readonly XLRanges _ranges;

internal XLWorksheet Worksheet => _worksheet;


private XLDataValidation(XLWorksheet worksheet)
{
Ranges = new XLRanges();
_worksheet = worksheet ?? throw new ArgumentNullException(nameof(worksheet));
_ranges = new XLRanges();
Initialize();
}

public XLDataValidation(IXLRange range)
: this()
: this(range?.Worksheet as XLWorksheet)
{
Ranges.Add(new XLRange(new XLRangeParameters((XLRangeAddress)range.RangeAddress, range.Worksheet.Style)));
if (range == null) throw new ArgumentNullException(nameof(range));

AddRange(range);
}

public XLDataValidation(IXLRanges ranges)
: this()
public XLDataValidation(IXLDataValidation dataValidation, XLWorksheet worksheet)
: this(worksheet)
{
ranges.ForEach(range =>
{
Ranges.Add(new XLRange(new XLRangeParameters((XLRangeAddress)range.RangeAddress, range.Worksheet.Style)));
});
_worksheet = worksheet;
CopyFrom(dataValidation);
}

private void Initialize()
Expand Down Expand Up @@ -53,14 +62,76 @@ public Boolean IsDirty()
(!String.IsNullOrWhiteSpace(ErrorTitle) || !String.IsNullOrWhiteSpace(ErrorMessage)));
}

public XLDataValidation(IXLDataValidation dataValidation)
#region IXLDataValidation Members

public IEnumerable<IXLRange> Ranges => _ranges.AsEnumerable();

/// <summary>
/// Add a range to the collection of ranges this rule applies to.
/// If the specified range does not belong to the worksheet of the data validation
/// rule it is transferred to the target worksheet.
/// </summary>
/// <param name="range">A range to add.</param>
public void AddRange(IXLRange range)
{
CopyFrom(dataValidation);
if (range == null) throw new ArgumentNullException(nameof(range));

if (range.Worksheet != Worksheet)
range = Worksheet.Range(((XLRangeAddress) range.RangeAddress).WithoutWorksheet());

_ranges.Add(range);

RangeAdded?.Invoke(this, new RangeEventArgs(range));
}

#region IXLDataValidation Members
/// <summary>
/// Add a collection of ranges to the collection of ranges this rule applies to.
/// Ranges that do not belong to the worksheet of the data validation
/// rule are transferred to the target worksheet.
/// </summary>
/// <param name="ranges">Ranges to add.</param>
public void AddRanges(IEnumerable<IXLRange> ranges)
{
ranges = ranges ?? Enumerable.Empty<IXLRange>();

foreach (var range in ranges)
{
AddRange(range);
}
}

/// <summary>
/// Detach data validation rule of all ranges it applies to.
/// </summary>
public void ClearRanges()
{
var allRanges = _ranges.ToList();
_ranges.RemoveAll();

foreach (var range in allRanges)
{
RangeRemoved?.Invoke(this, new RangeEventArgs(range));
}
}

/// <summary>
/// Remove the specified range from the collection of range this rule applies to.
/// </summary>
/// <param name="range">A range to remove.</param>
public bool RemoveRange(IXLRange range)
{
if (range == null)
return false;

var res = _ranges.Remove(range);

if (res)
{
RangeRemoved?.Invoke(this, new RangeEventArgs(range));
}

public IXLRanges Ranges { get; set; }
return res;
}

public Boolean IgnoreBlanks { get; set; }
public Boolean InCellDropdown { get; set; }
Expand Down Expand Up @@ -165,11 +236,8 @@ public void CopyFrom(IXLDataValidation dataValidation)
{
if (dataValidation == this) return;

if (Ranges == null && dataValidation.Ranges != null)
{
Ranges = new XLRanges();
dataValidation.Ranges.ForEach(r => Ranges.Add(r));
}
if (!_ranges.Any())
AddRanges(dataValidation.Ranges);

IgnoreBlanks = dataValidation.IgnoreBlanks;
InCellDropdown = dataValidation.InCellDropdown;
Expand All @@ -196,5 +264,31 @@ private void Validate(String value)
if (value.Length > 255)
throw new ArgumentOutOfRangeException(nameof(value), "The maximum allowed length of the value is 255 characters.");
}

internal event EventHandler<RangeEventArgs> RangeAdded;

internal event EventHandler<RangeEventArgs> RangeRemoved;

internal void SplitBy(IXLRangeAddress rangeAddress)
{
var rangesToSplit = _ranges.GetIntersectedRanges(rangeAddress).ToList();

foreach (var rangeToSplit in rangesToSplit)
{
var newRanges = (rangeToSplit as XLRange).Split(rangeAddress, includeIntersection: false);
RemoveRange(rangeToSplit);
newRanges.ForEach(AddRange);
}
}
}

internal class RangeEventArgs : EventArgs
{
public RangeEventArgs(IXLRange range)
{
Range = range ?? throw new ArgumentNullException(nameof(range));
}

public IXLRange Range { get; }
}
}