This repository was archived by the owner on Jul 3, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathWorksheetAccessor.cs
More file actions
1994 lines (1884 loc) · 89.1 KB
/
Copy pathWorksheetAccessor.cs
File metadata and controls
1994 lines (1884 loc) · 89.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/***************************************************************************
Copyright (c) Microsoft Corporation 2012-2013.
This code is licensed using the Microsoft Public License (Ms-PL). The text of the license can be found here:
http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx
Published at http://OpenXmlDeveloper.org
Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
Developer: Eric White
Blog: http://www.ericwhite.com
Twitter: @EricWhiteDev
Email: eric@ericwhite.com
Version: 2.6.00
***************************************************************************/
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using DocumentFormat.OpenXml.Packaging;
using System.Xml;
using ExcelFormula;
namespace OpenXmlPowerTools
{
// Classes for "bulk load" of a spreadsheet
public class MemorySpreadsheet
{
private SortedList<int, MemoryRow> rowList;
public MemorySpreadsheet()
{
rowList = new SortedList<int, MemoryRow>();
}
public void SetCellValue(int row, int column, object value)
{
if (!rowList.ContainsKey(row))
rowList.Add(row, new MemoryRow(row));
MemoryRow mr = rowList[row];
mr.SetCell(new MemoryCell(column, value));
}
public void SetCellValue(int row, int column, object value, int styleIndex)
{
if (!rowList.ContainsKey(row))
rowList.Add(row, new MemoryRow(row));
MemoryRow mr = rowList[row];
mr.SetCell(new MemoryCell(column, value, styleIndex));
}
public object GetCellValue(int row, int column)
{
if (!rowList.ContainsKey(row))
return null;
MemoryCell cell = rowList[row].GetCell(column);
if (cell == null)
return null;
return cell.GetValue();
}
public XElement GetElements()
{
XElement root = new XElement(S.sheetData);
foreach (KeyValuePair<int, MemoryRow> item in rowList)
root.Add(item.Value.GetElements());
return root;
}
}
public class MemoryRow
{
private int row;
private SortedList<int, MemoryCell> cellList;
public MemoryRow(int Row)
{
row = Row;
cellList = new SortedList<int, MemoryCell>();
}
public MemoryCell GetCell(int column)
{
if (!cellList.ContainsKey(column))
return null;
return cellList[column];
}
public void SetCell(MemoryCell cell)
{
if (cellList.ContainsKey(cell.GetColumn()))
cellList.Remove(cell.GetColumn());
cellList.Add(cell.GetColumn(), cell);
}
public XElement GetElements()
{
XElement root = new XElement(S.row, new XAttribute(NoNamespace.r, row));
foreach (KeyValuePair<int, MemoryCell> item in cellList)
root.Add(item.Value.GetElements(row));
return root;
}
}
public class MemoryCell
{
private int column;
private object cellValue;
private int styleIndex;
public MemoryCell(int col, object value)
{
column = col;
cellValue = value;
}
public MemoryCell(int col, object value, int style)
{
column = col;
cellValue = value;
styleIndex = style;
}
public int GetColumn()
{
return column;
}
public object GetValue()
{
return cellValue;
}
public int GetStyleIndex()
{
return styleIndex;
}
public XElement GetElements(int row)
{
string cellReference = WorksheetAccessor.GetColumnId(column) + row.ToString();
XElement newCell = null;
if (cellValue is int || cellValue is double)
newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XElement(S.v, cellValue.ToString()));
else if (cellValue is bool)
newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "b"), new XElement(S.v, (bool)cellValue ? "1" : "0"));
else if (cellValue is string)
{
newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "inlineStr"),
new XElement(S._is, new XElement(S.t, cellValue.ToString())));
}
if (newCell == null)
throw new ArgumentException("Invalid cell type.");
if (styleIndex != 0)
newCell.Add(new XAttribute(NoNamespace.s, styleIndex));
return newCell;
}
}
// Static methods to modify worksheets in SpreadsheetML
public class WorksheetAccessor
{
// Finds the WorksheetPart by sheet name
public static WorksheetPart GetWorksheet(SpreadsheetDocument document, string worksheetName)
{
XDocument workbook = document.WorkbookPart.GetXDocument();
return (WorksheetPart)document.WorkbookPart.GetPartById(
workbook.Root.Element(S.sheets).Elements(S.sheet).Where(
s => s.Attribute(NoNamespace.name).Value.ToLower().Equals(worksheetName.ToLower()))
.FirstOrDefault().Attribute(R.id).Value);
}
// Creates a new worksheet with the specified name
public static WorksheetPart AddWorksheet(SpreadsheetDocument document, string worksheetName)
{
// Create the empty sheet
WorksheetPart worksheetPart = document.WorkbookPart.AddNewPart<WorksheetPart>();
worksheetPart.PutXDocument(new XDocument(
new XElement(S.worksheet, new XAttribute("xmlns", S.s), new XAttribute(XNamespace.Xmlns + "r", R.r),
new XElement(S.sheetData))));
XDocument wb = document.WorkbookPart.GetXDocument();
// Generate a unique sheet ID number
int sheetId = 1;
if (wb.Root.Element(S.sheets).Elements(S.sheet).Count() != 0)
sheetId = wb.Root.Element(S.sheets).Elements(S.sheet).Max(n => Convert.ToInt32(n.Attribute(NoNamespace.sheetId).Value)) + 1;
// If name is null, generate a name based on the sheet ID
if (worksheetName == null)
worksheetName = "Sheet" + sheetId.ToString();
// Create the new sheet element in the workbook
wb.Root.Element(S.sheets).Add(new XElement(S.sheet,
new XAttribute(NoNamespace.name, worksheetName),
new XAttribute(NoNamespace.sheetId, sheetId),
new XAttribute(R.id, document.WorkbookPart.GetIdOfPart(worksheetPart))));
document.WorkbookPart.PutXDocument();
return worksheetPart;
}
// Creates a new worksheet with the specified name and contents from a memory spreadsheet
public static void SetSheetContents(SpreadsheetDocument document, WorksheetPart worksheet, MemorySpreadsheet contents)
{
XDocument worksheetXDocument = worksheet.GetXDocument();
worksheetXDocument.Root.Element(S.sheetData).ReplaceWith(contents.GetElements());
worksheet.PutXDocument();
}
// Translates the column number to the column reference string (e.g. 1 -> A, 2-> B)
public static string GetColumnId(int columnNumber)
{
string result = "";
do
{
result = ((char)((columnNumber - 1) % 26 + (int)'A')).ToString() + result;
columnNumber = (columnNumber - 1) / 26;
} while (columnNumber != 0);
return result;
}
// Gets the value of the specified cell
// Returned object can be double/Double, int/Int32, bool/Boolean or string/String types
public static object GetCellValue(SpreadsheetDocument document, WorksheetPart worksheet, int column, int row)
{
XDocument worksheetXDocument = worksheet.GetXDocument();
XElement cellValue = GetCell(worksheetXDocument, column, row);
if (cellValue != null)
{
if (cellValue.Attribute(NoNamespace.t) == null)
{
string value = cellValue.Element(S.v).Value;
if (value.Contains("."))
return Convert.ToDouble(value);
return Convert.ToInt32(value);
}
switch (cellValue.Attribute(NoNamespace.t).Value)
{
case "b":
return (cellValue.Element(S.v).Value == "1");
case "s":
return GetSharedString(document, System.Convert.ToInt32(cellValue.Element(S.v).Value));
case "inlineStr":
return cellValue.Element(S._is).Element(S.t).Value;
}
}
return null;
}
// Finds the shared string using its index
private static string GetSharedString(SpreadsheetDocument document, int index)
{
XDocument sharedStringsXDocument = document.WorkbookPart.SharedStringTablePart.GetXDocument();
return sharedStringsXDocument.Root.Elements().ElementAt<XElement>(index).Value;
}
// Gets the cell element (c) for the specified cell
private static XElement GetCell(XDocument worksheet, int column, int row)
{
string cellReference = GetColumnId(column) + row.ToString();
XElement rowElement = worksheet.Root
.Element(S.sheetData)
.Elements(S.row)
.Where(r => r.Attribute(NoNamespace.r).Value.Equals(row.ToString())).FirstOrDefault<XElement>();
if (rowElement == null)
return null;
return rowElement.Elements(S.c).Where(c => c.Attribute(NoNamespace.r).Value.Equals(cellReference)).FirstOrDefault<XElement>();
}
// Sets the value for the specified cell
// The "value" must be double/Double, int/Int32, bool/Boolean or string/String type
public static void SetCellValue(SpreadsheetDocument document, WorksheetPart worksheet, int row, int column, object value)
{
XDocument worksheetXDocument = worksheet.GetXDocument();
string cellReference = GetColumnId(column) + row.ToString();
XElement newCell = null;
if (value is int || value is double)
newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XElement(S.v, value.ToString()));
else if (value is bool)
newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "b"), new XElement(S.v, (bool)value ? "1" : "0"));
else if (value is string)
{
newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "inlineStr"),
new XElement(S._is, new XElement(S.t, value.ToString())));
}
if (newCell == null)
throw new ArgumentException("Invalid cell type.");
SetCell(worksheetXDocument, newCell);
}
// Sets the specified cell
private static void SetCell(XDocument worksheetXDocument, XElement newCell)
{
int row;
int column;
string cellReference = newCell.Attribute(NoNamespace.r).Value;
GetRowColumn(cellReference, out row, out column);
// Find the row containing the cell to add the value to
XElement rowElement = worksheetXDocument.Root
.Element(S.sheetData)
.Elements(S.row)
.Where(t => t.Attribute(NoNamespace.r).Value == row.ToString())
.FirstOrDefault();
if (rowElement == null)
{
//row element does not exist
//create a new one
rowElement = CreateEmptyRow(row);
//row elements must appear in order inside sheetData element
if (worksheetXDocument.Root.Element(S.sheetData).HasElements)
{ //if there are more rows already defined at sheetData element
//find the row with the inmediate higher index for the row containing the cell to set the value to
XElement rowAfterElement = FindRowAfter(worksheetXDocument, row);
//if there is a row with an inmediate higher index already defined at sheetData
if (rowAfterElement != null)
{
//add the new row before the row with an inmediate higher index
rowAfterElement.AddBeforeSelf(rowElement);
}
else
{ //this row is going to be the one with the highest index (add it as the last element for sheetData)
worksheetXDocument.Root.Element(S.sheetData).Elements(S.row).Last().AddAfterSelf(rowElement);
}
}
else
{ //there are no other rows already defined at sheetData
//Add a new row elemento to sheetData
worksheetXDocument.Root.Element(S.sheetData).Add(rowElement);
}
//Add the new cell to the row Element
rowElement.Add(newCell);
}
else
{
//row containing the cell to set the value to is already defined at sheetData
//look if cell already exist at that row
XElement currentCell = rowElement
.Elements(S.c)
.Where(t => t.Attribute(NoNamespace.r).Value == cellReference)
.FirstOrDefault();
if (currentCell == null)
{ //cell element does not exist at row indicated as parameter
//find the inmediate right column for the cell to set the value to
XElement columnAfterXElement = FindColumAfter(worksheetXDocument, row, column);
if (columnAfterXElement != null)
{
//Insert the new cell before the inmediate right column
columnAfterXElement.AddBeforeSelf(newCell);
}
else
{ //There is no inmediate right cell
//Add the new cell as the last element for the row
rowElement.Add(newCell);
}
}
else
{
//cell alreay exist
//replace the current cell with that with the new value
currentCell.ReplaceWith(newCell);
}
}
}
// Finds the row element (r) with a higher number than the specified "row" number
private static XElement FindRowAfter(XDocument worksheet, int row)
{
return worksheet.Root
.Element(S.sheetData)
.Elements(S.row)
.FirstOrDefault(r => System.Convert.ToInt32(r.Attribute(NoNamespace.r).Value) > row);
}
// Finds the cell element (c) in the specified row that is after the specified "column" number
private static XElement FindColumAfter(XDocument worksheet, int row, int column)
{
return worksheet.Root
.Element(S.sheetData)
.Elements(S.row)
.FirstOrDefault(r => System.Convert.ToInt32(r.Attribute(NoNamespace.r).Value) == row)
.Elements(S.c)
.FirstOrDefault(c => GetColumnNumber(c.Attribute(NoNamespace.r).Value) > GetColumnNumber(GetColumnId(column) + row));
}
// Converts the column reference string to a column number (e.g. A -> 1, B -> 2)
private static int GetColumnNumber(string cellReference)
{
int columnNumber = 0;
foreach (char c in cellReference)
{
if (Char.IsLetter(c))
columnNumber = columnNumber * 26 + System.Convert.ToInt32(c) - System.Convert.ToInt32('A') + 1;
}
return columnNumber;
}
// Converts a cell reference string into the row and column numbers for that cell
// e.g. G5 -> [row = 5, column = 7]
private static void GetRowColumn(string cellReference, out int row, out int column)
{
row = 0;
column = 0;
foreach (char c in cellReference)
{
if (Char.IsLetter(c))
column = column * 26 + System.Convert.ToInt32(c) - System.Convert.ToInt32('A') + 1;
else
row = row * 10 + System.Convert.ToInt32(c) - System.Convert.ToInt32('0');
}
}
// Returns the row and column numbers and worksheet part for the named range
public static WorksheetPart GetRange(SpreadsheetDocument doc, string rangeName, out int startRow, out int startColumn, out int endRow, out int endColumn)
{
XDocument book = doc.WorkbookPart.GetXDocument();
if (book.Root.Element(S.definedNames) == null)
throw new ArgumentException("Range name not found: " + rangeName);
XElement element = book.Root.Element(S.definedNames).Elements(S.definedName)
.Where(t => t.Attribute(NoNamespace.name).Value == rangeName).FirstOrDefault();
if (element == null)
throw new ArgumentException("Range name not found: " + rangeName);
string sheetName = element.Value.Substring(0, element.Value.IndexOf('!'));
string range = element.Value.Substring(element.Value.IndexOf('!') + 1).Replace("$","");
int colonIndex = range.IndexOf(':');
GetRowColumn(range.Substring(0, colonIndex), out startRow, out startColumn);
GetRowColumn(range.Substring(colonIndex + 1), out endRow, out endColumn);
return GetWorksheet(doc, sheetName);
}
// Sets the named range with the specified range of row and column numbers
public static void SetRange(SpreadsheetDocument doc, string rangeName, string sheetName, int startRow, int startColumn, int endRow, int endColumn)
{
XDocument book = doc.WorkbookPart.GetXDocument();
if (book.Root.Element(S.definedNames) == null)
book.Root.Add(new XElement(S.definedNames));
XElement element = book.Root.Element(S.definedNames).Elements(S.definedName)
.Where(t => t.Attribute(NoNamespace.name).Value == rangeName).FirstOrDefault();
if (element == null)
{
element = new XElement(S.definedName, new XAttribute(NoNamespace.name, rangeName));
book.Root.Element(S.definedNames).Add(element);
}
element.SetValue(String.Format("{0}!${1}${2}:${3}${4}", sheetName, GetColumnId(startColumn), startRow, GetColumnId(endColumn), endRow));
doc.WorkbookPart.PutXDocument();
}
// Sets the end row for the named range
public static void UpdateRangeEndRow(SpreadsheetDocument doc, string rangeName, int lastRow)
{
// Update named range used by pivot table
XDocument book = doc.WorkbookPart.GetXDocument();
XElement element = book.Root.Element(S.definedNames).Elements(S.definedName)
.Where(t => t.Attribute(NoNamespace.name).Value == rangeName).FirstOrDefault();
if (element != null)
{
string original = element.Value;
element.SetValue(original.Substring(0, original.Length - 1) + lastRow.ToString());
}
doc.WorkbookPart.PutXDocument();
}
// Creates an empty row element (r) with the specified row number
private static XElement CreateEmptyRow(int row)
{
return new XElement(S.row, new XAttribute(NoNamespace.r, row.ToString()));
}
public static void ForceCalculateOnLoad(SpreadsheetDocument document)
{
XDocument book = document.WorkbookPart.GetXDocument();
XElement element = book.Root.Element(S.calcPr);
if (element == null)
{
book.Root.Add(new XElement(S.calcPr));
}
element.SetAttributeValue(NoNamespace.fullCalcOnLoad, "1");
document.WorkbookPart.PutXDocument();
}
public static void FormulaReplaceSheetName(SpreadsheetDocument document, string oldName, string newName)
{
foreach (WorksheetPart sheetPart in document.WorkbookPart.WorksheetParts)
{
XDocument sheetDoc = sheetPart.GetXDocument();
bool changed = false;
foreach (XElement formula in sheetDoc.Descendants(S.f))
{
ParseFormula parser = new ParseFormula(formula.Value);
string newFormula = parser.ReplaceSheetName(oldName, newName);
if (newFormula != formula.Value)
{
formula.SetValue(newFormula);
changed = true;
}
}
if (changed)
{
sheetPart.PutXDocument();
ForceCalculateOnLoad(document);
}
}
}
// Copy all cells in the specified range to a new location
public static void CopyCellRange(SpreadsheetDocument document, WorksheetPart worksheet, int startRow, int startColumn, int endRow, int endColumn,
int toRow, int toColumn)
{
int rowOffset = toRow - startRow;
int columnOffset = toColumn - startColumn;
XDocument worksheetXDocument = worksheet.GetXDocument();
for (int row = startRow; row <= endRow; row++)
for (int column = startColumn; column <= endColumn; column++)
{
XElement oldCell = GetCell(worksheetXDocument, column, row);
if (oldCell != null)
{
XElement newCell = new XElement(oldCell);
newCell.SetAttributeValue(NoNamespace.r, GetColumnId(column + columnOffset) + (row + rowOffset).ToString());
XElement formula = newCell.Element(S.f);
if (formula != null)
{
ParseFormula parser = new ParseFormula(formula.Value);
formula.SetValue(parser.ReplaceRelativeCell(rowOffset, columnOffset));
}
SetCell(worksheetXDocument, newCell);
}
}
worksheet.PutXDocument();
ForceCalculateOnLoad(document);
}
// Creates a pivot table in the specified sheet using the specified range name
// The new pivot table will not be configured with any fields in the rows, columns, filters or values
public static PivotTablePart CreatePivotTable(SpreadsheetDocument document, string rangeName, WorksheetPart sheet)
{
int startRow, startColumn, endRow, endColumn;
WorksheetPart sourceSheet = GetRange(document, rangeName, out startRow, out startColumn, out endRow, out endColumn);
// Fill out pivotFields element (for PivotTablePart) and cacheFields element (for PivotTableCacheDefinitionPart)
// with an element for each column in the source range
XElement pivotFields = new XElement(S.pivotFields, new XAttribute(NoNamespace.count, (endColumn - startColumn + 1).ToString()));
XElement cacheFields = new XElement(S.cacheFields, new XAttribute(NoNamespace.count, (endColumn - startColumn + 1).ToString()));
for (int column = startColumn; column <= endColumn; column++)
{
pivotFields.Add(new XElement(S.pivotField, new XAttribute(NoNamespace.showAll, "0")));
XElement sharedItems = new XElement(S.sharedItems);
// Determine numeric sharedItems values, if any
object value = GetCellValue(document, sourceSheet, column, startRow + 1);
if (value is double || value is Int32)
{
bool hasDouble = false;
double minValue = Convert.ToDouble(value);
double maxValue = Convert.ToDouble(value);
if (value is double)
hasDouble = true;
for (int row = startRow + 1; row <= endRow; row++)
{
value = GetCellValue(document, sourceSheet, column, row);
if (value is double)
hasDouble = true;
if (Convert.ToDouble(value) < minValue)
minValue = Convert.ToDouble(value);
if (Convert.ToDouble(value) > maxValue)
maxValue = Convert.ToDouble(value);
}
sharedItems.Add(new XAttribute(NoNamespace.containsSemiMixedTypes, "0"),
new XAttribute(NoNamespace.containsString, "0"), new XAttribute(NoNamespace.containsNumber, "1"),
new XAttribute(NoNamespace.minValue, minValue.ToString()), new XAttribute(NoNamespace.maxValue, maxValue.ToString()));
if (!hasDouble)
sharedItems.Add(new XAttribute(NoNamespace.containsInteger, "1"));
}
cacheFields.Add(new XElement(S.cacheField, new XAttribute(NoNamespace.name, GetCellValue(document, sourceSheet, column, startRow).ToString()),
new XAttribute(NoNamespace.numFmtId, "0"), sharedItems));
}
// Fill out pivotCacheRecords element (for PivotTableCacheRecordsPart) with an element
// for each row in the source range
XElement pivotCacheRecords = new XElement(S.pivotCacheRecords, new XAttribute("xmlns", S.s),
new XAttribute(XNamespace.Xmlns + "r", R.r), new XAttribute(NoNamespace.count, (endRow - startRow).ToString()));
for (int row = startRow + 1; row <= endRow; row++)
{
XElement r = new XElement(S.r);
// Fill the record element with a value from each column in the source row
for (int column = startColumn; column <= endColumn; column++)
{
object value = GetCellValue(document, sourceSheet, column, row);
if (value is String)
r.Add(new XElement(S._s, new XAttribute(NoNamespace.v, value.ToString())));
else
r.Add(new XElement(S.n, new XAttribute(NoNamespace.v, value.ToString())));
}
pivotCacheRecords.Add(r);
}
// Create pivot table parts with proper links
PivotTablePart pivotTable = sheet.AddNewPart<PivotTablePart>();
PivotTableCacheDefinitionPart cacheDef = pivotTable.AddNewPart<PivotTableCacheDefinitionPart>();
PivotTableCacheRecordsPart records = cacheDef.AddNewPart<PivotTableCacheRecordsPart>();
document.WorkbookPart.AddPart<PivotTableCacheDefinitionPart>(cacheDef);
// Set content for the PivotTableCacheRecordsPart and PivotTableCacheDefinitionPart
records.PutXDocument(new XDocument(pivotCacheRecords));
cacheDef.PutXDocument(new XDocument(new XElement(S.pivotCacheDefinition, new XAttribute("xmlns", S.s),
new XAttribute(XNamespace.Xmlns + "r", R.r), new XAttribute(R.id, cacheDef.GetIdOfPart(records)),
new XAttribute(NoNamespace.recordCount, (endRow - startRow).ToString()),
new XElement(S.cacheSource, new XAttribute(NoNamespace.type, "worksheet"),
new XElement(S.worksheetSource, new XAttribute(NoNamespace.name, rangeName))),
cacheFields)));
// Create the pivotCache entry in the workbook part
int cacheId = 1;
XDocument wb = document.WorkbookPart.GetXDocument();
if (wb.Root.Element(S.pivotCaches) == null)
wb.Root.Add(new XElement(S.pivotCaches));
else
{
if (wb.Root.Element(S.pivotCaches).Elements(S.pivotCache).Count() != 0)
cacheId = wb.Root.Element(S.pivotCaches).Elements(S.pivotCache).Max(n => Convert.ToInt32(n.Attribute(NoNamespace.cacheId).Value)) + 1;
}
wb.Root.Element(S.pivotCaches).Add(new XElement(S.pivotCache,
new XAttribute(NoNamespace.cacheId, cacheId),
new XAttribute(R.id, document.WorkbookPart.GetIdOfPart(cacheDef))));
document.WorkbookPart.PutXDocument();
// Set the content for the PivotTablePart
pivotTable.PutXDocument(new XDocument(new XElement(S.pivotTableDefinition, new XAttribute("xmlns", S.s),
new XAttribute(NoNamespace.name, "PivotTable1"), new XAttribute(NoNamespace.cacheId, cacheId.ToString()),
new XAttribute(NoNamespace.dataCaption, "Values"),
new XElement(S.location, new XAttribute(NoNamespace._ref, "A3:C20"),
new XAttribute(NoNamespace.firstHeaderRow, "1"), new XAttribute(NoNamespace.firstDataRow, "1"),
new XAttribute(NoNamespace.firstDataCol, "0")), pivotFields)));
return pivotTable;
}
public enum PivotAxis { Row, Column, Page };
public static void AddPivotAxis(SpreadsheetDocument document, WorksheetPart sheet, string fieldName, PivotAxis axis)
{
// Create indexed items in cache and definition
PivotTablePart pivotTablePart = sheet.GetPartsOfType<PivotTablePart>().First();
PivotTableCacheDefinitionPart cacheDefPart = pivotTablePart.GetPartsOfType<PivotTableCacheDefinitionPart>().First();
PivotTableCacheRecordsPart recordsPart = cacheDefPart.GetPartsOfType<PivotTableCacheRecordsPart>().First();
XDocument cacheDef = cacheDefPart.GetXDocument();
int index = Array.FindIndex(cacheDef.Descendants(S.cacheField).ToArray(),
z => z.Attribute(NoNamespace.name).Value == fieldName);
XDocument records = recordsPart.GetXDocument();
List<XElement> values = new List<XElement>();
foreach (XElement rec in records.Descendants(S.r))
{
XElement val = rec.Elements().Skip(index).First();
int x = Array.FindIndex(values.ToArray(), z => XElement.DeepEquals(z, val));
if (x == -1)
{
values.Add(val);
x = values.Count() - 1;
}
val.ReplaceWith(new XElement(S.x, new XAttribute(NoNamespace.v, x)));
}
XElement sharedItems = cacheDef.Descendants(S.cacheField).Skip(index).First().Element(S.sharedItems);
sharedItems.Add(new XAttribute(NoNamespace.count, values.Count()), values);
recordsPart.PutXDocument();
cacheDefPart.PutXDocument();
// Add axis definition to pivot table field
XDocument pivotTable = pivotTablePart.GetXDocument();
XElement pivotField = pivotTable.Descendants(S.pivotField).Skip(index).First();
XElement items = new XElement(S.items, new XAttribute(NoNamespace.count, values.Count() + 1),
values.OrderBy(z => z.Attribute(NoNamespace.v).Value).Select(z => new XElement(S.item,
new XAttribute(NoNamespace.x, Array.FindIndex(values.ToArray(),
a => a.Attribute(NoNamespace.v).Value == z.Attribute(NoNamespace.v).Value)))));
items.Add(new XElement(S.item, new XAttribute(NoNamespace.t, "default")));
switch (axis)
{
case PivotAxis.Column:
pivotField.Add(new XAttribute(NoNamespace.axis, "axisCol"), items);
// Add to colFields
{
XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
if (fields == null)
{
fields = new XElement(S.colFields, new XAttribute(NoNamespace.count, 0));
XElement rowFields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
if (rowFields == null)
pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
else
rowFields.AddAfterSelf(fields);
}
fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, index)));
fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
}
break;
case PivotAxis.Row:
pivotField.Add(new XAttribute(NoNamespace.axis, "axisRow"), items);
// Add to rowFields
{
XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
if (fields == null)
{
fields = new XElement(S.rowFields, new XAttribute(NoNamespace.count, 0));
pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
}
fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, index)));
fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
}
break;
case PivotAxis.Page:
pivotField.Add(new XAttribute(NoNamespace.axis, "axisPage"), items);
// Add to pageFields
{
XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.pageFields);
if (fields == null)
{
fields = new XElement(S.pageFields, new XAttribute(NoNamespace.count, 0));
XElement prev = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
if (prev == null)
prev = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
if (prev == null)
pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
else
prev.AddAfterSelf(fields);
}
fields.Add(new XElement(S.pageField, new XAttribute(NoNamespace.fld, index)));
fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
}
break;
}
pivotTablePart.PutXDocument();
ForcePivotRefresh(cacheDefPart);
}
public static void AddDataValueLabel(SpreadsheetDocument document, WorksheetPart sheet, PivotAxis axis)
{
PivotTablePart pivotTablePart = sheet.GetPartsOfType<PivotTablePart>().First();
XDocument pivotTable = pivotTablePart.GetXDocument();
switch (axis)
{
case PivotAxis.Column:
// Add to colFields
{
XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
if (fields == null)
{
fields = new XElement(S.colFields, new XAttribute(NoNamespace.count, 0));
XElement rowFields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
if (rowFields == null)
pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
else
rowFields.AddAfterSelf(fields);
}
fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, -2)));
fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
}
break;
case PivotAxis.Row:
// Add to rowFields
{
XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
if (fields == null)
{
fields = new XElement(S.rowFields, new XAttribute(NoNamespace.count, 0));
pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
}
fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, -2)));
fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
}
break;
case PivotAxis.Page:
// Add to pageFields
{
XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.pageFields);
if (fields == null)
{
fields = new XElement(S.pageFields, new XAttribute(NoNamespace.count, 0));
XElement prev = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
if (prev == null)
prev = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
if (prev == null)
pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
else
prev.AddAfterSelf(fields);
}
fields.Add(new XElement(S.pageField, new XAttribute(NoNamespace.fld, -2)));
fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
}
break;
}
pivotTablePart.PutXDocument();
PivotTableCacheDefinitionPart cacheDefPart = pivotTablePart.GetPartsOfType<PivotTableCacheDefinitionPart>().First();
ForcePivotRefresh(cacheDefPart);
}
public static void AddDataValue(SpreadsheetDocument document, WorksheetPart sheet, string fieldName)
{
PivotTablePart pivotTablePart = sheet.GetPartsOfType<PivotTablePart>().First();
PivotTableCacheDefinitionPart cacheDefPart = pivotTablePart.GetPartsOfType<PivotTableCacheDefinitionPart>().First();
XDocument cacheDef = cacheDefPart.GetXDocument();
int index = Array.FindIndex(cacheDef.Descendants(S.cacheField).ToArray(),
z => z.Attribute(NoNamespace.name).Value == fieldName);
XDocument pivotTable = pivotTablePart.GetXDocument();
XElement pivotField = pivotTable.Descendants(S.pivotField).Skip(index).First();
pivotField.Add(new XAttribute(NoNamespace.dataField, "1"));
XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.dataFields);
if (fields == null)
{
fields = new XElement(S.dataFields, new XAttribute(NoNamespace.count, 0));
XElement prev = pivotTable.Element(S.pivotTableDefinition).Element(S.pageFields);
if (prev == null)
prev = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
if (prev == null)
prev = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
if (prev == null)
prev = pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields);
prev.AddAfterSelf(fields);
}
fields.Add(new XElement(S.dataField, new XAttribute(NoNamespace.name, "Sum of " + fieldName),
new XAttribute(NoNamespace.fld, index), new XAttribute(NoNamespace.baseField, 0),
new XAttribute(NoNamespace.baseItem, 0)));
int count = fields.Elements(S.dataField).Count();
fields.Attribute(NoNamespace.count).Value = count.ToString();
if (count == 2)
{ // Only when data field count goes from 1 to 2 do we add a special column to label the data fields
AddDataValueLabel(document, sheet, PivotAxis.Column);
}
pivotTablePart.PutXDocument();
ForcePivotRefresh(cacheDefPart);
}
private static void ForcePivotRefresh(PivotTableCacheDefinitionPart cacheDef)
{
XDocument doc = cacheDef.GetXDocument();
XElement def = doc.Element(S.pivotCacheDefinition);
if (def.Attribute(NoNamespace.refreshOnLoad) == null)
def.Add(new XAttribute(NoNamespace.refreshOnLoad, 1));
else
def.Attribute(NoNamespace.refreshOnLoad).Value = "1";
cacheDef.PutXDocument();
}
public static void CheckNumberFormat(SpreadsheetDocument document, int fmtID, string formatCode)
{
XElement numFmt = new XElement(S.numFmt, new XAttribute(NoNamespace.numFmtId, fmtID.ToString()),
new XAttribute(NoNamespace.formatCode, formatCode));
XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
XElement numFmts = styles.Root.Element(S.numFmts);
if (numFmts == null)
{
styles.Root.Element(S.fonts).AddBeforeSelf(new XElement(S.numFmts, new XAttribute(NoNamespace.count, "0")));
numFmts = styles.Root.Element(S.numFmts);
}
int index = Array.FindIndex(numFmts.Elements(S.numFmt).ToArray(),
z => XElement.DeepEquals(z, numFmt));
if (index == -1)
{
numFmts.Add(numFmt);
numFmts.Attribute(NoNamespace.count).Value = numFmts.Elements(S.numFmt).Count().ToString();
document.WorkbookPart.WorkbookStylesPart.PutXDocument();
}
}
public class ColorInfo
{
public enum ColorType { Theme, Indexed };
private bool Auto;
private string RGB;
private int Indexed;
private int Theme;
private double Tint;
public ColorInfo()
{
Auto = true;
}
public ColorInfo(ColorType type, int value)
{
if (type == ColorType.Indexed)
Indexed = value;
else if (type == ColorType.Theme)
Theme = value;
}
public ColorInfo(int theme, double tint)
{
Theme = theme;
Tint = tint;
}
public ColorInfo(string rgb)
{
RGB = rgb;
}
public XElement GetXElement(XName colorName)
{
XElement color = new XElement(colorName);
if (Auto)
color.Add(new XAttribute(NoNamespace.auto, "1"));
else if (RGB != null)
color.Add(new XAttribute(NoNamespace.rgb, RGB));
else if (Indexed != 0)
color.Add(new XAttribute(NoNamespace.indexed, Indexed));
else
color.Add(new XAttribute(NoNamespace.theme, Theme));
if (Tint != 0)
color.Add(new XAttribute(NoNamespace.tint, Tint));
return color;
}
}
public class Font
{
public enum SchemeType { None, Major, Minor };
public bool Bold { get; set; }
public ColorInfo Color { get; set; }
public bool Condense { get; set; }
public bool Extend { get; set; }
public int Family { get; set; }
public bool Italic { get; set; }
public string Name { get; set; }
public bool Outline { get; set; }
public SchemeType Scheme { get; set; }
public bool Shadow { get; set; }
public bool StrikeThrough { get; set; }
public int Size { get; set; }
public bool Underline { get; set; }
public XElement GetXElement()
{
XElement font = new XElement(S.font);
if (Bold)
font.Add(new XElement(S.b));
if (Italic)
font.Add(new XElement(S.i));
if (Underline)
font.Add(new XElement(S.u));
if (StrikeThrough)
font.Add(new XElement(S.strike));
if (Condense)
font.Add(new XElement(S.condense));
if (Extend)
font.Add(new XElement(S.extend));
if (Outline)
font.Add(new XElement(S.outline));
if (Shadow)
font.Add(new XElement(S.shadow));
if (Size != 0)
font.Add(new XElement(S.sz, new XAttribute(NoNamespace.val, Size.ToString())));
if (Color != null)
font.Add(Color.GetXElement(S.color));
if (Name != null)
font.Add(new XElement(S.name, new XAttribute(NoNamespace.val, Name)));
if (Family != 0)
font.Add(new XElement(S.family, new XAttribute(NoNamespace.val, Family.ToString())));
switch (Scheme)
{
case SchemeType.Major:
font.Add(new XElement(S.scheme, new XAttribute(NoNamespace.val, "major")));
break;
case SchemeType.Minor:
font.Add(new XElement(S.scheme, new XAttribute(NoNamespace.val, "minor")));
break;
}
return font;
}
}
public static int GetFontIndex(SpreadsheetDocument document, Font f)
{
XElement font = f.GetXElement();
XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
XElement fonts = styles.Root.Element(S.fonts);
int index = Array.FindIndex(fonts.Elements(S.font).ToArray(),
z => XElement.DeepEquals(z, font));
if (index != -1)
return index;
fonts.Add(font);
fonts.Attribute(NoNamespace.count).Value = fonts.Elements(S.font).Count().ToString();
document.WorkbookPart.WorkbookStylesPart.PutXDocument();
return fonts.Elements(S.font).Count() - 1;
}
public class PatternFill
{
public enum PatternType { None, Solid, DarkDown, DarkGray, DarkGrid, DarkHorizontal, DarkTrellis, DarkUp, DarkVertical,
Gray0625, Gray125, LightDown, LightGray, LightGrid, LightHorizontal, LightTrellis, LightUp, LightVertical, MediumGray };
private PatternType Pattern;