11using NanoXLSX.Registry;
12using NanoXLSX.Registry.Attributes;
14using NanoXLSX.Utils.Xml;
16using System.Collections.Generic;
26 [NanoXlsxPlugIn(PlugInUUID = PlugInUUID.WorksheetWriter)]
27 internal class WorksheetWriter : IWorksheetWriter
29 private XmlElement worksheet;
30 private Worksheet currentWorksheet;
31 private IPasswordWriter passwordWriter;
32 private ISortedMap sharedStrings;
33 private ISharedStringWriter sharedStringWriter;
39 public Workbook Workbook {
get;
set; }
44 public Worksheet CurrentWorksheet
46 get => currentWorksheet;
49 currentWorksheet = value;
50 IPassword passwordInstance = ((Worksheet)CurrentWorksheet).SheetProtectionPassword;
52 this.passwordWriter =
new LegacyPasswordWriter(PasswordType.WorksheetProtection, passwordInstance.PasswordHash);
59 public XmlElement XmlElement {
get => worksheet; }
67 internal WorksheetWriter()
77 public void Init(IBaseWriter baseWriter)
79 this.Workbook = baseWriter.Workbook;
80 this.sharedStringWriter = baseWriter.SharedStringWriter;
81 this.sharedStrings = this.sharedStringWriter.SharedStrings;
89 Worksheet ws = currentWorksheet;
90 ws.RecalculateAutoFilter();
91 ws.RecalculateColumns();
92 worksheet = XmlElement.CreateElement(
"worksheet");
93 worksheet.AddDefaultXmlNameSpace(
"http://schemas.openxmlformats.org/spreadsheetml/2006/main");
94 worksheet.AddNameSpaceAttribute(
"mc",
"xmlns",
"http://schemas.openxmlformats.org/markup-compatibility/2006");
95 worksheet.AddNameSpaceAttribute(
"x14ac",
"xmlns",
"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac");
96 worksheet.AddAttribute(
"mc:Ignorable",
"x14ac");
97 if (ws.GetLastCellAddress().HasValue && ws.GetFirstCellAddress().HasValue)
99 worksheet.AddChildElementWithAttribute(
"dimension",
"ref",
new Range(ws.GetFirstCellAddress().Value, ws.GetLastCellAddress().Value).ToString());
101 if (ws.SelectedCells.Count > 0 || ws.PaneSplitTopHeight !=
null || ws.PaneSplitLeftWidth !=
null || ws.PaneSplitAddress !=
null ||
102 ws.Hidden || ws.ZoomFactor != 100 || ws.ZoomFactors.Count > 1 || !ws.ShowGridLines || !ws.ShowRuler || !ws.ShowRowColumnHeaders || ws.ViewType != Worksheet.SheetViewType.Normal)
104 worksheet.AddChildElement(CreateSheetViewElement(ws));
106 XmlElement sheetFormatPr = worksheet.AddChildElement(
"sheetFormatPr");
107 if (!HasPaneSplitting(ws))
110 sheetFormatPr.AddAttribute(
"defaultColWidth", ParserUtils.ToString(ws.DefaultColumnWidth));
112 sheetFormatPr.AddAttribute(
"defaultRowHeight", ParserUtils.ToString(ws.DefaultRowHeight));
113 sheetFormatPr.AddAttribute(
"baseColWidth", ParserUtils.ToString(ws.DefaultColumnWidth));
114 sheetFormatPr.AddAttribute(
"dyDescent",
"0.25",
"x14ac");
116 worksheet.AddChildElement(CreateColsElement(ws));
118 XmlElement sheetData = worksheet.AddChildElement(
"sheetData");
119 sheetData.AddChildElements(CreateRowElements(ws));
121 worksheet.AddChildElement(CreateMergedCellsElement(ws));
122 worksheet.AddChildElement(CreateSheetProtectionElement(ws));
123 if (ws.AutoFilterRange !=
null)
125 worksheet.AddChildElementWithAttribute(
"autoFilter",
"ref", ws.AutoFilterRange.Value.ToString());
128 WriterPlugInHandler.HandleInlineQueuePlugins(ref worksheet, Workbook, PlugInUUID.WorksheetInlineWriter, currentWorksheet.SheetID);
136 private static XmlElement CreateMergedCellsElement(Worksheet worksheet)
138 if (worksheet.MergedCells.Count < 1)
142 XmlElement mergeCells = XmlElement.CreateElementWithAttribute(
"mergeCells",
"count", ParserUtils.ToString(worksheet.MergedCells.Count));
143 foreach (KeyValuePair<string, Range> item
in worksheet.MergedCells)
145 mergeCells.AddChildElementWithAttribute(
"mergeCell",
"ref", item.Value.ToString());
155 private XmlElement CreateSheetViewElement(Worksheet worksheet)
157 XmlElement sheetViews = XmlElement.CreateElement(
"sheetViews");
158 XmlElement sheetView = sheetViews.AddChildElementWithAttribute(
"sheetView",
"workbookViewId",
"0");
159 if (Workbook.SelectedWorksheet == worksheet.SheetID - 1 && !worksheet.Hidden)
161 sheetView.AddAttribute(
"tabSelected",
"1");
163 if (worksheet.ViewType != Worksheet.SheetViewType.Normal)
165 if (worksheet.ViewType == Worksheet.SheetViewType.PageLayout)
167 if (worksheet.ShowRuler)
169 sheetView.AddAttribute(
"showRuler",
"1");
173 sheetView.AddAttribute(
"showRuler",
"0");
175 sheetView.AddAttribute(
"view",
"pageLayout");
177 else if (worksheet.ViewType == Worksheet.SheetViewType.PageBreakPreview)
179 sheetView.AddAttribute(
"view",
"pageBreakPreview");
182 if (!worksheet.ShowGridLines)
184 sheetView.AddAttribute(
"showGridLines",
"0");
186 if (!worksheet.ShowRowColumnHeaders)
188 sheetView.AddAttribute(
"showRowColHeaders",
"0");
190 sheetView.AddAttribute(
"zoomScale", ParserUtils.ToString(worksheet.ZoomFactor));
191 foreach (KeyValuePair<Worksheet.SheetViewType, int> scaleFactor
in worksheet.ZoomFactors)
193 if (scaleFactor.Key == worksheet.ViewType)
197 if (scaleFactor.Key == Worksheet.SheetViewType.Normal)
199 sheetView.AddAttribute(
"zoomScaleNormal", ParserUtils.ToString(scaleFactor.Value));
201 else if (scaleFactor.Key == Worksheet.SheetViewType.PageBreakPreview)
203 sheetView.AddAttribute(
"zoomScaleSheetLayoutView", ParserUtils.ToString(scaleFactor.Value));
205 else if (scaleFactor.Key == Worksheet.SheetViewType.PageLayout)
207 sheetView.AddAttribute(
"zoomScalePageLayoutView", ParserUtils.ToString(scaleFactor.Value));
210 sheetView.AddChildElements(CreatePaneElements(worksheet));
211 if (worksheet.SelectedCells.Count > 0)
213 XmlElement selection = sheetView.AddChildElement(
"selection");
214 selection.AddAttribute(
"activeCell", worksheet.SelectedCells[0].StartAddress.ToString());
215 StringBuilder sb =
new StringBuilder(worksheet.SelectedCells.Count * 4);
216 for (
int i = 0; i < worksheet.SelectedCells.Count; i++)
218 sb.Append(worksheet.SelectedCells[i].ToString());
219 if (i < worksheet.SelectedCells.Count - 1)
224 selection.AddAttribute(
"sqref", sb.ToString());
234 private static bool HasPaneSplitting(Worksheet worksheet)
236 if (worksheet.PaneSplitLeftWidth ==
null && worksheet.PaneSplitTopHeight ==
null && worksheet.PaneSplitAddress ==
null)
248 private XmlElement CreateSheetProtectionElement(Worksheet worksheet)
250 if (!worksheet.UseSheetProtection)
254 Dictionary<Worksheet.SheetProtectionValue,
int> actualLockingValues =
new Dictionary<Worksheet.SheetProtectionValue,
int>();
255 if (!worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.Objects))
257 actualLockingValues.Add(Worksheet.SheetProtectionValue.Objects, 1);
259 if (!worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.Scenarios))
261 actualLockingValues.Add(Worksheet.SheetProtectionValue.Scenarios, 1);
263 bool allowSelectLocked = worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.SelectLockedCells);
264 bool allowSelectUnlocked = worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.SelectUnlockedCells);
265 if (allowSelectLocked && !allowSelectUnlocked)
268 allowSelectUnlocked =
true;
270 if (!allowSelectLocked)
272 actualLockingValues.Add(Worksheet.SheetProtectionValue.SelectLockedCells, 1);
274 if (!allowSelectUnlocked)
276 actualLockingValues.Add(Worksheet.SheetProtectionValue.SelectUnlockedCells, 1);
279 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.FormatCells))
281 actualLockingValues.Add(Worksheet.SheetProtectionValue.FormatCells, 0);
283 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.FormatColumns))
285 actualLockingValues.Add(Worksheet.SheetProtectionValue.FormatColumns, 0);
287 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.FormatRows))
289 actualLockingValues.Add(Worksheet.SheetProtectionValue.FormatRows, 0);
291 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.InsertColumns))
293 actualLockingValues.Add(Worksheet.SheetProtectionValue.InsertColumns, 0);
295 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.InsertRows))
297 actualLockingValues.Add(Worksheet.SheetProtectionValue.InsertRows, 0);
299 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.InsertHyperlinks))
301 actualLockingValues.Add(Worksheet.SheetProtectionValue.InsertHyperlinks, 0);
303 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.DeleteColumns))
305 actualLockingValues.Add(Worksheet.SheetProtectionValue.DeleteColumns, 0);
307 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.DeleteRows))
309 actualLockingValues.Add(Worksheet.SheetProtectionValue.DeleteRows, 0);
311 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.Sort))
313 actualLockingValues.Add(Worksheet.SheetProtectionValue.Sort, 0);
315 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.AutoFilter))
317 actualLockingValues.Add(Worksheet.SheetProtectionValue.AutoFilter, 0);
319 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.PivotTables))
321 actualLockingValues.Add(Worksheet.SheetProtectionValue.PivotTables, 0);
323 XmlElement sheetProtection = XmlElement.CreateElement(
"sheetProtection");
325 foreach (KeyValuePair<Worksheet.SheetProtectionValue, int> item
in actualLockingValues)
327 temp = Worksheet.GetSheetProtectionName(item.Key);
329 sheetProtection.AddAttribute(temp, ParserUtils.ToString(item.Value));
331 if (passwordWriter.PasswordIsSet())
333 sheetProtection.AddAttributes(passwordWriter.GetAttributes());
335 sheetProtection.AddAttribute(
"sheet",
"1");
336 return sheetProtection;
344 private static List<DynamicRow> GetSortedSheetData(Worksheet worksheet)
346 List<Cell> temp =
new List<Cell>();
347 foreach (KeyValuePair<string, Cell> item
in worksheet.Cells)
349 temp.Add(item.Value);
352 DynamicRow row =
new DynamicRow(); ;
353 Dictionary<int, DynamicRow> rows =
new Dictionary<int, DynamicRow>();
357 rowNumber = temp[0].RowNumber;
358 row.RowNumber = rowNumber;
359 foreach (Cell cell
in temp)
361 if (cell.RowNumber != rowNumber)
363 rows.Add(rowNumber, row);
366 RowNumber = cell.RowNumber
368 rowNumber = cell.RowNumber;
370 row.CellDefinitions.Add(cell);
372 if (row.CellDefinitions.Count > 0)
374 rows.Add(rowNumber, row);
377 foreach (KeyValuePair<int, float> rowHeight
in worksheet.RowHeights)
379 if (!rows.ContainsKey(rowHeight.Key))
383 RowNumber = rowHeight.Key
385 rows.Add(rowHeight.Key, row);
388 foreach (KeyValuePair<int, bool> hiddenRow
in worksheet.HiddenRows)
390 if (!rows.ContainsKey(hiddenRow.Key))
394 RowNumber = hiddenRow.Key
396 rows.Add(hiddenRow.Key, row);
399 List<DynamicRow> output = rows.Values.ToList();
400 output.Sort((r1, r2) => r1.RowNumber.CompareTo(r2.RowNumber));
409 private static List<XmlElement> CreatePaneElements(Worksheet worksheet)
411 if (!HasPaneSplitting(worksheet))
415 List<XmlElement> elements =
new List<XmlElement>(2);
416 XmlElement pane = XmlElement.CreateElement(
"pane");
417 bool applyXSplit =
false;
418 bool applyYSplit =
false;
419 if (worksheet.PaneSplitAddress !=
null)
421 bool freeze = worksheet.FreezeSplitPanes !=
null && worksheet.FreezeSplitPanes.Value;
422 int xSplit = worksheet.PaneSplitAddress.Value.Column;
423 int ySplit = worksheet.PaneSplitAddress.Value.Row;
428 pane.AddAttribute(
"xSplit", ParserUtils.ToString(xSplit));
432 pane.AddAttribute(
"xSplit", ParserUtils.ToString(CalculatePaneWidth(worksheet, xSplit)));
440 pane.AddAttribute(
"ySplit", ParserUtils.ToString(ySplit));
444 pane.AddAttribute(
"ySplit", ParserUtils.ToString(CalculatePaneHeight(worksheet, ySplit)));
448 if (freeze && applyXSplit && applyYSplit)
450 pane.AddAttribute(
"state",
"frozenSplit");
454 pane.AddAttribute(
"state",
"frozen");
459 if (worksheet.PaneSplitLeftWidth !=
null)
461 pane.AddAttribute(
"xSplit", ParserUtils.ToString(DataUtils.GetInternalPaneSplitWidth(worksheet.PaneSplitLeftWidth.Value)));
464 if (worksheet.PaneSplitTopHeight !=
null)
466 pane.AddAttribute(
"ySplit", ParserUtils.ToString(DataUtils.GetInternalPaneSplitHeight(worksheet.PaneSplitTopHeight.Value)));
470 if ((applyXSplit || applyYSplit) && worksheet.ActivePane !=
null)
472 switch (worksheet.ActivePane.Value)
474 case Worksheet.WorksheetPane.BottomLeft:
475 pane.AddAttribute(
"activePane",
"bottomLeft");
477 case Worksheet.WorksheetPane.BottomRight:
478 pane.AddAttribute(
"activePane",
"bottomRight");
480 case Worksheet.WorksheetPane.TopLeft:
481 pane.AddAttribute(
"activePane",
"topLeft");
483 case Worksheet.WorksheetPane.TopRight:
484 pane.AddAttribute(
"activePane",
"topRight");
488 string topLeftCell = worksheet.PaneSplitTopLeftCell.Value.GetAddress();
489 pane.AddAttribute(
"topLeftCell", topLeftCell);
491 if (applyXSplit && !applyYSplit)
493 XmlElement selection = XmlElement.CreateElement(
"selection");
494 selection.AddAttribute(
"pane",
"topRight");
495 selection.AddAttribute(
"activeCell", topLeftCell);
496 selection.AddAttribute(
"sqref", topLeftCell);
497 elements.Add(selection);
499 else if (applyYSplit && !applyXSplit)
501 XmlElement selection = XmlElement.CreateElement(
"selection");
502 selection.AddAttribute(
"pane",
"bottomLeft");
503 selection.AddAttribute(
"activeCell", topLeftCell);
504 selection.AddAttribute(
"sqref", topLeftCell);
505 elements.Add(selection);
507 else if (applyYSplit && applyXSplit)
509 XmlElement selection = XmlElement.CreateElement(
"selection");
510 selection.AddAttribute(
"activeCell", topLeftCell);
511 selection.AddAttribute(
"sqref", topLeftCell);
512 elements.Add(selection);
523 private static float CalculatePaneHeight(Worksheet worksheet,
int numberOfRows)
526 for (
int i = 0; i < numberOfRows; i++)
528 if (worksheet.RowHeights.ContainsKey(i))
530 height += DataUtils.GetInternalRowHeight(worksheet.RowHeights[i]);
534 height += DataUtils.GetInternalRowHeight(Worksheet.DefaultWorksheetRowHeight);
537 return DataUtils.GetInternalPaneSplitHeight(height);
546 private static float CalculatePaneWidth(Worksheet worksheet,
int numberOfColumns)
549 for (
int i = 0; i < numberOfColumns; i++)
551 if (worksheet.Columns.ContainsKey(i))
553 width += DataUtils.GetInternalColumnWidth(worksheet.Columns[i].Width);
557 width += DataUtils.GetInternalColumnWidth(Worksheet.DefaultWorksheetColumnWidth);
561 return DataUtils.GetInternalPaneSplitWidth(width) + ((numberOfColumns - 1) * 0f);
569 private static XmlElement CreateColsElement(Worksheet worksheet)
571 XmlElement cols =
null;
572 if (worksheet.Columns.Count == 0)
576 foreach (KeyValuePair<int, Column> column
in worksheet.Columns)
578 if (Comparators.CompareDimensions(column.Value.Width, worksheet.DefaultColumnWidth) == 0 && !column.Value.IsHidden && column.Value.DefaultColumnStyle ==
null)
584 cols = XmlElement.CreateElement(
"cols");
586 XmlElement col = cols.AddChildElement(
"col");
587 col.AddAttribute(
"width", ParserUtils.ToString(DataUtils.GetInternalColumnWidth(column.Value.Width)));
588 string minMax = ParserUtils.ToString(column.Key + 1);
589 col.AddAttribute(
"max", minMax);
590 col.AddAttribute(
"min", minMax);
591 col.AddAttribute(
"customWidth",
"1");
592 if (worksheet.Columns.ContainsKey(column.Key) && worksheet.Columns[column.Key].IsHidden)
594 col.AddAttribute(
"hidden",
"1");
596 if (column.Value.DefaultColumnStyle !=
null)
598 col.AddAttribute(
"style", ParserUtils.ToString(column.Value.DefaultColumnStyle.InternalID.Value));
611 private XmlElement CreateRowElement(DynamicRow dynamicRow, Worksheet worksheet)
613 int rowNumber = dynamicRow.RowNumber;
614 XmlElement row = XmlElement.CreateElementWithAttribute(
"row",
"r", ParserUtils.ToString(rowNumber + 1));
615 if (worksheet.RowHeights.ContainsKey(rowNumber) && Comparators.CompareDimensions(worksheet.RowHeights[rowNumber], worksheet.DefaultRowHeight) != 0)
617 row.AddAttribute(
"dyDescent",
"0.25",
"x14ac");
618 row.AddAttribute(
"customHeight",
"1");
619 row.AddAttribute(
"ht", ParserUtils.ToString(DataUtils.GetInternalRowHeight(worksheet.RowHeights[rowNumber])));
621 if (worksheet.HiddenRows.ContainsKey(rowNumber) && worksheet.HiddenRows[rowNumber])
623 row.AddAttribute(
"hidden",
"1");
626 string valueDef =
"";
627 foreach (Cell item
in dynamicRow.CellDefinitions)
629 XmlAttribute? styleDef =
null;
630 XmlAttribute? typeDef =
null;
632 if (item.CellStyle !=
null)
634 styleDef = XmlAttribute.CreateAttribute(
"s", ParserUtils.ToString(item.CellStyle.InternalID.Value));
636 if (item.DataType == Cell.CellType.Bool)
638 typeDef = XmlAttribute.CreateAttribute(
"t",
"b");
639 if ((
bool)item.Value) { valueDef =
"1"; }
640 else { valueDef =
"0"; }
644 else if (item.DataType == Cell.CellType.Number)
646 typeDef = XmlAttribute.CreateAttribute(
"t",
"n");
647 Type t = item.Value.GetType();
649 if (t == typeof(
byte)) { valueDef = ParserUtils.ToString((
byte)item.Value); }
650 else if (t == typeof(sbyte)) { valueDef = ParserUtils.ToString((sbyte)item.Value); }
651 else if (t == typeof(decimal)) { valueDef = ParserUtils.ToString((decimal)item.Value); }
652 else if (t == typeof(
double)) { valueDef = ParserUtils.ToString((
double)item.Value); }
653 else if (t == typeof(
float)) { valueDef = ParserUtils.ToString((
float)item.Value); }
654 else if (t == typeof(
int)) { valueDef = ParserUtils.ToString((
int)item.Value); }
655 else if (t == typeof(uint)) { valueDef = ParserUtils.ToString((uint)item.Value); }
656 else if (t == typeof(
long)) { valueDef = ParserUtils.ToString((
long)item.Value); }
657 else if (t == typeof(ulong)) { valueDef = ParserUtils.ToString((ulong)item.Value); }
658 else if (t == typeof(
short)) { valueDef = ParserUtils.ToString((
short)item.Value); }
659 else if (t == typeof(ushort)) { valueDef = ParserUtils.ToString((ushort)item.Value); }
662 else if (item.DataType == Cell.CellType.Date)
664 DateTime date = (DateTime)item.Value;
665 valueDef = DataUtils.GetOADateTimeString(date);
668 else if (item.DataType == Cell.CellType.Time)
670 TimeSpan time = (TimeSpan)item.Value;
671 valueDef = DataUtils.GetOATimeString(time);
675 string typeAttribute =
null;
676 if (item.Value ==
null)
678 typeAttribute =
null;
684 if (item.DataType == Cell.CellType.Formula)
686 typeAttribute =
"str";
687 valueDef = item.Value.ToString();
692 if (item.Value is IFormattableText text)
694 valueDef = sharedStrings.Add(text, ParserUtils.ToString(sharedStrings.Count));
698 valueDef = sharedStrings.Add(
new PlainText(item.Value.ToString()), ParserUtils.ToString(sharedStrings.Count));
700 this.sharedStringWriter.SharedStringsTotalCount++;
703 typeDef = XmlAttribute.CreateAttribute(
"t", typeAttribute);
705 if (item.DataType != Cell.CellType.Empty)
707 XmlElement c = row.AddChildElementWithAttribute(
"c",
"r", item.CellAddress);
708 c.AddAttribute(typeDef);
709 c.AddAttribute(styleDef);
710 if (item.DataType == Cell.CellType.Formula)
712 c.AddChildElementWithValue(
"f", XmlUtils.SanitizeXmlValue(item.Value.ToString()));
716 c.AddChildElementWithValue(
"v", XmlUtils.SanitizeXmlValue(valueDef));
719 else if (valueDef ==
null || item.DataType == Cell.CellType.Empty)
721 XmlElement c = row.AddChildElementWithAttribute(
"c",
"r", item.CellAddress);
722 c.AddAttribute(styleDef);
733 private List<XmlElement> CreateRowElements(Worksheet worksheet)
735 List<DynamicRow> cellData = GetSortedSheetData(worksheet);
736 List<XmlElement> rows =
new List<XmlElement>(cellData.Count);
737 foreach (DynamicRow row
in cellData)
739 rows.Add(CreateRowElement(row, worksheet));
745 #region helperClasses
749 internal class DynamicRow
751 public int RowNumber {
get;
set; }
756 public List<Cell> CellDefinitions {
get; }
763 this.CellDefinitions =
new List<Cell>();
Static class that contains enums for password handling.