2
2
using NPOI . SS . Util ;
3
3
using System ;
4
4
using System . Collections . Generic ;
5
+ using System . Linq ;
5
6
using System . Text ;
6
7
7
8
namespace NPOI . SS . Formula
8
9
{
10
+ /// <summary>
11
+ /// <para>
12
+ /// Evaluates Conditional Formatting constraints.
13
+ /// </para>
14
+ /// <para>
15
+ /// For performance reasons, this class keeps a cache of all previously evaluated rules and cells.
16
+ /// Be sure to call <see cref="clearAllCachedFormats()" /> if any conditional formats are modified, added, or deleted,
17
+ /// and <see cref="clearAllCachedValues()" /> whenever cell values change.
18
+ /// </para>
19
+ /// </summary>
9
20
public class ConditionalFormattingEvaluator
10
21
{
11
22
private WorkbookEvaluator workbookEvaluator ;
12
23
private IWorkbook workbook ;
13
-
24
+ /// <summary>
25
+ /// <para>
26
+ /// All the underlying structures, for both HSSF and XSSF, repeatedly go to the raw bytes/XML for the
27
+ /// different pieces used in the ConditionalFormatting* structures. That's highly inefficient,
28
+ /// and can cause significant lag when checking formats for large workbooks.
29
+ /// </para>
30
+ /// <para>
31
+ /// Instead we need a cached version that is discarded when definitions change.
32
+ /// </para>
33
+ /// <para>
34
+ /// Sheets don't implement equals, and since its an interface,
35
+ /// there's no guarantee instances won't be recreated on the fly by some implementation.
36
+ /// So we use sheet name.
37
+ /// </para>
38
+ /// </summary>
14
39
private Dictionary < String , List < EvaluationConditionalFormatRule > > formats = new Dictionary < string , List < EvaluationConditionalFormatRule > > ( ) ;
40
+ /// <summary>
41
+ /// <para>
42
+ /// Evaluating rules for cells in their region(s) is expensive, so we want to cache them,
43
+ /// and empty/reevaluate the cache when values change.
44
+ /// </para>
45
+ /// <para>
46
+ /// Rule lists are in priority order, as evaluated by Excel (smallest priority # for XSSF, definition order for HSSF)
47
+ /// </para>
48
+ /// <para>
49
+ /// CellReference : equals().
50
+ /// </para>
51
+ /// </summary>
15
52
private SortedDictionary < CellReference , List < EvaluationConditionalFormatRule > > values = new SortedDictionary < CellReference , List < EvaluationConditionalFormatRule > > ( ) ;
16
53
17
54
public ConditionalFormattingEvaluator ( IWorkbook wb , IWorkbookEvaluatorProvider provider )
@@ -26,6 +63,28 @@ protected WorkbookEvaluator WorkbookEvaluator
26
63
return workbookEvaluator ;
27
64
}
28
65
}
66
+
67
+ /// <summary>
68
+ /// <para>
69
+ /// This checks all applicable <see cref="ConditionalFormattingRule"/>s for the cell's sheet,
70
+ /// in defined "priority" order, returning the matches if any. This is a property currently
71
+ /// not exposed from <c>CTCfRule</c> in <c>XSSFConditionalFormattingRule</c>.
72
+ /// </para>
73
+ /// <para>
74
+ /// Most cells will have zero or one applied rule, but it is possible to define multiple rules
75
+ /// that apply at the same time to the same cell, thus the List result.
76
+ /// </para>
77
+ /// <para>
78
+ /// Note that to properly apply conditional rules, care must be taken to offset the base
79
+ /// formula by the relative position of the current cell, or the wrong value is checked.
80
+ /// This is handled by <see cref="WorkbookEvaluator.Evaluate(String, CellReference, CellRangeAddressBase)" />.
81
+ /// </para>
82
+ /// </summary>
83
+ /// <param name="cellRef">NOTE: if no sheet name is specified, this uses the workbook active sheet</param>
84
+ /// <returns>Unmodifiable List of <see cref="EvaluationConditionalFormatRule"/>s that apply to the current cell value,
85
+ /// in priority order, as evaluated by Excel (smallest priority # for XSSF, definition order for HSSF),
86
+ /// or null if none apply
87
+ /// </returns>
29
88
public List < EvaluationConditionalFormatRule > GetConditionalFormattingForCell ( CellReference cellRef )
30
89
{
31
90
List < EvaluationConditionalFormatRule > rules = values . TryGetValue ( cellRef , out List < EvaluationConditionalFormatRule > value ) ? value : null ;
@@ -71,6 +130,28 @@ public List<EvaluationConditionalFormatRule> GetConditionalFormattingForCell(Cel
71
130
72
131
return rules ;
73
132
}
133
+
134
+ /// <summary>
135
+ /// <para>
136
+ /// This checks all applicable <see cref="ConditionalFormattingRule"/>s for the cell's sheet,
137
+ /// in defined "priority" order, returning the matches if any. This is a property currently
138
+ /// not exposed from <c>CTCfRule</c> in <c>XSSFConditionalFormattingRule</c>.
139
+ /// </para>
140
+ /// <para>
141
+ /// Most cells will have zero or one applied rule, but it is possible to define multiple rules
142
+ /// that apply at the same time to the same cell, thus the List result.
143
+ /// </para>
144
+ /// <para>
145
+ /// Note that to properly apply conditional rules, care must be taken to offset the base
146
+ /// formula by the relative position of the current cell, or the wrong value is checked.
147
+ /// This is handled by <see cref="WorkbookEvaluator.Evaluate(String, CellReference, CellRangeAddressBase)" />.
148
+ /// </para>
149
+ /// </summary>
150
+ /// <param name="cell"></param>
151
+ /// <returns>Unmodifiable List of <see cref="EvaluationConditionalFormatRule"/>s that apply to the current cell value,
152
+ /// in priority order, as evaluated by Excel (smallest priority # for XSSF, definition order for HSSF),
153
+ /// or null if none apply
154
+ /// </returns>
74
155
public List < EvaluationConditionalFormatRule > GetConditionalFormattingForCell ( ICell cell )
75
156
{
76
157
return GetConditionalFormattingForCell ( GetRef ( cell ) ) ;
@@ -80,6 +161,11 @@ public static CellReference GetRef(ICell cell)
80
161
return new CellReference ( cell . Sheet . SheetName , cell . RowIndex , cell . ColumnIndex , false , false ) ;
81
162
}
82
163
164
+ /// <summary>
165
+ /// lazy load by sheet since reading can be expensive
166
+ /// </summary>
167
+ /// <param name="sheet"></param>
168
+ /// <returns>unmodifiable list of rules</returns>
83
169
protected List < EvaluationConditionalFormatRule > GetRules ( ISheet sheet )
84
170
{
85
171
String sheetName = sheet . SheetName ;
@@ -110,13 +196,95 @@ protected List<EvaluationConditionalFormatRule> GetRules(ISheet sheet)
110
196
}
111
197
return rules ;
112
198
}
199
+ /// <summary>
200
+ /// Call this whenever rules are added, reordered, or removed, or a rule formula is changed
201
+ /// (not the formula inputs but the formula expression itself)
202
+ /// </summary>
113
203
public void ClearAllCachedFormats ( )
114
204
{
115
205
formats . Clear ( ) ;
116
206
}
207
+ /// <summary>
208
+ /// <para>
209
+ /// Call this whenever cell values change in the workbook, so condional formats are re-evaluated
210
+ /// for all cells.
211
+ /// </para>
212
+ /// <para>
213
+ /// TODO: eventually this should work like <see cref="EvaluationCache.notifyUpdateCell(int, int, EvaluationCell)" />
214
+ /// and only clear values that need recalculation based on the formula dependency tree.
215
+ /// </para>
216
+ /// </summary>
117
217
public void ClearAllCachedValues ( )
118
218
{
119
219
values . Clear ( ) ;
120
220
}
221
+
222
+ /// <summary>
223
+ /// </summary>
224
+ /// <param name="sheetName"></param>
225
+ /// <returns>unmodifiable list of all Conditional format rules for the given sheet, if any</returns>
226
+ public List < EvaluationConditionalFormatRule > GetFormatRulesForSheet ( string sheetName ) {
227
+ return GetFormatRulesForSheet ( workbook . GetSheet ( sheetName ) ) ;
228
+ }
229
+
230
+ /// <summary>
231
+ /// </summary>
232
+ /// <param name="sheet"></param>
233
+ /// <returns>unmodifiable list of all Conditional format rules for the given sheet, if any</returns>
234
+ public List < EvaluationConditionalFormatRule > GetFormatRulesForSheet ( ISheet sheet ) {
235
+ return GetRules ( sheet ) ;
236
+ }
237
+
238
+ /// <summary>
239
+ /// <para>
240
+ /// Conditional formatting rules can apply only to cells in the sheet to which they are attached.
241
+ /// The POI data model does not have a back-reference to the owning sheet, so it must be passed in separately.
242
+ /// </para>
243
+ /// <para>
244
+ /// We could overload this with convenience methods taking a sheet name and sheet index as well.
245
+ /// </para>
246
+ /// </summary>
247
+ /// <param name="sheet">containing the rule</param>
248
+ /// <param name="conditionalFormattingIndex">of the <see cref="ConditionalFormatting"/> instance in the sheet's array</param>
249
+ /// <param name="ruleIndex">of the <see cref="ConditionalFormattingRule"/> instance within the <see cref="ConditionalFormatting"/></param>
250
+ /// <returns>unmodifiable List of all cells in the rule's region matching the rule's condition</returns>
251
+ public List < ICell > GetMatchingCells ( ISheet sheet , int conditionalFormattingIndex , int ruleIndex ) {
252
+ foreach ( EvaluationConditionalFormatRule rule in GetRules ( sheet ) ) {
253
+ if ( rule . Sheet . Equals ( sheet ) && rule . FormattingIndex == conditionalFormattingIndex && rule . RuleIndex == ruleIndex ) {
254
+ return GetMatchingCells ( rule ) ;
255
+ }
256
+ }
257
+ return [ ] ;
258
+ }
259
+
260
+ /// <summary>
261
+ /// </summary>
262
+ /// <param name="rule"></param>
263
+ /// <returns>unmodifiable List of all cells in the rule's region matching the rule's condition</returns>
264
+ public List < ICell > GetMatchingCells ( EvaluationConditionalFormatRule rule ) {
265
+ List < ICell > cells = new List < ICell > ( ) ;
266
+ ISheet sheet = rule . Sheet ;
267
+
268
+ foreach ( CellRangeAddress region in rule . Regions ) {
269
+ for ( int r = region . FirstRow ; r <= region . LastRow ; r ++ ) {
270
+ IRow row = sheet . GetRow ( r ) ;
271
+ if ( row == null ) {
272
+ continue ; // no cells to check
273
+ }
274
+ for ( int c = region . FirstColumn ; c <= region . LastColumn ; c ++ ) {
275
+ ICell cell = row . GetCell ( c ) ;
276
+ if ( cell == null ) {
277
+ continue ;
278
+ }
279
+
280
+ List < EvaluationConditionalFormatRule > cellRules = GetConditionalFormattingForCell ( cell ) ;
281
+ if ( cellRules . Contains ( rule ) ) {
282
+ cells . Add ( cell ) ;
283
+ }
284
+ }
285
+ }
286
+ }
287
+ return cells ;
288
+ }
121
289
}
122
290
}
0 commit comments