Skip to content

Commit 9d10325

Browse files
author
fileformat-cells
authored
Merge pull request #12 from fahadadeel/main
Feature: Add Advanced ValidationRule Support in Worksheet and Range Classes
2 parents f84a948 + ac36eb6 commit 9d10325

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)