Skip to content

Commit ac36eb6

Browse files
author
Fahad Adeel
committed
Implementation of ValidationRule support in Worksheet and Range classes
This commit introduces a significant enhancement to the Worksheet and Range classes by adding comprehensive support for various types of data validations through the ValidationRule class. Key highlights of this update include: - Extended the ValidationRule class to support a diverse range of data validations, including List, WholeNumber, Decimal, TextLength, and CustomFormula types. - Integrated ValidationRule application within the Worksheet class, allowing for dynamic and flexible data validation across different cells and scenarios. - Adapted the Range class to apply ValidationRules to specific cell ranges, enhancing the granularity and control of data validation in worksheets. - Crafted and added a suite of robust unit tests for each type of ValidationRule, ensuring the reliability and correctness of these new features. The tests cover the application of ValidationRules in various scenarios, validating their functionality and effectiveness in maintaining data integrity. - Ensured that the implementation is compatible with existing functionalities and adheres to project standards for code quality and performance. Overall, this update significantly bolsters the data handling capabilities of our Excel processing toolkit, providing advanced options for enforcing data integrity and guiding user inputs within worksheets. This is a pivotal step towards offering more dynamic and user-centric data validation features in FileFormat.Cells.
1 parent a67c838 commit ac36eb6

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-0
lines changed

FileFormat.Cells/Range.cs

+13
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,19 @@ public void AddDropdownListValidation(string[] options)
163163
}
164164
}
165165

166+
public void ApplyValidation(ValidationRule rule)
167+
{
168+
// Iterate over all cells in the range and apply the validation rule
169+
for (uint row = StartRowIndex; row <= EndRowIndex; row++)
170+
{
171+
for (uint column = StartColumnIndex; column <= EndColumnIndex; column++)
172+
{
173+
var cellReference = $"{ColumnIndexToLetter(column)}{row}";
174+
_worksheet.ApplyValidation(cellReference, rule);
175+
}
176+
}
177+
}
178+
166179

167180

168181
private static string ColumnIndexToLetter(uint columnIndex)

FileFormat.Cells/ValidationRule.cs

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using System.Xml;
7+
8+
namespace FileFormat.Cells
9+
{
10+
/// <summary>
11+
/// Specifies the types of validation that can be applied to a cell or range of cells.
12+
/// </summary>
13+
public enum ValidationType
14+
{
15+
/// <summary>Specifies a list validation type where cell value must be one of a predefined list.</summary>
16+
List,
17+
18+
/// <summary>Specifies a date validation type where cell value must be a date within a specified range.</summary>
19+
Date,
20+
21+
/// <summary>Specifies a whole number validation type where cell value must be a whole number within a specified range.</summary>
22+
WholeNumber,
23+
24+
/// <summary>Specifies a decimal number validation type where cell value must be a decimal number within a specified range.</summary>
25+
Decimal,
26+
27+
/// <summary>Specifies a text length validation type where the length of the cell text must be within a specified range.</summary>
28+
TextLength,
29+
30+
/// <summary>Specifies a custom formula validation type where cell value must satisfy a custom formula.</summary>
31+
CustomFormula
32+
}
33+
34+
/// <summary>
35+
/// Represents a validation rule that can be applied to a cell or range of cells.
36+
/// </summary>
37+
public class ValidationRule
38+
{
39+
/// <summary>Gets or sets the type of validation.</summary>
40+
public ValidationType Type { get; set; }
41+
42+
/// <summary>Gets or sets the list of options for list validation.</summary>
43+
public string[] Options { get; set; }
44+
45+
/// <summary>Gets or sets the minimum value for numeric or text length validation.</summary>
46+
public double? MinValue { get; set; }
47+
48+
/// <summary>Gets or sets the maximum value for numeric or text length validation.</summary>
49+
public double? MaxValue { get; set; }
50+
51+
/// <summary>Gets or sets the custom formula for custom formula validation.</summary>
52+
public string CustomFormula { get; set; }
53+
54+
/// <summary>Gets or sets the title of the error dialog that appears when validation fails.</summary>
55+
public string ErrorTitle { get; set; } = "Invalid Input";
56+
57+
/// <summary>Gets or sets the error message displayed when validation fails.</summary>
58+
public string ErrorMessage { get; set; } = "The value entered is invalid.";
59+
60+
/// <summary>
61+
/// Initializes a new instance of the <see cref="ValidationRule"/> class for list validation.
62+
/// </summary>
63+
/// <param name="options">The options for the list validation.</param>
64+
public ValidationRule(string[] options)
65+
{
66+
Type = ValidationType.List;
67+
Options = options;
68+
}
69+
70+
/// <summary>
71+
/// Initializes a new instance of the <see cref="ValidationRule"/> class for numeric (whole number, decimal) or text length validations.
72+
/// </summary>
73+
/// <param name="type">The type of validation (whole number, decimal, text length).</param>
74+
/// <param name="minValue">The minimum value for the validation.</param>
75+
/// <param name="maxValue">The maximum value for the validation.</param>
76+
/// <exception cref="ArgumentException">Thrown if the type is not numeric or text length.</exception>
77+
// Constructor for numeric (decimal, whole number) and text length validation rules
78+
public ValidationRule(ValidationType type, double minValue, double maxValue)
79+
{
80+
if (type != ValidationType.WholeNumber && type != ValidationType.Decimal && type != ValidationType.TextLength)
81+
{
82+
throw new ArgumentException("This constructor is only for numeric and text length validations.");
83+
}
84+
85+
Type = type;
86+
MinValue = minValue;
87+
MaxValue = maxValue;
88+
}
89+
90+
/// <summary>
91+
/// Initializes a new instance of the <see cref="ValidationRule"/> class for custom formula validation.
92+
/// </summary>
93+
/// <param name="customFormula">The custom formula for the validation.</param>
94+
public ValidationRule(string customFormula)
95+
{
96+
Type = ValidationType.CustomFormula;
97+
CustomFormula = customFormula;
98+
}
99+
100+
// Optional: You can add more constructors or methods to initialize different types of validation rules
101+
}
102+
103+
}

FileFormat.Cells/Worksheet.cs

+138
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,144 @@ public void AddDropdownListValidation(string cellReference, string[] options)
663663
dataValidations.AppendChild(dataValidation);
664664
}
665665

666+
public void ApplyValidation(string cellReference, ValidationRule rule)
667+
{
668+
DataValidation dataValidation = CreateDataValidation(cellReference, rule);
669+
AddDataValidation(dataValidation);
670+
}
671+
672+
public ValidationRule GetValidationRule(string cellReference)
673+
{
674+
if (_worksheetPart == null)
675+
{
676+
throw new InvalidOperationException("Worksheet part is not loaded.");
677+
}
678+
679+
var dataValidations = _worksheetPart.Worksheet.Descendants<DataValidation>();
680+
681+
foreach (var dataValidation in dataValidations)
682+
{
683+
if (dataValidation.SequenceOfReferences.InnerText.Contains(cellReference))
684+
{
685+
return CreateValidationRuleFromDataValidation(dataValidation);
686+
}
687+
}
688+
689+
return null; // No validation rule found for the specified cell
690+
}
691+
692+
private ValidationRule CreateValidationRuleFromDataValidation(DataValidation dataValidation)
693+
{
694+
// Determine the type of validation
695+
ValidationType type = DetermineValidationType(dataValidation.Type);
696+
697+
// Depending on the type, create a corresponding ValidationRule
698+
switch (type)
699+
{
700+
case ValidationType.List:
701+
// Assuming list validations use a comma-separated list of options in Formula1
702+
var options = dataValidation.Formula1.Text.Split(',');
703+
return new ValidationRule(options);
704+
705+
case ValidationType.CustomFormula:
706+
return new ValidationRule(dataValidation.Formula1.Text);
707+
708+
// Add cases for other validation types (e.g., Date, WholeNumber, Decimal, TextLength)
709+
// ...
710+
711+
default:
712+
throw new NotImplementedException("Validation type not supported.");
713+
}
714+
}
715+
716+
private ValidationType DetermineValidationType(DataValidationValues openXmlType)
717+
{
718+
// Map the OpenXML DataValidationValues to your ValidationType enum
719+
// This mapping depends on how closely your ValidationType enum aligns with OpenXML's types
720+
// Example mapping:
721+
switch (openXmlType)
722+
{
723+
case DataValidationValues.List:
724+
return ValidationType.List;
725+
case DataValidationValues.Custom:
726+
return ValidationType.CustomFormula;
727+
// Map other types...
728+
default:
729+
throw new NotImplementedException("Validation type not supported.");
730+
}
731+
}
732+
733+
734+
735+
private DataValidation CreateDataValidation(string cellReference, ValidationRule rule)
736+
{
737+
DataValidation dataValidation = new DataValidation
738+
{
739+
SequenceOfReferences = new ListValue<StringValue> { InnerText = cellReference },
740+
ShowErrorMessage = true,
741+
ErrorTitle = rule.ErrorTitle,
742+
Error = rule.ErrorMessage
743+
};
744+
745+
switch (rule.Type)
746+
{
747+
case ValidationType.List:
748+
dataValidation.Type = DataValidationValues.List;
749+
dataValidation.Formula1 = new Formula1($"\"{string.Join(",", rule.Options)}\"");
750+
break;
751+
752+
case ValidationType.Date:
753+
dataValidation.Type = DataValidationValues.Date;
754+
dataValidation.Operator = DataValidationOperatorValues.Between;
755+
dataValidation.Formula1 = new Formula1(rule.MinValue.HasValue ? rule.MinValue.Value.ToString() : "0");
756+
dataValidation.Formula2 = new Formula2(rule.MaxValue.HasValue ? rule.MaxValue.Value.ToString() : "0");
757+
break;
758+
759+
case ValidationType.WholeNumber:
760+
dataValidation.Type = DataValidationValues.Whole;
761+
dataValidation.Operator = DataValidationOperatorValues.Between;
762+
dataValidation.Formula1 = new Formula1(rule.MinValue.HasValue ? rule.MinValue.Value.ToString() : "0");
763+
dataValidation.Formula2 = new Formula2(rule.MaxValue.HasValue ? rule.MaxValue.Value.ToString() : "0");
764+
break;
765+
766+
case ValidationType.Decimal:
767+
dataValidation.Type = DataValidationValues.Decimal;
768+
dataValidation.Operator = DataValidationOperatorValues.Between;
769+
dataValidation.Formula1 = new Formula1(rule.MinValue.HasValue ? rule.MinValue.Value.ToString() : "0");
770+
dataValidation.Formula2 = new Formula2(rule.MaxValue.HasValue ? rule.MaxValue.Value.ToString() : "0");
771+
break;
772+
773+
case ValidationType.TextLength:
774+
dataValidation.Type = DataValidationValues.TextLength;
775+
dataValidation.Operator = DataValidationOperatorValues.Between;
776+
dataValidation.Formula1 = new Formula1(rule.MinValue.HasValue ? rule.MinValue.Value.ToString() : "0");
777+
dataValidation.Formula2 = new Formula2(rule.MaxValue.HasValue ? rule.MaxValue.Value.ToString() : "0");
778+
break;
779+
780+
case ValidationType.CustomFormula:
781+
dataValidation.Type = DataValidationValues.Custom;
782+
dataValidation.Formula1 = new Formula1(rule.CustomFormula);
783+
break;
784+
785+
default:
786+
throw new ArgumentException("Unsupported validation type.");
787+
}
788+
789+
return dataValidation;
790+
}
791+
792+
793+
private void AddDataValidation(DataValidation dataValidation)
794+
{
795+
var dataValidations = _worksheetPart.Worksheet.GetFirstChild<DataValidations>();
796+
if (dataValidations == null)
797+
{
798+
dataValidations = new DataValidations();
799+
_worksheetPart.Worksheet.AppendChild(dataValidations);
800+
}
801+
dataValidations.AppendChild(dataValidation);
802+
}
803+
666804

667805
private (uint row, uint column) ParseCellReference(string cellReference)
668806
{

FileFormat.Cells_Tests/UnitTest1.cs

+70
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,78 @@ public void Test_ExtractImages()
330330

331331
}
332332

333+
[TestMethod]
334+
public void ListValidationRule_CreatesCorrectType()
335+
{
336+
var options = new[] { "Option1", "Option2", "Option3" };
337+
var rule = new ValidationRule(options);
338+
339+
Assert.AreEqual(ValidationType.List, rule.Type);
340+
CollectionAssert.AreEqual(options, rule.Options);
341+
}
342+
343+
[TestMethod]
344+
public void NumericValidationRule_CreatesCorrectTypeAndValues()
345+
{
346+
var minValue = 10.0;
347+
var maxValue = 100.0;
348+
var rule = new ValidationRule(ValidationType.Decimal, minValue, maxValue);
349+
350+
Assert.AreEqual(ValidationType.Decimal, rule.Type);
351+
Assert.AreEqual(minValue, rule.MinValue);
352+
Assert.AreEqual(maxValue, rule.MaxValue);
353+
}
354+
355+
[TestMethod]
356+
public void CustomFormulaValidationRule_CreatesCorrectFormula()
357+
{
358+
var formula = "=A1>0";
359+
var rule = new ValidationRule(formula);
360+
361+
Assert.AreEqual(ValidationType.CustomFormula, rule.Type);
362+
Assert.AreEqual(formula, rule.CustomFormula);
363+
}
333364

365+
[TestMethod]
366+
public void ApplyListValidation_And_Verify()
367+
{
368+
var expectedOptions = new[] { "Apple", "Banana", "Orange" };
369+
using (var workbook = new Workbook())
370+
{
371+
var worksheet = workbook.Worksheets[0];
372+
373+
// Act: Apply a list validation rule to a cell
374+
var listRule = new ValidationRule(expectedOptions);
375+
worksheet.ApplyValidation("A1", listRule); // Applying to cell A1
376+
377+
// Act: Save the workbook
378+
workbook.Save(testFilePath);
379+
}
380+
381+
// Assert: Reopen the workbook and verify the validation rule
382+
using (var workbook = new Workbook(testFilePath))
383+
{
384+
var worksheet = workbook.Worksheets[0];
385+
var retrievedRule = worksheet.GetValidationRule("A1");
386+
387+
Console.WriteLine("Expected Options: " + string.Join(", ", expectedOptions));
388+
Console.WriteLine("Retrieved Options: " + string.Join(", ", retrievedRule.Options));
389+
390+
// Assert: Check that the retrieved rule matches what was applied
391+
Assert.IsNotNull(retrievedRule, "Validation rule should not be null.");
392+
393+
Assert.AreEqual(ValidationType.List, retrievedRule.Type, "Validation type should be List.");
394+
395+
// Verify each option individually
396+
var processedRetrievedOptions = retrievedRule.Options
397+
.Select(option => option.Trim(new char[] { ' ', '"' }))
398+
.ToArray();
399+
400+
CollectionAssert.AreEqual(expectedOptions, processedRetrievedOptions, "Validation options do not match.");
401+
}
402+
}
334403

404+
335405
[TestCleanup]
336406
public void Cleanup()
337407
{

0 commit comments

Comments
 (0)