123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781 |
- // Copyright (c) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
- #undef DisplayWorkingSet
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Text.RegularExpressions;
- using System.Xml;
- using System.Xml.Linq;
- using DocumentFormat.OpenXml.Packaging;
- using OpenXmlPowerTools;
- namespace OpenXmlPowerTools
- {
- // The classes in SpreadsheetWriter are still a work-in-progress. While they are useful in their current state, I will be enhancing and
- // changing them in the future. In particular, I will be augmenting the various definition classes (WorkbookDfn, WorksheetDfn,
- // RowDfn, and CellDfn.
-
- // They are robust enough in their current form to be used in enterprise, mission critical.
- public class WorkbookDfn
- {
- public IEnumerable<WorksheetDfn> Worksheets;
- }
- public class WorksheetDfn
- {
- public string Name;
- public string TableName;
- public IEnumerable<CellDfn> ColumnHeadings;
- public IEnumerable<RowDfn> Rows;
- }
- public class RowDfn
- {
- public IEnumerable<CellDfn> Cells;
- }
- // Value can be:
- // - string
- // - bool
- // - DateTime
- // - int32, int64, uint, double, float, etc.
- // Standard formats
- public class CellDfn
- {
- public static Dictionary<string, int> StandardFormats = new Dictionary<string, int>
- {
- { "0", 1 },
- { "0.00", 2 },
- { "#,##0", 3 },
- { "#,##0.00", 4 },
- { "0%", 9 },
- { "0.00%", 10 },
- { "0.00E+00", 11 },
- { "# ?/?", 12 },
- { "# ??/??", 13 },
- { "mm-dd-yy", 14 },
- { "d-mmm-yy", 15 },
- { "d-mmm", 16 },
- { "mmm-yy", 17 },
- { "h:mm AM/PM", 18 },
- { "h:mm:ss AM/PM", 19 },
- { "h:mm", 20 },
- { "h:mm:ss", 21 },
- { "h/d/yy h:mm", 22 },
- { "#,##0;(#,##0)", 37 },
- { "#,##0;[Red](#,##0)", 38 },
- { "#,##0.00;(#,##0.00)", 39 },
- { "#,##0.00;[Red](#,##0.00)", 40 },
- { "mm:ss", 45 },
- { "[h]:mm:ss", 46 },
- { "mmss.0", 47 },
- { "##0.0E+0", 48 },
- { "@", 49 },
- };
- public object Value;
- public CellDataType? CellDataType;
- public HorizontalCellAlignment? HorizontalCellAlignment;
- public bool? Bold;
- public bool? Italic;
- public string FormatCode;
- }
- public enum HorizontalCellAlignment
- {
- Left,
- Center,
- Right,
- }
- public enum CellDataType
- {
- Boolean,
- Date,
- Number,
- String,
- }
- public static class SpreadsheetWriter
- {
- public static void Write(string fileName, WorkbookDfn workbook)
- {
- try
- {
- if (fileName == null) throw new ArgumentNullException("fileName");
- if (workbook == null) throw new ArgumentNullException("workbook");
- FileInfo fi = new FileInfo(fileName);
- if (fi.Exists)
- fi.Delete();
- // create the blank workbook
- char[] base64CharArray = _EmptyXlsx
- .Where(c => c != '\r' && c != '\n').ToArray();
- byte[] byteArray =
- System.Convert.FromBase64CharArray(base64CharArray,
- 0, base64CharArray.Length);
- File.WriteAllBytes(fi.FullName, byteArray);
- // open the workbook, and create the TableProperties sheet, populate it
- using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fi.FullName, true))
- {
- WorkbookPart workbookPart = sDoc.WorkbookPart;
- XDocument wXDoc = workbookPart.GetXDocument();
- XElement sheetElement = wXDoc
- .Root
- .Elements(S.sheets)
- .Elements(S.sheet)
- .Where(s => (string)s.Attribute(SSNoNamespace.name) == "Sheet1")
- .FirstOrDefault();
- if (sheetElement == null)
- throw new SpreadsheetWriterInternalException();
- string id = (string)sheetElement.Attribute(R.id);
- sheetElement.Remove();
- workbookPart.PutXDocument();
- WorksheetPart sPart = (WorksheetPart)workbookPart.GetPartById(id);
- workbookPart.DeletePart(sPart);
- XDocument appXDoc = sDoc
- .ExtendedFilePropertiesPart
- .GetXDocument();
- XElement vector = appXDoc
- .Root
- .Elements(EP.TitlesOfParts)
- .Elements(VT.vector)
- .FirstOrDefault();
- if (vector != null)
- {
- vector.SetAttributeValue(SSNoNamespace.size, 0);
- XElement lpstr = vector.Element(VT.lpstr);
- lpstr.Remove();
- }
- XElement vector2 = appXDoc
- .Root
- .Elements(EP.HeadingPairs)
- .Elements(VT.vector)
- .FirstOrDefault();
- XElement variant = vector2
- .Descendants(VT.i4)
- .FirstOrDefault();
- if (variant != null)
- variant.Value = "1";
- sDoc.ExtendedFilePropertiesPart.PutXDocument();
- if (workbook.Worksheets != null)
- foreach (var worksheet in workbook.Worksheets)
- AddWorksheet(sDoc, worksheet);
- workbookPart.WorkbookStylesPart.PutXDocument();
- }
- }
- catch (Exception e)
- {
- Console.WriteLine("Unhandled exception: {0} in {1}",
- e.ToString(), e.Source);
- throw e;
- }
- }
- public static void AddWorksheet(SpreadsheetDocument sDoc, WorksheetDfn worksheetData)
- {
- Regex validSheetName = new Regex(@"^[^'*\[\]/\\:?][^*\[\]/\\:?]{0,30}$");
- if (!validSheetName.IsMatch(worksheetData.Name))
- throw new InvalidSheetNameException(worksheetData.Name);
- // throw WorksheetAlreadyExistsException if a sheet with the same name (case-insensitive) already exists in the workbook
- string UCName = worksheetData.Name.ToUpper();
- XDocument wXDoc = sDoc.WorkbookPart.GetXDocument();
- if (wXDoc
- .Root
- .Elements(S.sheets)
- .Elements(S.sheet)
- .Attributes(SSNoNamespace.name)
- .Select(a => ((string)a).ToUpper())
- .Contains(UCName))
- throw new WorksheetAlreadyExistsException(worksheetData.Name);
- // create the worksheet with the supplied name
- XDocument appXDoc = sDoc
- .ExtendedFilePropertiesPart
- .GetXDocument();
- XElement vector = appXDoc
- .Root
- .Elements(EP.TitlesOfParts)
- .Elements(VT.vector)
- .FirstOrDefault();
- if (vector != null)
- {
- int? size = (int?)vector.Attribute(SSNoNamespace.size);
- if (size == null)
- size = 1;
- else
- size = size + 1;
- vector.SetAttributeValue(SSNoNamespace.size, size);
- vector.Add(
- new XElement(VT.lpstr, worksheetData.Name));
- XElement i4 = appXDoc
- .Root
- .Elements(EP.HeadingPairs)
- .Elements(VT.vector)
- .Elements(VT.variant)
- .Elements(VT.i4)
- .FirstOrDefault();
- if (i4 != null)
- i4.Value = ((int)i4 + 1).ToString();
- sDoc.ExtendedFilePropertiesPart.PutXDocument();
- }
- WorkbookPart workbook = sDoc.WorkbookPart;
- string rId = "R" + Guid.NewGuid().ToString().Replace("-", "");
- WorksheetPart worksheetPart = workbook.AddNewPart<WorksheetPart>(rId);
- XDocument wbXDoc = workbook.GetXDocument();
- XElement sheets = wbXDoc.Descendants(S.sheets).FirstOrDefault();
- sheets.Add(
- new XElement(S.sheet,
- new XAttribute(SSNoNamespace.name, worksheetData.Name.ToString()),
- new XAttribute(SSNoNamespace.sheetId, sheets.Elements(S.sheet).Count() + 1),
- new XAttribute(R.id, rId)));
- workbook.PutXDocument();
- string ws = S.s.ToString();
- string relns = R.r.ToString();
- using (Stream partStream = worksheetPart.GetStream(FileMode.Create, FileAccess.Write))
- {
- using (XmlWriter partXmlWriter = XmlWriter.Create(partStream))
- {
- partXmlWriter.WriteStartDocument();
- partXmlWriter.WriteStartElement("worksheet", ws);
- partXmlWriter.WriteStartElement("sheetData", ws);
- int numColumnHeadingRows = 0;
- int numColumns = 0;
- int numColumnsInRows = 0;
- int numRows;
- if (worksheetData.ColumnHeadings != null)
- {
- RowDfn row = new RowDfn
- {
- Cells = worksheetData.ColumnHeadings
- };
- SerializeRows(sDoc, partXmlWriter, new[] { row }, 1, out numColumns, out numColumnHeadingRows);
- }
- SerializeRows(sDoc, partXmlWriter, worksheetData.Rows, numColumnHeadingRows + 1, out numColumnsInRows,
- out numRows);
- int totalRows = numColumnHeadingRows + numRows;
- int totalColumns = Math.Max(numColumns, numColumnsInRows);
- if (worksheetData.ColumnHeadings != null && worksheetData.TableName != null)
- {
- partXmlWriter.WriteEndElement();
- string rId2 = "R" + Guid.NewGuid().ToString().Replace("-", "");
- partXmlWriter.WriteStartElement("tableParts", ws);
- partXmlWriter.WriteStartAttribute("count");
- partXmlWriter.WriteValue(1);
- partXmlWriter.WriteEndAttribute();
- partXmlWriter.WriteStartElement("tablePart", ws);
- partXmlWriter.WriteStartAttribute("id", relns);
- partXmlWriter.WriteValue(rId2);
- TableDefinitionPart tdp = worksheetPart.AddNewPart<TableDefinitionPart>(rId2);
- XDocument tXDoc = tdp.GetXDocument();
- XElement table = new XElement(S.table,
- new XAttribute(SSNoNamespace.id, 1),
- new XAttribute(SSNoNamespace.name, worksheetData.TableName),
- new XAttribute(SSNoNamespace.displayName, worksheetData.TableName),
- new XAttribute(SSNoNamespace._ref, "A1:" + SpreadsheetMLUtil.IntToColumnId(totalColumns - 1) + totalRows.ToString()),
- new XAttribute(SSNoNamespace.totalsRowShown, 0),
- new XElement(S.autoFilter,
- new XAttribute(SSNoNamespace._ref, "A1:" + SpreadsheetMLUtil.IntToColumnId(totalColumns - 1) + totalRows.ToString())),
- new XElement(S.tableColumns,
- new XAttribute(SSNoNamespace.count, totalColumns),
- worksheetData.ColumnHeadings.Select((ch, i) =>
- new XElement(S.tableColumn,
- new XAttribute(SSNoNamespace.id, i + 1),
- new XAttribute(SSNoNamespace.name, ch.Value)))),
- new XElement(S.tableStyleInfo,
- new XAttribute(SSNoNamespace.name, "TableStyleMedium2"),
- new XAttribute(SSNoNamespace.showFirstColumn, 0),
- new XAttribute(SSNoNamespace.showLastColumn, 0),
- new XAttribute(SSNoNamespace.showRowStripes, 1),
- new XAttribute(SSNoNamespace.showColumnStripes, 0)));
- tXDoc.Add(table);
- tdp.PutXDocument();
- }
- }
- }
- sDoc.WorkbookPart.WorkbookStylesPart.PutXDocument();
- sDoc.WorkbookPart.WorkbookStylesPart.Stylesheet.Save();
- }
- private static void SerializeRows(SpreadsheetDocument sDoc, XmlWriter xmlWriter, IEnumerable<RowDfn> rows,
- int startingRowNumber, out int numColumns, out int numRows)
- {
- int rowCount = 0;
- int rowNumber = startingRowNumber;
- int maxColumns = 0;
- int localNumColumns;
- #if DisplayWorkingSet
- int workingSetInterval = 10000;
- int workingSetCount = 0;
- #endif
- foreach (var row in rows)
- {
- SerializeRow(sDoc, xmlWriter, rowNumber, row, out localNumColumns);
- maxColumns = Math.Max(maxColumns, localNumColumns);
- rowNumber++;
- rowCount++;
- #if DisplayWorkingSet
- if (workingSetCount++ > workingSetInterval)
- {
- workingSetCount = 0;
- Console.WriteLine(Environment.WorkingSet);
- }
- #endif
- }
- numColumns = maxColumns;
- numRows = rowCount;
- }
- private static void SerializeRow(SpreadsheetDocument sDoc, XmlWriter xw, int rowCount, RowDfn row, out int numColumns)
- {
- string ns = S.s.NamespaceName;
- xw.WriteStartElement("row", ns);
- xw.WriteStartAttribute("r");
- xw.WriteValue(rowCount);
- xw.WriteEndAttribute();
- xw.WriteStartAttribute("spans");
- xw.WriteValue("1:" + row.Cells.Count().ToString());
- xw.WriteEndAttribute();
- int cellCount = 0;
- foreach (var cell in row.Cells)
- {
- if (cell != null)
- {
- xw.WriteStartElement("c", ns);
- xw.WriteStartAttribute("r");
- xw.WriteValue(SpreadsheetMLUtil.IntToColumnId(cellCount) + rowCount.ToString());
- xw.WriteEndAttribute();
- if (cell.Bold != null ||
- cell.Italic != null ||
- cell.FormatCode != null ||
- cell.HorizontalCellAlignment != null)
- {
- xw.WriteStartAttribute("s");
- xw.WriteValue(GetCellStyle(sDoc, cell));
- xw.WriteEndAttribute();
- }
- switch (cell.CellDataType)
- {
- case CellDataType.Boolean:
- xw.WriteStartAttribute("t");
- xw.WriteValue("b");
- xw.WriteEndAttribute();
- break;
- case CellDataType.Date:
- xw.WriteStartAttribute("t");
- xw.WriteValue("d");
- xw.WriteEndAttribute();
- break;
- case CellDataType.Number:
- xw.WriteStartAttribute("t");
- xw.WriteValue("n");
- xw.WriteEndAttribute();
- break;
- case CellDataType.String:
- xw.WriteStartAttribute("t");
- xw.WriteValue("str");
- xw.WriteEndAttribute();
- break;
- default:
- xw.WriteStartAttribute("t");
- xw.WriteValue("str");
- xw.WriteEndAttribute();
- break;
- }
- if (cell.Value != null)
- {
- xw.WriteStartElement("v", ns);
- xw.WriteValue(cell.Value);
- xw.WriteEndElement();
- }
- xw.WriteEndElement();
- }
- cellCount++;
- }
- xw.WriteEndElement();
- numColumns = cellCount;
- }
- private static int GetCellStyle(SpreadsheetDocument sDoc, CellDfn cell)
- {
- XDocument sXDoc = sDoc.WorkbookPart.WorkbookStylesPart.GetXDocument();
- var match = sXDoc
- .Root
- .Element(S.cellXfs)
- .Elements(S.xf)
- .Select((e, i) => new
- {
- Element = e,
- Index = i,
- })
- .FirstOrDefault(xf => CompareStyles(sXDoc, xf.Element, cell));
- if (match != null)
- return match.Index;
- // if no match, then create a style
- int newId = CreateNewStyle(sXDoc, cell, sDoc);
- return newId;
- }
- private static int CreateNewStyle(XDocument sXDoc, CellDfn cell, SpreadsheetDocument sDoc)
- {
- XAttribute applyFont = null;
- XAttribute fontId = null;
- if (cell.Bold == true || cell.Italic == true)
- {
- applyFont = new XAttribute(SSNoNamespace.applyFont, 1);
- fontId = new XAttribute(SSNoNamespace.fontId, GetFontId(sXDoc, cell));
- }
- XAttribute applyAlignment = null;
- XElement alignment = null;
- if (cell.HorizontalCellAlignment != null)
- {
- applyAlignment = new XAttribute(SSNoNamespace.applyAlignment, 1);
- alignment = new XElement(S.alignment,
- new XAttribute(SSNoNamespace.horizontal, cell.HorizontalCellAlignment.ToString().ToLower()));
- }
- XAttribute applyNumberFormat = null;
- XAttribute numFmtId = null;
- if (cell.FormatCode != null)
- {
- if (CellDfn.StandardFormats.ContainsKey(cell.FormatCode))
- {
- applyNumberFormat = new XAttribute(SSNoNamespace.applyNumberFormat, 1);
- numFmtId = new XAttribute(SSNoNamespace.numFmtId, CellDfn.StandardFormats[cell.FormatCode]);
- }
- else
- {
- applyNumberFormat = new XAttribute(SSNoNamespace.applyNumberFormat, 1);
- numFmtId = new XAttribute(SSNoNamespace.numFmtId, GetNumFmtId(sXDoc, cell.FormatCode));
- }
- }
- XElement newXf = new XElement(S.xf,
- applyFont,
- fontId,
- applyAlignment,
- alignment,
- applyNumberFormat,
- numFmtId);
- XElement cellXfs = sXDoc
- .Root
- .Element(S.cellXfs);
- if (cellXfs == null)
- {
- cellXfs = new XElement(S.cellXfs,
- new XAttribute(SSNoNamespace.count, 1),
- newXf);
- return 0;
- }
- else
- {
- int currentCount = (int)cellXfs.Attribute(SSNoNamespace.count);
- cellXfs.SetAttributeValue(SSNoNamespace.count, currentCount + 1);
- cellXfs.Add(newXf);
- return currentCount;
- }
- }
- private static int GetFontId(XDocument sXDoc, CellDfn cell)
- {
- XElement fonts = sXDoc.Root.Element(S.fonts);
- if (fonts == null)
- {
- fonts = new XElement(S.fonts,
- new XAttribute(SSNoNamespace.count, 1),
- new XElement(S.font,
- cell.Bold == true ? new XElement(S.b) : null,
- cell.Italic == true ? new XElement(S.i) : null));
- sXDoc.Root.Add(fonts);
- return 0;
- }
- XElement font = new XElement(S.font,
- cell.Bold == true ? new XElement(S.b) : null,
- cell.Italic == true ? new XElement(S.i) : null);
- fonts.Add(font);
- int count = (int)fonts.Attribute(SSNoNamespace.count);
- fonts.SetAttributeValue(SSNoNamespace.count, count + 1);
- return count;
- }
- private static int GetNumFmtId(XDocument sXDoc, string formatCode)
- {
- int xfNumber = 81;
- while (true)
- {
- if (!sXDoc
- .Root
- .Elements(S.numFmts)
- .Elements(S.numFmt)
- .Any(nf => (int)nf.Attribute(SSNoNamespace.numFmtId) == xfNumber))
- break;
- ++xfNumber;
- }
- XElement numFmts = sXDoc.Root.Element(S.numFmts);
- if (numFmts == null)
- {
- numFmts = new XElement(S.numFmts,
- new XAttribute(SSNoNamespace.count, 1),
- new XElement(S.numFmt,
- new XAttribute(SSNoNamespace.numFmtId, xfNumber),
- new XAttribute(SSNoNamespace.formatCode, formatCode)));
- sXDoc.Root.AddFirst(numFmts);
- return xfNumber;
- }
- XElement numFmt = new XElement(S.numFmt,
- new XAttribute(SSNoNamespace.numFmtId, xfNumber),
- new XAttribute(SSNoNamespace.formatCode, formatCode));
- numFmts.Add(numFmt);
- return xfNumber;
- }
- private static bool CompareStyles(XDocument sXDoc, XElement xf, CellDfn cell)
- {
- bool matchFont = MatchFont(sXDoc, xf, cell);
- bool matchAlignment = MatchAlignment(sXDoc, xf, cell);
- bool matchFormat = MatchFormat(sXDoc, xf, cell);
- return (matchFont && matchAlignment && matchFormat);
- }
- private static bool MatchFont(XDocument sXDoc, XElement xf, CellDfn cell)
- {
- if (((int?)xf.Attribute(SSNoNamespace.applyFont) == 0 ||
- xf.Attribute(SSNoNamespace.applyFont) == null) &&
- (cell.Bold == null || cell.Bold == false) &&
- (cell.Italic == null || cell.Italic == false))
- return true;
- if (((int?)xf.Attribute(SSNoNamespace.applyFont) == 0 ||
- xf.Attribute(SSNoNamespace.applyFont) == null) &&
- (cell.Bold == true ||
- cell.Italic == true))
- return false;
- int fontId = (int)xf.Attribute(SSNoNamespace.fontId);
- XElement font = sXDoc
- .Root
- .Element(S.fonts)
- .Elements(S.font)
- .ElementAt(fontId);
- XElement fabFont = new XElement(S.font,
- cell.Bold == true ? new XElement(S.b) : null,
- cell.Italic == true ? new XElement(S.i) : null);
- bool match = XNode.DeepEquals(font, fabFont);
- return match;
- }
- private static bool MatchAlignment(XDocument sXDoc, XElement xf, CellDfn cell)
- {
- if ((int?)xf.Attribute(SSNoNamespace.applyAlignment) == 0 ||
- (xf.Attribute(SSNoNamespace.applyAlignment) == null) &&
- cell.HorizontalCellAlignment == null)
- return true;
- if (xf.Attribute(SSNoNamespace.applyAlignment) == null &&
- cell.HorizontalCellAlignment != null)
- return false;
- string alignment = (string)xf.Element(S.alignment).Attribute(SSNoNamespace.horizontal);
- bool match = alignment == cell.HorizontalCellAlignment.ToString().ToLower();
- return match;
- }
- private static bool MatchFormat(XDocument sXDoc, XElement xf, CellDfn cell)
- {
- if ((int?)xf.Attribute(SSNoNamespace.applyNumberFormat) != 1 &&
- cell.FormatCode == null)
- return true;
- if (xf.Attribute(SSNoNamespace.applyNumberFormat) == null &&
- cell.FormatCode != null)
- return false;
- int numFmtId = (int)xf.Attribute(SSNoNamespace.numFmtId);
- int? nfi = null;
- if (cell.FormatCode != null)
- {
- if (CellDfn.StandardFormats.ContainsKey(cell.FormatCode))
- nfi = CellDfn.StandardFormats[cell.FormatCode];
- if (nfi == numFmtId)
- return true;
- }
- XElement numFmts = sXDoc
- .Root
- .Element(S.numFmts);
- if (numFmts == null)
- return false;
- XElement numFmt = numFmts
- .Elements(S.numFmt)
- .FirstOrDefault(numFmtElement =>
- (int)numFmtElement.Attribute(SSNoNamespace.numFmtId) == numFmtId);
- if (numFmt == null)
- return false;
- string styleFormatCode = (string)numFmt.Attribute(SSNoNamespace.formatCode);
- bool match = styleFormatCode == cell.FormatCode;
- return match;
- }
- private static string _EmptyXlsx = @"UEsDBBQABgAIAAAAIQBi7p1oYQEAAJAEAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACs
- lE1PwzAMhu9I/IcqV9Rm44AQWrcDH0eYxPgBoXHXaGkSxd7Y/j1u9iGEyqaJXRq1sd/3iWtnNFm3
- NltBRONdKYbFQGTgKq+Nm5fiY/aS34sMSTmtrHdQig2gmIyvr0azTQDMONthKRqi8CAlVg20Cgsf
- wPFO7WOriF/jXAZVLdQc5O1gcCcr7wgc5dRpiPHoCWq1tJQ9r/nzliSCRZE9bgM7r1KoEKypFDGp
- XDn9yyXfORScmWKwMQFvGEPIXodu52+DXd4blyYaDdlURXpVLWPItZVfPi4+vV8Ux0V6KH1dmwq0
- r5YtV6DAEEFpbACotUVai1YZt+c+4p+CUaZleGGQ7nxJ+AQH8f8GmZ7/R0gyJwyRNhbwwqfdip5y
- blQE/U6RJ+PiAD+1j3Fw30yjD8gTFOH8KuxHpMvOAwtBJAOHIelrtoMjT9/5hr+6Hbr51qB7vGW6
- T8bfAAAA//8DAFBLAwQUAAYACAAAACEAtVUwI/UAAABMAgAACwAIAl9yZWxzLy5yZWxzIKIEAiig
- AAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AIySz07DMAzG70i8Q+T76m5ICKGlu0xIuyFUHsAk7h+1jaMkQPf2hAOCSmPb0fbnzz9b3u7maVQf
- HGIvTsO6KEGxM2J712p4rZ9WD6BiImdpFMcajhxhV93ebF94pJSbYtf7qLKLixq6lPwjYjQdTxQL
- 8exypZEwUcphaNGTGahl3JTlPYa/HlAtPNXBaggHeweqPvo8+bK3NE1veC/mfWKXToxAnhM7y3bl
- Q2YLqc/bqJpCy0mDFfOc0xHJ+yJjA54m2lxP9P+2OHEiS4nQSODzPN+Kc0Dr64Eun2ip+L3OPOKn
- hOFNZPhhwcUPVF8AAAD//wMAUEsDBBQABgAIAAAAIQCBPpSX9AAAALoCAAAaAAgBeGwvX3JlbHMv
- d29ya2Jvb2sueG1sLnJlbHMgogQBKKAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- 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";
- }
- public class SpreadsheetWriterInternalException : Exception
- {
- public SpreadsheetWriterInternalException()
- : base("Internal error - unexpected content in _EmptyXlsx.")
- {
- }
- }
- public class InvalidSheetNameException : Exception
- {
- public InvalidSheetNameException(string name)
- : base(string.Format("The supplied name ({0}) is not a valid XLSX worksheet name.", name))
- {
- }
- }
- }
|