8using NanoXLSX.Exceptions;
11using NanoXLSX.Registry;
15using System.Collections.Generic;
17using System.IO.Packaging;
20using System.Threading.Tasks;
22using IOException =
NanoXLSX.Exceptions.IOException;
24using XmlElement =
NanoXLSX.Utils.Xml.XmlElement;
32 internal class XlsxWriter : IBaseWriter
36 private static readonly DocumentPath WORKBOOK =
new DocumentPath(
"workbook.xml",
"xl/");
37 private static readonly DocumentPath STYLES =
new DocumentPath(
"styles.xml",
"xl/");
38 private static readonly DocumentPath APP_PROPERTIES =
new DocumentPath(
"app.xml",
"docProps/");
39 private static readonly DocumentPath CORE_PROPERTIES =
new DocumentPath(
"core.xml",
"docProps/");
40 private static readonly DocumentPath SHARED_STRINGS =
new DocumentPath(
"sharedStrings.xml",
"xl/");
41 private static readonly DocumentPath THEME =
new DocumentPath(
"theme1.xml",
"xl/theme/");
45 private int rootPackageIndex = 1;
46 private int xlPackageIndex = 1;
48 private readonly List<PackagePartDefinition> packagePartDefinitions =
new List<PackagePartDefinition>();
50 private readonly Dictionary<string, Dictionary<string, PackagePart>> packageParts =
new Dictionary<string, Dictionary<string, PackagePart>>();
51 private readonly Dictionary<int, DocumentPath> worksheetPaths =
new Dictionary<int, DocumentPath>();
52 private Package
package = null;
60 public Workbook Workbook {
get; }
65 public StyleManager Styles {
get;
private set; }
70 public ISharedStringWriter SharedStringWriter {
get;
set; }
79 public XlsxWriter(Workbook workbook)
81 this.Workbook = workbook;
85 #region documentCreation_methods
99 FileStream fs =
new FileStream(Workbook.Filename, FileMode.Create);
105 throw new IOException(
"An error occurred while saving. See inner exception for details: " + e.Message, e);
114 public async Task SaveAsync()
116 await Task.Run(() => { Save(); });
122 private void RegisterCommonPackageParts()
125 RegisterPackagePart(PackagePartType.Root, PackagePartDefinition.WORKBOOK_PACKAGE_PART_INDEX, WORKBOOK,
@"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
@"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument");
126 if (this.Workbook.WorkbookMetadata !=
null)
128 int index = PackagePartDefinition.METADATA_PACKAGE_PART_START_INDEX;
129 RegisterPackagePart(PackagePartType.Root, index, CORE_PROPERTIES,
@"application/vnd.openxmlformats-package.core-properties+xml",
@"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties");
130 RegisterPackagePart(PackagePartType.Root, index + 1000, APP_PROPERTIES,
@"application/vnd.openxmlformats-officedocument.extended-properties+xml",
@"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties");
132 int worksheetOrderNumber = PackagePartDefinition.WORKSHEET_PACKAGE_PART_START_INDEX;
133 if (this.Workbook.Worksheets.Count == 0)
135 RegisterPackagePart(PackagePartType.Worksheet, worksheetOrderNumber,
"sheet1.xml",
"xl/worksheets",
@"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
@"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet");
139 for (
int i = 0; i < this.Workbook.Worksheets.Count; i++)
141 string fileName =
"sheet" + ParserUtils.ToString(i + 1) +
".xml";
142 RegisterPackagePart(PackagePartType.Worksheet, worksheetOrderNumber, fileName,
"xl/worksheets",
@"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
@"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet");
143 worksheetOrderNumber++;
146 int postWorksheetOrderNumber = PackagePartDefinition.POST_WORSHEET_PACKAGE_PART_START_INDEX;
147 if (Workbook.WorkbookTheme !=
null)
149 RegisterPackagePart(PackagePartType.Other, postWorksheetOrderNumber, THEME,
@"application/vnd.openxmlformats-officedocument.theme+xml",
@"http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme");
150 postWorksheetOrderNumber += 1000;
152 RegisterPackagePart(PackagePartType.Other, postWorksheetOrderNumber, STYLES,
@"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
@"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles");
153 postWorksheetOrderNumber += 1000;
154 RegisterPackagePart(PackagePartType.Other, postWorksheetOrderNumber, SHARED_STRINGS,
@"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
@"http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings");
161 private void PreparePackage()
163 List<PackagePartDefinition> definitions = PackagePartDefinition.Sort(this.packagePartDefinitions);
164 PackagePartDefinition workbookDefinition = definitions.First(p => p.OrderNumber == PackagePartDefinition.WORKBOOK_PACKAGE_PART_INDEX);
165 PackagePart workbookPart = CreateRootPackagePart(workbookDefinition.Path, workbookDefinition.ContentType, workbookDefinition.RelationshipType);
166 foreach (PackagePartDefinition definition
in definitions)
168 if (definition.OrderNumber == PackagePartDefinition.WORKBOOK_PACKAGE_PART_INDEX)
172 if (definition.PartType == PackagePartType.Root)
174 CreateRootPackagePart(definition.Path, definition.ContentType, definition.RelationshipType);
178 CreateXlPackagePart(workbookPart, definition.Path, definition.ContentType, definition.RelationshipType);
179 if (definition.PartType == PackagePartType.Worksheet)
181 worksheetPaths.Add(definition.GetWorksheetIndex(), definition.Path);
194 internal PackagePart CreateRootPackagePart(DocumentPath documentPath,
string contentType,
string relationshipType)
196 Uri uri =
new Uri(documentPath.GetFullPath(), UriKind.Relative);
197 PackagePart part = this.package.CreatePart(uri, contentType, CompressionOption.Normal);
198 if (!packageParts.ContainsKey(documentPath.Path))
200 packageParts.Add(documentPath.Path,
new Dictionary<string, PackagePart>());
202 packageParts[documentPath.Path].Add(documentPath.Filename, part);
203 this.package.CreateRelationship(uri, TargetMode.Internal, relationshipType,
"rId" + ParserUtils.ToString(rootPackageIndex));
215 internal void CreateXlPackagePart(PackagePart parentPart, DocumentPath documentPath,
string contentType,
string relationshipType)
217 Uri uri =
new Uri(documentPath.GetFullPath(), UriKind.Relative);
218 PackagePart part = this.package.CreatePart(uri, contentType, CompressionOption.Normal);
219 if (!packageParts.ContainsKey(documentPath.Path))
221 packageParts.Add(documentPath.Path,
new Dictionary<string, PackagePart>());
223 packageParts[documentPath.Path].Add(documentPath.Filename, part);
224 parentPart.CreateRelationship(uri, TargetMode.Internal, relationshipType,
"rId" + ParserUtils.ToString(xlPackageIndex));
237 internal void RegisterPackagePart(PackagePartDefinition.PackagePartType type,
int orderNumber,
string fileNameInPackage,
string pathInPackage,
string contentType,
string relationshipType)
239 this.packagePartDefinitions.Add(
new PackagePartDefinition(type, orderNumber, fileNameInPackage, pathInPackage, contentType, relationshipType));
250 internal void RegisterPackagePart(PackagePartType type,
int orderNumber, DocumentPath documentPath,
string contentType,
string relationshipType)
252 this.packagePartDefinitions.Add(
new PackagePartDefinition(type, orderNumber, documentPath, contentType, relationshipType));
261 public void SaveAsStream(Stream stream,
bool leaveOpen =
false)
263 Workbook.ResolveMergedCells();
264 this.Styles = StyleManager.GetManagedStyles(Workbook);
267 HandlePackageRegistryQueuePlugIns();
268 HandleQueuePlugIns(PlugInUUID.WriterPrependingQueue);
270 RegisterCommonPackageParts();
271 using (Package xlsxPackage = Package.Open(stream, FileMode.Create))
273 this.package = xlsxPackage;
278 IPlugInWriter workbookWriter = PlugInLoader.GetPlugIn<IPlugInWriter>(PlugInUUID.WorkbookWriter,
new WorkbookWriter());
279 workbookWriter.Init(
this);
280 workbookWriter.Execute();
281 part = packageParts[WORKBOOK.Path][WORKBOOK.Filename];
282 AppendXmlToPackagePart(workbookWriter.XmlElement, part);
285 IPlugInWriter styleWriter = PlugInLoader.GetPlugIn<IPlugInWriter>(PlugInUUID.StyleWriter,
new StyleWriter());
286 styleWriter.Init(
this);
287 styleWriter.Execute();
288 part = packageParts[STYLES.Path][STYLES.Filename];
289 AppendXmlToPackagePart(styleWriter.XmlElement, part);
292 SharedStringWriter = PlugInLoader.GetPlugIn<ISharedStringWriter>(PlugInUUID.SharedStringsWriter,
new SharedStringWriter());
293 SharedStringWriter.Init(
this);
295 IWorksheetWriter worksheetWriter = PlugInLoader.GetPlugIn<IWorksheetWriter>(PlugInUUID.WorksheetWriter,
new WorksheetWriter());
296 worksheetWriter.Init(
this);
297 if (Workbook.Worksheets.Count > 0)
299 for (
int i = 0; i < Workbook.Worksheets.Count; i++)
301 Worksheet item = Workbook.Worksheets[i];
302 part = packageParts[worksheetPaths[i].Path][worksheetPaths[i].Filename];
303 worksheetWriter.CurrentWorksheet = item;
304 worksheetWriter.Execute();
305 AppendXmlToPackagePart(worksheetWriter.XmlElement, part);
310 part = packageParts[worksheetPaths[0].Path][worksheetPaths[0].Filename];
311 worksheetWriter.CurrentWorksheet =
new Worksheet(
"sheet1");
312 worksheetWriter.Execute();
313 AppendXmlToPackagePart(worksheetWriter.XmlElement, part);
317 part = packageParts[SHARED_STRINGS.Path][SHARED_STRINGS.Filename];
318 SharedStringWriter.Execute();
319 AppendXmlToPackagePart(SharedStringWriter.XmlElement, part);
322 if (this.Workbook.WorkbookMetadata !=
null)
324 IPlugInWriter metadataAppWriter = PlugInLoader.GetPlugIn<IPlugInWriter>(PlugInUUID.MetadataAppWriter,
new MetadataAppWriter());
325 metadataAppWriter.Init(
this);
326 metadataAppWriter.Execute();
327 part = packageParts[APP_PROPERTIES.Path][APP_PROPERTIES.Filename];
328 AppendXmlToPackagePart(metadataAppWriter.XmlElement, part);
329 IPlugInWriter metadataCoreWriter = PlugInLoader.GetPlugIn<IPlugInWriter>(PlugInUUID.MetadataCoreWriter,
new MetadataCoreWriter());
330 metadataCoreWriter.Init(
this);
331 metadataCoreWriter.Execute();
332 part = packageParts[CORE_PROPERTIES.Path][CORE_PROPERTIES.Filename];
333 AppendXmlToPackagePart(metadataCoreWriter.XmlElement, part);
337 if (Workbook.WorkbookTheme !=
null)
339 IPlugInWriter themeWriter = PlugInLoader.GetPlugIn<IPlugInWriter>(PlugInUUID.ThemeWriter,
new ThemeWriter());
340 themeWriter.Init(
this);
341 themeWriter.Execute();
342 part = packageParts[THEME.Path][THEME.Filename];
343 AppendXmlToPackagePart(themeWriter.XmlElement, part);
346 HandleQueuePlugIns(PlugInUUID.WriterAppendingQueue);
348 this.package.Flush();
349 this.package.Close();
359 throw new IOException(
"An error occurred while saving. See inner exception for details: " + e.Message, e);
370 public async Task SaveAsStreamAsync(Stream stream,
bool leaveOpen =
false)
372 await Task.Run(() => { SaveAsStream(stream, leaveOpen); });
380 private void HandleQueuePlugIns(
string queueUuid)
382 IPlugInWriter queueWriter;
383 string lastUuid =
null;
386 queueWriter = PlugInLoader.GetNextQueuePlugIn<IPlugInWriter>(queueUuid, lastUuid, out
string currentUuid);
387 if (queueWriter !=
null)
389 queueWriter.Init(
this);
390 queueWriter.Execute();
391 if (queueWriter is IPlugInPackageWriter packageWriter)
393 if (!
string.IsNullOrEmpty(packageWriter.PackagePartPath) && !
string.IsNullOrEmpty(packageWriter.PackagePartFileName))
395 if (packageParts.ContainsKey(packageWriter.PackagePartPath) && packageParts[packageWriter.PackagePartPath].ContainsKey(packageWriter.PackagePartFileName))
397 PackagePart pp = packageParts[packageWriter.PackagePartPath][packageWriter.PackagePartFileName];
398 AppendXmlToPackagePart(packageWriter.XmlElement, pp);
402 lastUuid = currentUuid;
409 }
while (queueWriter !=
null);
415 private void HandlePackageRegistryQueuePlugIns()
417 IPlugInPackageWriter queueWriter;
418 string lastUuid =
null;
421 queueWriter = PlugInLoader.GetNextQueuePlugIn<IPlugInPackageWriter>(PlugInUUID.WriterPackageRegistryQueue, lastUuid, out
string currentUuid);
422 if (queueWriter !=
null)
424 queueWriter.Execute();
425 PackagePartType packagePartType;
426 if (queueWriter.IsRootPackagePart)
428 packagePartType = PackagePartType.Root;
432 packagePartType = PackagePartType.Other;
434 RegisterPackagePart(packagePartType, queueWriter.OrderNumber,
new DocumentPath(queueWriter.PackagePartFileName, queueWriter.PackagePartPath), queueWriter.ContentType, queueWriter.RelationshipType);
435 lastUuid = currentUuid;
442 }
while (queueWriter !=
null);
450 private void AppendXmlToPackagePart(XmlElement rootElement, PackagePart pp)
452 XmlDocument doc = rootElement.TransformToDocument();
453 using (MemoryStream ms =
new MemoryStream())
455 XmlWriterSettings settings =
new XmlWriterSettings
457 Encoding =
new UTF8Encoding(
false),
459 OmitXmlDeclaration =
false
462 using (XmlWriter writer = XmlWriter.Create(ms, settings))
464 writer.WriteProcessingInstruction(
"xml",
"version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"");
469 AddStreamToPackagePart(ms, pp);
478 internal void AddStreamToPackagePart(MemoryStream stream, PackagePart pp)
481 stream.CopyTo(pp.GetStream());