8using NanoXLSX.Interfaces;
9using NanoXLSX.Interfaces.Writer;
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;
51 this.passwordWriter = PlugInLoader.GetPlugIn<IPasswordWriter>(PlugInUUID.PasswordWriter,
new LegacyPasswordWriter());
52 this.passwordWriter.Init(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);
134 void IWorksheetWriter.ReleaseXmlElement()
136 this.worksheet =
null;
144 private static XmlElement CreateMergedCellsElement(Worksheet worksheet)
146 if (worksheet.MergedCells.Count < 1)
150 XmlElement mergeCells = XmlElement.CreateElementWithAttribute(
"mergeCells",
"count", ParserUtils.ToString(worksheet.MergedCells.Count));
151 foreach (KeyValuePair<string, Range> item
in worksheet.MergedCells)
153 mergeCells.AddChildElementWithAttribute(
"mergeCell",
"ref", item.Value.ToString());
163 private XmlElement CreateSheetViewElement(Worksheet worksheet)
165 XmlElement sheetViews = XmlElement.CreateElement(
"sheetViews");
166 XmlElement sheetView = sheetViews.AddChildElementWithAttribute(
"sheetView",
"workbookViewId",
"0");
167 if (Workbook.SelectedWorksheet == worksheet.SheetID - 1 && !worksheet.Hidden)
169 sheetView.AddAttribute(
"tabSelected",
"1");
171 if (worksheet.ViewType != Worksheet.SheetViewType.Normal)
173 if (worksheet.ViewType == Worksheet.SheetViewType.PageLayout)
175 if (worksheet.ShowRuler)
177 sheetView.AddAttribute(
"showRuler",
"1");
181 sheetView.AddAttribute(
"showRuler",
"0");
183 sheetView.AddAttribute(
"view",
"pageLayout");
185 else if (worksheet.ViewType == Worksheet.SheetViewType.PageBreakPreview)
187 sheetView.AddAttribute(
"view",
"pageBreakPreview");
190 if (!worksheet.ShowGridLines)
192 sheetView.AddAttribute(
"showGridLines",
"0");
194 if (!worksheet.ShowRowColumnHeaders)
196 sheetView.AddAttribute(
"showRowColHeaders",
"0");
198 sheetView.AddAttribute(
"zoomScale", ParserUtils.ToString(worksheet.ZoomFactor));
199 foreach (KeyValuePair<Worksheet.SheetViewType, int> scaleFactor
in worksheet.ZoomFactors)
201 if (scaleFactor.Key == worksheet.ViewType)
205 if (scaleFactor.Key == Worksheet.SheetViewType.Normal)
207 sheetView.AddAttribute(
"zoomScaleNormal", ParserUtils.ToString(scaleFactor.Value));
209 else if (scaleFactor.Key == Worksheet.SheetViewType.PageBreakPreview)
211 sheetView.AddAttribute(
"zoomScaleSheetLayoutView", ParserUtils.ToString(scaleFactor.Value));
213 else if (scaleFactor.Key == Worksheet.SheetViewType.PageLayout)
215 sheetView.AddAttribute(
"zoomScalePageLayoutView", ParserUtils.ToString(scaleFactor.Value));
218 sheetView.AddChildElements(CreatePaneElements(worksheet));
219 if (worksheet.SelectedCells.Count > 0)
221 XmlElement selection = sheetView.AddChildElement(
"selection");
222 selection.AddAttribute(
"activeCell", worksheet.SelectedCells[0].StartAddress.ToString());
223 StringBuilder sb =
new StringBuilder(worksheet.SelectedCells.Count * 4);
224 for (
int i = 0; i < worksheet.SelectedCells.Count; i++)
226 sb.Append(worksheet.SelectedCells[i].ToString());
227 if (i < worksheet.SelectedCells.Count - 1)
232 selection.AddAttribute(
"sqref", sb.ToString());
242 private static bool HasPaneSplitting(Worksheet worksheet)
244 if (worksheet.PaneSplitLeftWidth ==
null && worksheet.PaneSplitTopHeight ==
null && worksheet.PaneSplitAddress ==
null)
256 private XmlElement CreateSheetProtectionElement(Worksheet worksheet)
258 if (!worksheet.UseSheetProtection)
262 Dictionary<Worksheet.SheetProtectionValue,
int> actualLockingValues =
new Dictionary<Worksheet.SheetProtectionValue,
int>();
263 if (!worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.Objects))
265 actualLockingValues.Add(Worksheet.SheetProtectionValue.Objects, 1);
267 if (!worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.Scenarios))
269 actualLockingValues.Add(Worksheet.SheetProtectionValue.Scenarios, 1);
271 bool allowSelectLocked = worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.SelectLockedCells);
272 bool allowSelectUnlocked = worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.SelectUnlockedCells);
273 if (allowSelectLocked && !allowSelectUnlocked)
276 allowSelectUnlocked =
true;
278 if (!allowSelectLocked)
280 actualLockingValues.Add(Worksheet.SheetProtectionValue.SelectLockedCells, 1);
282 if (!allowSelectUnlocked)
284 actualLockingValues.Add(Worksheet.SheetProtectionValue.SelectUnlockedCells, 1);
287 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.FormatCells))
289 actualLockingValues.Add(Worksheet.SheetProtectionValue.FormatCells, 0);
291 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.FormatColumns))
293 actualLockingValues.Add(Worksheet.SheetProtectionValue.FormatColumns, 0);
295 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.FormatRows))
297 actualLockingValues.Add(Worksheet.SheetProtectionValue.FormatRows, 0);
299 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.InsertColumns))
301 actualLockingValues.Add(Worksheet.SheetProtectionValue.InsertColumns, 0);
303 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.InsertRows))
305 actualLockingValues.Add(Worksheet.SheetProtectionValue.InsertRows, 0);
307 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.InsertHyperlinks))
309 actualLockingValues.Add(Worksheet.SheetProtectionValue.InsertHyperlinks, 0);
311 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.DeleteColumns))
313 actualLockingValues.Add(Worksheet.SheetProtectionValue.DeleteColumns, 0);
315 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.DeleteRows))
317 actualLockingValues.Add(Worksheet.SheetProtectionValue.DeleteRows, 0);
319 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.Sort))
321 actualLockingValues.Add(Worksheet.SheetProtectionValue.Sort, 0);
323 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.AutoFilter))
325 actualLockingValues.Add(Worksheet.SheetProtectionValue.AutoFilter, 0);
327 if (worksheet.SheetProtectionValues.Contains(Worksheet.SheetProtectionValue.PivotTables))
329 actualLockingValues.Add(Worksheet.SheetProtectionValue.PivotTables, 0);
331 XmlElement sheetProtection = XmlElement.CreateElement(
"sheetProtection");
333 foreach (KeyValuePair<Worksheet.SheetProtectionValue, int> item
in actualLockingValues)
335 temp = Worksheet.GetSheetProtectionName(item.Key);
337 sheetProtection.AddAttribute(temp, ParserUtils.ToString(item.Value));
339 if (passwordWriter.PasswordIsSet())
341 sheetProtection.AddAttributes(passwordWriter.GetAttributes());
343 sheetProtection.AddAttribute(
"sheet",
"1");
344 return sheetProtection;
352 private static List<DynamicRow> GetSortedSheetData(Worksheet worksheet)
354 List<Cell> temp =
new List<Cell>(worksheet.CellValues);
356 DynamicRow row =
new DynamicRow(); ;
357 Dictionary<int, DynamicRow> rows =
new Dictionary<int, DynamicRow>();
361 rowNumber = temp[0].RowNumber;
362 row.RowNumber = rowNumber;
363 foreach (Cell cell
in temp)
365 if (cell.RowNumber != rowNumber)
367 rows.Add(rowNumber, row);
370 RowNumber = cell.RowNumber
372 rowNumber = cell.RowNumber;
374 row.CellDefinitions.Add(cell);
376 if (row.CellDefinitions.Count > 0)
378 rows.Add(rowNumber, row);
381 foreach (KeyValuePair<int, float> rowHeight
in worksheet.RowHeights)
383 if (!rows.ContainsKey(rowHeight.Key))
387 RowNumber = rowHeight.Key
389 rows.Add(rowHeight.Key, row);
392 foreach (KeyValuePair<int, bool> hiddenRow
in worksheet.HiddenRows)
394 if (!rows.ContainsKey(hiddenRow.Key))
398 RowNumber = hiddenRow.Key
400 rows.Add(hiddenRow.Key, row);
403 List<DynamicRow> output = rows.Values.ToList();
404 output.Sort((r1, r2) => r1.RowNumber.CompareTo(r2.RowNumber));
413 private static List<XmlElement> CreatePaneElements(Worksheet worksheet)
415 if (!HasPaneSplitting(worksheet))
419 List<XmlElement> elements =
new List<XmlElement>(2);
420 XmlElement pane = XmlElement.CreateElement(
"pane");
421 bool applyXSplit =
false;
422 bool applyYSplit =
false;
423 if (worksheet.PaneSplitAddress !=
null)
425 bool freeze = worksheet.FreezeSplitPanes !=
null && worksheet.FreezeSplitPanes.Value;
426 int xSplit = worksheet.PaneSplitAddress.Value.Column;
427 int ySplit = worksheet.PaneSplitAddress.Value.Row;
432 pane.AddAttribute(
"xSplit", ParserUtils.ToString(xSplit));
436 pane.AddAttribute(
"xSplit", ParserUtils.ToString(CalculatePaneWidth(worksheet, xSplit)));
444 pane.AddAttribute(
"ySplit", ParserUtils.ToString(ySplit));
448 pane.AddAttribute(
"ySplit", ParserUtils.ToString(CalculatePaneHeight(worksheet, ySplit)));
452 if (freeze && applyXSplit && applyYSplit)
454 pane.AddAttribute(
"state",
"frozenSplit");
458 pane.AddAttribute(
"state",
"frozen");
463 if (worksheet.PaneSplitLeftWidth !=
null)
465 pane.AddAttribute(
"xSplit", ParserUtils.ToString(DataUtils.GetInternalPaneSplitWidth(worksheet.PaneSplitLeftWidth.Value)));
468 if (worksheet.PaneSplitTopHeight !=
null)
470 pane.AddAttribute(
"ySplit", ParserUtils.ToString(DataUtils.GetInternalPaneSplitHeight(worksheet.PaneSplitTopHeight.Value)));
474 if ((applyXSplit || applyYSplit) && worksheet.ActivePane !=
null)
476 switch (worksheet.ActivePane.Value)
478 case Worksheet.WorksheetPane.BottomLeft:
479 pane.AddAttribute(
"activePane",
"bottomLeft");
481 case Worksheet.WorksheetPane.BottomRight:
482 pane.AddAttribute(
"activePane",
"bottomRight");
484 case Worksheet.WorksheetPane.TopLeft:
485 pane.AddAttribute(
"activePane",
"topLeft");
487 case Worksheet.WorksheetPane.TopRight:
488 pane.AddAttribute(
"activePane",
"topRight");
492 string topLeftCell = worksheet.PaneSplitTopLeftCell.Value.GetAddress();
493 pane.AddAttribute(
"topLeftCell", topLeftCell);
495 if (applyXSplit && !applyYSplit)
497 XmlElement selection = XmlElement.CreateElement(
"selection");
498 selection.AddAttribute(
"pane",
"topRight");
499 selection.AddAttribute(
"activeCell", topLeftCell);
500 selection.AddAttribute(
"sqref", topLeftCell);
501 elements.Add(selection);
503 else if (applyYSplit && !applyXSplit)
505 XmlElement selection = XmlElement.CreateElement(
"selection");
506 selection.AddAttribute(
"pane",
"bottomLeft");
507 selection.AddAttribute(
"activeCell", topLeftCell);
508 selection.AddAttribute(
"sqref", topLeftCell);
509 elements.Add(selection);
511 else if (applyYSplit && applyXSplit)
513 XmlElement selection = XmlElement.CreateElement(
"selection");
514 selection.AddAttribute(
"activeCell", topLeftCell);
515 selection.AddAttribute(
"sqref", topLeftCell);
516 elements.Add(selection);
527 private static float CalculatePaneHeight(Worksheet worksheet,
int numberOfRows)
530 for (
int i = 0; i < numberOfRows; i++)
532 if (worksheet.RowHeights.ContainsKey(i))
534 height += DataUtils.GetInternalRowHeight(worksheet.RowHeights[i]);
538 height += DataUtils.GetInternalRowHeight(Worksheet.DefaultWorksheetRowHeight);
541 return DataUtils.GetInternalPaneSplitHeight(height);
550 private static float CalculatePaneWidth(Worksheet worksheet,
int numberOfColumns)
553 for (
int i = 0; i < numberOfColumns; i++)
555 if (worksheet.Columns.ContainsKey(i))
557 width += DataUtils.GetInternalColumnWidth(worksheet.Columns[i].Width);
561 width += DataUtils.GetInternalColumnWidth(Worksheet.DefaultWorksheetColumnWidth);
565 return DataUtils.GetInternalPaneSplitWidth(width) + ((numberOfColumns - 1) * 0f);
573 private static XmlElement CreateColsElement(Worksheet worksheet)
575 XmlElement cols =
null;
576 if (worksheet.Columns.Count == 0)
580 foreach (KeyValuePair<int, Column> column
in worksheet.Columns)
582 if (Comparators.CompareDimensions(column.Value.Width, worksheet.DefaultColumnWidth) == 0 && !column.Value.IsHidden && column.Value.DefaultColumnStyle ==
null)
588 cols = XmlElement.CreateElement(
"cols");
590 XmlElement col = cols.AddChildElement(
"col");
591 col.AddAttribute(
"width", ParserUtils.ToString(DataUtils.GetInternalColumnWidth(column.Value.Width)));
592 string minMax = ParserUtils.ToString(column.Key + 1);
593 col.AddAttribute(
"max", minMax);
594 col.AddAttribute(
"min", minMax);
595 col.AddAttribute(
"customWidth",
"1");
596 if (worksheet.Columns.ContainsKey(column.Key) && worksheet.Columns[column.Key].IsHidden)
598 col.AddAttribute(
"hidden",
"1");
600 if (column.Value.DefaultColumnStyle !=
null)
602 col.AddAttribute(
"style", ParserUtils.ToString(column.Value.DefaultColumnStyle.InternalID.Value));
615 private XmlElement CreateRowElement(DynamicRow dynamicRow, Worksheet worksheet)
617 int rowNumber = dynamicRow.RowNumber;
618 XmlElement row = XmlElement.CreateElementWithAttribute(
"row",
"r", ParserUtils.ToString(rowNumber + 1));
619 if (worksheet.RowHeights.ContainsKey(rowNumber) && Comparators.CompareDimensions(worksheet.RowHeights[rowNumber], worksheet.DefaultRowHeight) != 0)
621 row.AddAttribute(
"dyDescent",
"0.25",
"x14ac");
622 row.AddAttribute(
"customHeight",
"1");
623 row.AddAttribute(
"ht", ParserUtils.ToString(DataUtils.GetInternalRowHeight(worksheet.RowHeights[rowNumber])));
625 if (worksheet.HiddenRows.ContainsKey(rowNumber) && worksheet.HiddenRows[rowNumber])
627 row.AddAttribute(
"hidden",
"1");
630 string valueDef =
"";
631 foreach (Cell item
in dynamicRow.CellDefinitions)
633 XmlAttribute? styleDef =
null;
634 XmlAttribute? typeDef =
null;
636 if (item.CellStyle !=
null)
638 styleDef = XmlAttribute.CreateAttribute(
"s", ParserUtils.ToString(item.CellStyle.InternalID.Value));
640 if (item.DataType == Cell.CellType.Bool)
642 typeDef = XmlAttribute.CreateAttribute(
"t",
"b");
643 if ((
bool)item.Value) { valueDef =
"1"; }
644 else { valueDef =
"0"; }
648 else if (item.DataType == Cell.CellType.Number)
650 typeDef = XmlAttribute.CreateAttribute(
"t",
"n");
651 Type t = item.Value.GetType();
653 if (t == typeof(
byte)) { valueDef = ParserUtils.ToString((
byte)item.Value); }
654 else if (t == typeof(sbyte)) { valueDef = ParserUtils.ToString((sbyte)item.Value); }
655 else if (t == typeof(decimal)) { valueDef = ParserUtils.ToString((decimal)item.Value); }
656 else if (t == typeof(
double)) { valueDef = ParserUtils.ToString((
double)item.Value); }
657 else if (t == typeof(
float)) { valueDef = ParserUtils.ToString((
float)item.Value); }
658 else if (t == typeof(
int)) { valueDef = ParserUtils.ToString((
int)item.Value); }
659 else if (t == typeof(uint)) { valueDef = ParserUtils.ToString((uint)item.Value); }
660 else if (t == typeof(
long)) { valueDef = ParserUtils.ToString((
long)item.Value); }
661 else if (t == typeof(ulong)) { valueDef = ParserUtils.ToString((ulong)item.Value); }
662 else if (t == typeof(
short)) { valueDef = ParserUtils.ToString((
short)item.Value); }
663 else if (t == typeof(ushort)) { valueDef = ParserUtils.ToString((ushort)item.Value); }
666 else if (item.DataType == Cell.CellType.Date)
668 DateTime date = (DateTime)item.Value;
669 valueDef = DataUtils.GetOADateTimeString(date);
672 else if (item.DataType == Cell.CellType.Time)
674 TimeSpan time = (TimeSpan)item.Value;
675 valueDef = DataUtils.GetOATimeString(time);
679 string typeAttribute =
null;
680 if (item.Value ==
null)
682 typeAttribute =
null;
688 if (item.DataType == Cell.CellType.Formula)
690 typeAttribute =
"str";
691 valueDef = item.Value.ToString();
696 if (item.Value is IFormattableText text)
698 valueDef = sharedStrings.Add(text, ParserUtils.ToString(sharedStrings.Count));
702 valueDef = sharedStrings.Add(
new PlainText(item.Value.ToString()), ParserUtils.ToString(sharedStrings.Count));
704 this.sharedStringWriter.SharedStringsTotalCount++;
707 typeDef = XmlAttribute.CreateAttribute(
"t", typeAttribute);
709 if (item.DataType != Cell.CellType.Empty)
711 XmlElement c = row.AddChildElementWithAttribute(
"c",
"r", item.CellAddress);
712 c.AddAttribute(typeDef);
713 c.AddAttribute(styleDef);
714 if (item.DataType == Cell.CellType.Formula)
716 c.AddChildElementWithValue(
"f", XmlUtils.SanitizeXmlValue(item.Value.ToString()));
720 c.AddChildElementWithValue(
"v", XmlUtils.SanitizeXmlValue(valueDef));
723 else if (valueDef ==
null || item.DataType == Cell.CellType.Empty)
725 XmlElement c = row.AddChildElementWithAttribute(
"c",
"r", item.CellAddress);
726 c.AddAttribute(styleDef);
737 private List<XmlElement> CreateRowElements(Worksheet worksheet)
739 List<DynamicRow> cellData = GetSortedSheetData(worksheet);
740 List<XmlElement> rows =
new List<XmlElement>(cellData.Count);
741 foreach (DynamicRow row
in cellData)
743 rows.Add(CreateRowElement(row, worksheet));
749 #region helperClasses
753 internal class DynamicRow
755 public int RowNumber {
get;
set; }
760 public List<Cell> CellDefinitions {
get; }
767 this.CellDefinitions =
new List<Cell>();