From 41cacf62ff4ef8aed8b04bd1ad90a3be4d17756a Mon Sep 17 00:00:00 2001 From: KharchenkoVV Date: Wed, 15 Sep 2021 10:43:46 +0500 Subject: [PATCH 1/8] Raw(!!) working OpenXML library code with creating Excel file and writing data --- ConsoleApp1/Program.cs | 92 ++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index 05546ee7..5345b3c9 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -9,47 +9,87 @@ using DocumentFormat.OpenXml.Spreadsheet; //using AutoMapper; using Microsoft.EntityFrameworkCore; using System; +using System.IO; +using System.Linq; namespace ConsoleApp1 { + //var options = new DbContextOptionsBuilder() + // .UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True") + // .Options; + //var context = new AsbCloudDbContext(options); + class Program { static void Main(/*string[] args*/) { - //var options = new DbContextOptionsBuilder() - // .UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True") - // .Options; - //var context = new AsbCloudDbContext(options); + // source files + using var firstExcel = SpreadsheetDocument.Open(@"D:\excels\excel1.xlsx", true); + using var secondExcel = SpreadsheetDocument.Open(@"D:\excels\excel2.xlsx", true); - var fileNames = new string[] { @"d:\temp\1\book1.xlsx", @"d:\temp\1\book2.xlsx" }; - using var spreadsheetDocument = SpreadsheetDocument.Create(@"d:\temp\1\b.xlsx", SpreadsheetDocumentType.Workbook); - // Add a WorkbookPart and Workbook objects. - WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart(); - workbookpart.Workbook = new Workbook(); - // Add a WorksheetPart to the WorkbookPart. - WorksheetPart worksheetPart = workbookpart.AddNewPart(); - worksheetPart.Worksheet = new Worksheet(new SheetData()); - - // Add Sheets to the Workbook. - Sheets sheets = spreadsheetDocument.WorkbookPart.Workbook. - AppendChild(new Sheets()); - - // Append a new worksheet and associate it with the workbook. - Sheet sheet = new Sheet() + // init destination file + using var resultExcel = SpreadsheetDocument.Create(@"D:\excels\result.xlsx", SpreadsheetDocumentType.Workbook); + WorkbookPart resultExcelWorkbookpart = resultExcel.AddWorkbookPart(); + resultExcelWorkbookpart.Workbook = new Workbook { - Id = spreadsheetDocument.WorkbookPart. - GetIdOfPart(worksheetPart), - SheetId = 1, - Name = "mySheetNameISHere!" + Sheets = new Sheets() }; - sheets.Append(sheet); + var resultExcelWorksheetPart = resultExcelWorkbookpart.AddNewPart(); + resultExcelWorksheetPart.Worksheet = new Worksheet(new SheetData()); + resultExcelWorkbookpart.Workbook.Save(); + resultExcel.Close(); + + + foreach (var sheet in firstExcel.WorkbookPart.Workbook.Descendants()) + { + //resultExcel.WorkbookPart.Workbook.Sheets.Append(sheet.CloneNode(true)); // Clone empty sheet + + var firstExcelWorkSheetPart = firstExcel.WorkbookPart.GetPartById(sheet.Id) as WorksheetPart; // get rows from source + var rows = firstExcelWorkSheetPart.Worksheet.Descendants(); // Rows + + } + + + + //resultExcel.Close(); + + + + using (var document = SpreadsheetDocument.Create(@"D:\excels\result123123.xlsx", SpreadsheetDocumentType.Workbook, true)) + { + // Add a WorkbookPart to the document. + var workbookPart = document.AddWorkbookPart(); + workbookPart.Workbook = new Workbook(); + + // Add a WorksheetPart to the WorkbookPart. + var worksheetPart1 = workbookPart.AddNewPart(); + worksheetPart1.Worksheet = new Worksheet(); + var sheets = workbookPart.Workbook.AppendChild(new Sheets()); + + + var sheet = new Sheet() { Id = workbookPart.GetIdOfPart(worksheetPart1), SheetId = 1, Name = "Test Sheet" }; + sheets.Append(sheet); + workbookPart.Workbook.Save(); + } + + using (var document = SpreadsheetDocument.Open(@"D:\excels\result123123.xlsx", true)) + { + var worksheetPart1 = document.WorkbookPart.WorksheetParts.First(); + var sheetData1 = worksheetPart1.Worksheet.AppendChild(new SheetData()); + + for (uint i = 1; i < 10; i++) + { + var row1 = new Row(); + row1.Append(new Cell { CellValue = new CellValue(i.ToString()) }); + sheetData1.AppendChild(row1); + } + } + - spreadsheetDocument.Save(); - spreadsheetDocument.Close(); Console.WriteLine("Done. Press any key to quit."); Console.ReadKey(); From 22827b8dcf539915b99edbfa3f0dbae17bcd74cc Mon Sep 17 00:00:00 2001 From: KharchenkoVV Date: Wed, 15 Sep 2021 12:00:46 +0500 Subject: [PATCH 2/8] OpenXML code writing only numeric cells from source Excel file --- ConsoleApp1/Program.cs | 78 ++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index 5345b3c9..5dfbf7c3 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -25,72 +25,60 @@ namespace ConsoleApp1 static void Main(/*string[] args*/) { - // source files using var firstExcel = SpreadsheetDocument.Open(@"D:\excels\excel1.xlsx", true); using var secondExcel = SpreadsheetDocument.Open(@"D:\excels\excel2.xlsx", true); - - // init destination file - using var resultExcel = SpreadsheetDocument.Create(@"D:\excels\result.xlsx", SpreadsheetDocumentType.Workbook); - WorkbookPart resultExcelWorkbookpart = resultExcel.AddWorkbookPart(); - resultExcelWorkbookpart.Workbook = new Workbook - { - Sheets = new Sheets() - }; - var resultExcelWorksheetPart = resultExcelWorkbookpart.AddNewPart(); - resultExcelWorksheetPart.Worksheet = new Worksheet(new SheetData()); - resultExcelWorkbookpart.Workbook.Save(); - resultExcel.Close(); - - - foreach (var sheet in firstExcel.WorkbookPart.Workbook.Descendants()) - { - //resultExcel.WorkbookPart.Workbook.Sheets.Append(sheet.CloneNode(true)); // Clone empty sheet - - var firstExcelWorkSheetPart = firstExcel.WorkbookPart.GetPartById(sheet.Id) as WorksheetPart; // get rows from source - var rows = firstExcelWorkSheetPart.Worksheet.Descendants(); // Rows - - } - - - - //resultExcel.Close(); - - - - using (var document = SpreadsheetDocument.Create(@"D:\excels\result123123.xlsx", SpreadsheetDocumentType.Workbook, true)) + using (var resultExcel = SpreadsheetDocument.Create(@"D:\excels\result.xlsx", SpreadsheetDocumentType.Workbook, true)) { // Add a WorkbookPart to the document. - var workbookPart = document.AddWorkbookPart(); + var workbookPart = resultExcel.AddWorkbookPart(); workbookPart.Workbook = new Workbook(); // Add a WorksheetPart to the WorkbookPart. - var worksheetPart1 = workbookPart.AddNewPart(); - worksheetPart1.Worksheet = new Worksheet(); + var worksheetPart = workbookPart.AddNewPart(); + worksheetPart.Worksheet = new Worksheet(); var sheets = workbookPart.Workbook.AppendChild(new Sheets()); - - var sheet = new Sheet() { Id = workbookPart.GetIdOfPart(worksheetPart1), SheetId = 1, Name = "Test Sheet" }; - sheets.Append(sheet); + //var sheet = new Sheet() { Id = workbookPart.GetIdOfPart(worksheetPart), SheetId = 1, Name = "Test Sheet" }; + //sheets.Append(sheet); workbookPart.Workbook.Save(); } - using (var document = SpreadsheetDocument.Open(@"D:\excels\result123123.xlsx", true)) + using (var resultExcel = SpreadsheetDocument.Open(@"D:\excels\result.xlsx", true)) { - var worksheetPart1 = document.WorkbookPart.WorksheetParts.First(); - var sheetData1 = worksheetPart1.Worksheet.AppendChild(new SheetData()); + var resultExcelWorkSheetPart = resultExcel.WorkbookPart.WorksheetParts.First(); + var resultExcelSheetData = resultExcelWorkSheetPart.Worksheet.AppendChild(new SheetData()); - for (uint i = 1; i < 10; i++) + foreach (var sheet in firstExcel.WorkbookPart.Workbook.Descendants()) { - var row1 = new Row(); - row1.Append(new Cell { CellValue = new CellValue(i.ToString()) }); - sheetData1.AppendChild(row1); + var newSheet = new Sheet() { + Id = resultExcel.WorkbookPart.GetIdOfPart(resultExcelWorkSheetPart), + SheetId = sheet.SheetId.Value, + Name = sheet.Name + }; + resultExcel.WorkbookPart.Workbook.Sheets.Append(newSheet); + + var firstExcelWorkSheetPart = firstExcel.WorkbookPart.GetPartById(sheet.Id) as WorksheetPart; // get rows from source + var firstExcelRows = firstExcelWorkSheetPart.Worksheet.Descendants(); // retrieved rows + //var firstExcelRows = sheet.Descendants(); + + foreach (var row in firstExcelRows) + { + resultExcelSheetData.AppendChild(row.CloneNode(true)); + } } + + //for (uint i = 1; i < 10; i++) // fills cells fine + //{ + // var row = new Row(); + // row.Append(new Cell { CellValue = new CellValue(i.ToString()) }); + // sheetData.AppendChild(row.CloneNode(true)); + //} } - + //resultExcel.Close(); Console.WriteLine("Done. Press any key to quit."); Console.ReadKey(); } From 246bf9b8a526839e9b11585c63c21a86f588dfa2 Mon Sep 17 00:00:00 2001 From: KharchenkoVV Date: Wed, 15 Sep 2021 15:22:24 +0500 Subject: [PATCH 3/8] Added excel content copying with cell styles --- ConsoleApp1/Program.cs | 62 ++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index 5dfbf7c3..1f80a4af 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -1,15 +1,9 @@ -using AsbCloudApp.Data; -using AsbCloudDb.Model; -using AsbCloudInfrastructure.Services; -using AsbCloudInfrastructure.Services.Cache; -using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; //using AsbSaubReport; //using AutoMapper; -using Microsoft.EntityFrameworkCore; using System; -using System.IO; using System.Linq; namespace ConsoleApp1 @@ -19,6 +13,7 @@ namespace ConsoleApp1 // .Options; //var context = new AsbCloudDbContext(options); + class Program { @@ -29,26 +24,38 @@ namespace ConsoleApp1 using var secondExcel = SpreadsheetDocument.Open(@"D:\excels\excel2.xlsx", true); - using (var resultExcel = SpreadsheetDocument.Create(@"D:\excels\result.xlsx", SpreadsheetDocumentType.Workbook, true)) + using (var resultExcel = SpreadsheetDocument.Create(@"D:\excels\result.xlsx", + SpreadsheetDocumentType.Workbook, true)) { - // Add a WorkbookPart to the document. var workbookPart = resultExcel.AddWorkbookPart(); workbookPart.Workbook = new Workbook(); - // Add a WorksheetPart to the WorkbookPart. var worksheetPart = workbookPart.AddNewPart(); worksheetPart.Worksheet = new Worksheet(); var sheets = workbookPart.Workbook.AppendChild(new Sheets()); - //var sheet = new Sheet() { Id = workbookPart.GetIdOfPart(worksheetPart), SheetId = 1, Name = "Test Sheet" }; + var sharedStringTablePart = resultExcel.WorkbookPart. + AddNewPart(); + + var stylesPart = resultExcel.WorkbookPart. + AddNewPart(); + + //var sheet = new Sheet() + //{ + // Id = workbookPart.GetIdOfPart(worksheetPart), + // SheetId = 1, + // Name = "Test Sheet" + //}; //sheets.Append(sheet); workbookPart.Workbook.Save(); } using (var resultExcel = SpreadsheetDocument.Open(@"D:\excels\result.xlsx", true)) { - var resultExcelWorkSheetPart = resultExcel.WorkbookPart.WorksheetParts.First(); - var resultExcelSheetData = resultExcelWorkSheetPart.Worksheet.AppendChild(new SheetData()); + var resultExcelWorkSheetPart = resultExcel.WorkbookPart. + WorksheetParts.First(); + var resultExcelSheetData = resultExcelWorkSheetPart. + Worksheet.AppendChild(new SheetData()); foreach (var sheet in firstExcel.WorkbookPart.Workbook.Descendants()) { @@ -59,14 +66,33 @@ namespace ConsoleApp1 }; resultExcel.WorkbookPart.Workbook.Sheets.Append(newSheet); - var firstExcelWorkSheetPart = firstExcel.WorkbookPart.GetPartById(sheet.Id) as WorksheetPart; // get rows from source - var firstExcelRows = firstExcelWorkSheetPart.Worksheet.Descendants(); // retrieved rows - //var firstExcelRows = sheet.Descendants(); + var firstExcelWorkSheetPart = firstExcel.WorkbookPart. + GetPartById(sheet.Id) as WorksheetPart; + var firstExcelRows = firstExcelWorkSheetPart.Worksheet. + Descendants(); // retrieved rows from source + + // Числовые ячейки хранятся как есть и переносятся без проблем, а вот значения строковых ячеек хранятся + // в отдельной таблице. При этом в самой строковой ячейке хранится индекс этой строки в таблице + // (чтоб не хранить кучу одинаковых строк в документе. Что-то вроде интернирования строк). + // Тут как раз переносится клон этой таблицы из исходного файла в результирующий. + + var firstExcelStringTable = firstExcel.WorkbookPart.GetPartsOfType() + .FirstOrDefault().SharedStringTable; + resultExcel.WorkbookPart.SharedStringTablePart.SharedStringTable = + firstExcelStringTable.CloneNode(true) as SharedStringTable; + + // Аналогично переносим таблицу стилей ячеек (жирный шрифт и прочее). + // Иначе ячейки переносятся абсолютно обычными, без всякой стилизации. + + var stylesSheet = firstExcel.WorkbookPart.GetPartsOfType() + .FirstOrDefault().Stylesheet; + resultExcel.WorkbookPart.WorkbookStylesPart.Stylesheet = + stylesSheet.CloneNode(true) as Stylesheet; + + // Клонируем ряды в новый файл (без клонирования они не вытаскиваются из общего дерева) foreach (var row in firstExcelRows) - { resultExcelSheetData.AppendChild(row.CloneNode(true)); - } } //for (uint i = 1; i < 10; i++) // fills cells fine From 643c04be304820f6590ea28556ffd08f39e78280 Mon Sep 17 00:00:00 2001 From: KharchenkoVV Date: Wed, 15 Sep 2021 16:37:17 +0500 Subject: [PATCH 4/8] Added cloning data to separate lists as in source file --- ConsoleApp1/Program.cs | 51 +++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index 1f80a4af..1ff4e721 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -19,19 +19,20 @@ namespace ConsoleApp1 static void Main(/*string[] args*/) { + const string firstExcelPath = @"D:\excels\excel1.xlsx"; + const string secondExcelPath = @"D:\excels\excel2.xlsx"; + const string resultExcelPath = @"D:\excels\result.xlsx"; - using var firstExcel = SpreadsheetDocument.Open(@"D:\excels\excel1.xlsx", true); - using var secondExcel = SpreadsheetDocument.Open(@"D:\excels\excel2.xlsx", true); + using var firstExcel = SpreadsheetDocument.Open(firstExcelPath, true); + using var secondExcel = SpreadsheetDocument.Open(secondExcelPath, true); - using (var resultExcel = SpreadsheetDocument.Create(@"D:\excels\result.xlsx", + using (var resultExcel = SpreadsheetDocument.Create(resultExcelPath, SpreadsheetDocumentType.Workbook, true)) { var workbookPart = resultExcel.AddWorkbookPart(); workbookPart.Workbook = new Workbook(); - var worksheetPart = workbookPart.AddNewPart(); - worksheetPart.Worksheet = new Worksheet(); var sheets = workbookPart.Workbook.AppendChild(new Sheets()); var sharedStringTablePart = resultExcel.WorkbookPart. @@ -40,25 +41,22 @@ namespace ConsoleApp1 var stylesPart = resultExcel.WorkbookPart. AddNewPart(); - //var sheet = new Sheet() - //{ - // Id = workbookPart.GetIdOfPart(worksheetPart), - // SheetId = 1, - // Name = "Test Sheet" - //}; - //sheets.Append(sheet); workbookPart.Workbook.Save(); } - using (var resultExcel = SpreadsheetDocument.Open(@"D:\excels\result.xlsx", true)) + using (var resultExcel = SpreadsheetDocument.Open(resultExcelPath, true)) { - var resultExcelWorkSheetPart = resultExcel.WorkbookPart. - WorksheetParts.First(); - var resultExcelSheetData = resultExcelWorkSheetPart. - Worksheet.AppendChild(new SheetData()); - foreach (var sheet in firstExcel.WorkbookPart.Workbook.Descendants()) { + // Чтобы писать на новый лист, нужен новый WorkSheetPart, WorkSheet, SheetData и Sheet. + // https://stackoverflow.com/questions/9120544/openxml-multiple-sheets + + var resultExcelWorkSheetPart = resultExcel.WorkbookPart.AddNewPart(); + resultExcelWorkSheetPart.Worksheet = new Worksheet(); + + var resultExcelSheetData = resultExcelWorkSheetPart. + Worksheet.AppendChild(new SheetData()); + var newSheet = new Sheet() { Id = resultExcel.WorkbookPart.GetIdOfPart(resultExcelWorkSheetPart), SheetId = sheet.SheetId.Value, @@ -68,13 +66,18 @@ namespace ConsoleApp1 var firstExcelWorkSheetPart = firstExcel.WorkbookPart. GetPartById(sheet.Id) as WorksheetPart; - var firstExcelRows = firstExcelWorkSheetPart.Worksheet. - Descendants(); // retrieved rows from source + + // Не самая лучшая выборка рядов текущей страницы исходного файла. + // Наверняка для этого есть метод. + var firstExcelRows = firstExcelWorkSheetPart.Worksheet.ChildElements + .FirstOrDefault(w => w.LocalName == "sheetData").ChildElements; // Числовые ячейки хранятся как есть и переносятся без проблем, а вот значения строковых ячеек хранятся // в отдельной таблице. При этом в самой строковой ячейке хранится индекс этой строки в таблице // (чтоб не хранить кучу одинаковых строк в документе. Что-то вроде интернирования строк). // Тут как раз переносится клон этой таблицы из исходного файла в результирующий. + // Разные всяческие Part'ы можно найти тут: + // https://docs.microsoft.com/ru-ru/dotnet/api/documentformat.openxml.packaging.spreadsheetdocument.workbookpart?view=openxml-2.8.1 var firstExcelStringTable = firstExcel.WorkbookPart.GetPartsOfType() .FirstOrDefault().SharedStringTable; @@ -94,17 +97,9 @@ namespace ConsoleApp1 foreach (var row in firstExcelRows) resultExcelSheetData.AppendChild(row.CloneNode(true)); } - - //for (uint i = 1; i < 10; i++) // fills cells fine - //{ - // var row = new Row(); - // row.Append(new Cell { CellValue = new CellValue(i.ToString()) }); - // sheetData.AppendChild(row.CloneNode(true)); - //} } - //resultExcel.Close(); Console.WriteLine("Done. Press any key to quit."); Console.ReadKey(); } From d45dc54230cc0da24d33a589ebb7b9505eb2082a Mon Sep 17 00:00:00 2001 From: KharchenkoVV Date: Wed, 15 Sep 2021 17:48:34 +0500 Subject: [PATCH 5/8] Added union of several excels to one file --- ConsoleApp1/Program.cs | 69 +++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index 1ff4e721..b0feb913 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -4,6 +4,7 @@ using DocumentFormat.OpenXml.Spreadsheet; //using AsbSaubReport; //using AutoMapper; using System; +using System.Collections.Generic; using System.Linq; namespace ConsoleApp1 @@ -19,57 +20,62 @@ namespace ConsoleApp1 static void Main(/*string[] args*/) { - const string firstExcelPath = @"D:\excels\excel1.xlsx"; - const string secondExcelPath = @"D:\excels\excel2.xlsx"; + var sourceExcelPaths = new List + { + @"D:\excels\excel1.xlsx", + @"D:\excels\excel2.xlsx" + }; + const string resultExcelPath = @"D:\excels\result.xlsx"; - using var firstExcel = SpreadsheetDocument.Open(firstExcelPath, true); - using var secondExcel = SpreadsheetDocument.Open(secondExcelPath, true); - - - using (var resultExcel = SpreadsheetDocument.Create(resultExcelPath, - SpreadsheetDocumentType.Workbook, true)) + // result file create and init + using (var resultExcelFile = SpreadsheetDocument.Create(resultExcelPath, + SpreadsheetDocumentType.Workbook, true)) { - var workbookPart = resultExcel.AddWorkbookPart(); + var workbookPart = resultExcelFile.AddWorkbookPart(); workbookPart.Workbook = new Workbook(); - var sheets = workbookPart.Workbook.AppendChild(new Sheets()); + workbookPart.Workbook.AppendChild(new Sheets()); - var sharedStringTablePart = resultExcel.WorkbookPart. - AddNewPart(); + resultExcelFile.WorkbookPart.AddNewPart(); - var stylesPart = resultExcel.WorkbookPart. - AddNewPart(); + resultExcelFile.WorkbookPart.AddNewPart(); workbookPart.Workbook.Save(); } - using (var resultExcel = SpreadsheetDocument.Open(resultExcelPath, true)) + foreach (var sourceExcelPath in sourceExcelPaths) { - foreach (var sheet in firstExcel.WorkbookPart.Workbook.Descendants()) + using var sourceExcelFile = SpreadsheetDocument.Open(sourceExcelPath, false); + + using var resultExcelFile = SpreadsheetDocument.Open(resultExcelPath, true); + + foreach (var sheet in sourceExcelFile.WorkbookPart.Workbook.Descendants()) { // Чтобы писать на новый лист, нужен новый WorkSheetPart, WorkSheet, SheetData и Sheet. // https://stackoverflow.com/questions/9120544/openxml-multiple-sheets - var resultExcelWorkSheetPart = resultExcel.WorkbookPart.AddNewPart(); + var resultExcelWorkSheetPart = resultExcelFile.WorkbookPart.AddNewPart(); resultExcelWorkSheetPart.Worksheet = new Worksheet(); var resultExcelSheetData = resultExcelWorkSheetPart. Worksheet.AppendChild(new SheetData()); - var newSheet = new Sheet() { - Id = resultExcel.WorkbookPart.GetIdOfPart(resultExcelWorkSheetPart), - SheetId = sheet.SheetId.Value, - Name = sheet.Name + var newSheet = new Sheet() + { + Id = resultExcelFile.WorkbookPart.GetIdOfPart(resultExcelWorkSheetPart), + SheetId = sheet.SheetId.Value, + Name = sheet.Name }; - resultExcel.WorkbookPart.Workbook.Sheets.Append(newSheet); - var firstExcelWorkSheetPart = firstExcel.WorkbookPart. + resultExcelFile.WorkbookPart.Workbook.Sheets.Append(newSheet); + + var sourceWorkSheetPart = sourceExcelFile.WorkbookPart. GetPartById(sheet.Id) as WorksheetPart; // Не самая лучшая выборка рядов текущей страницы исходного файла. // Наверняка для этого есть метод. - var firstExcelRows = firstExcelWorkSheetPart.Worksheet.ChildElements + var sourceFileCurrentListRows = sourceWorkSheetPart.Worksheet.ChildElements .FirstOrDefault(w => w.LocalName == "sheetData").ChildElements; // Числовые ячейки хранятся как есть и переносятся без проблем, а вот значения строковых ячеек хранятся @@ -79,26 +85,27 @@ namespace ConsoleApp1 // Разные всяческие Part'ы можно найти тут: // https://docs.microsoft.com/ru-ru/dotnet/api/documentformat.openxml.packaging.spreadsheetdocument.workbookpart?view=openxml-2.8.1 - var firstExcelStringTable = firstExcel.WorkbookPart.GetPartsOfType() - .FirstOrDefault().SharedStringTable; - resultExcel.WorkbookPart.SharedStringTablePart.SharedStringTable = + var firstExcelStringTable = sourceExcelFile.WorkbookPart.GetPartsOfType() + .FirstOrDefault().SharedStringTable; + resultExcelFile.WorkbookPart.SharedStringTablePart.SharedStringTable = firstExcelStringTable.CloneNode(true) as SharedStringTable; // Аналогично переносим таблицу стилей ячеек (жирный шрифт и прочее). // Иначе ячейки переносятся абсолютно обычными, без всякой стилизации. - var stylesSheet = firstExcel.WorkbookPart.GetPartsOfType() - .FirstOrDefault().Stylesheet; - resultExcel.WorkbookPart.WorkbookStylesPart.Stylesheet = + var stylesSheet = sourceExcelFile.WorkbookPart.GetPartsOfType() + .FirstOrDefault().Stylesheet; + resultExcelFile.WorkbookPart.WorkbookStylesPart.Stylesheet = stylesSheet.CloneNode(true) as Stylesheet; // Клонируем ряды в новый файл (без клонирования они не вытаскиваются из общего дерева) - foreach (var row in firstExcelRows) + foreach (var row in sourceFileCurrentListRows) resultExcelSheetData.AppendChild(row.CloneNode(true)); } } + Console.WriteLine("Done. Press any key to quit."); Console.ReadKey(); From 3616c90c6527904f965051d412dfd6a47613891b Mon Sep 17 00:00:00 2001 From: KharchenkoVV Date: Thu, 16 Sep 2021 14:34:32 +0500 Subject: [PATCH 6/8] Fixed refill of cells in multiple tables union --- ConsoleApp1/Program.cs | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index b0feb913..d395ee23 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -24,6 +24,12 @@ namespace ConsoleApp1 { @"D:\excels\excel1.xlsx", @"D:\excels\excel2.xlsx" + //@"D:\excels\e1.xlsx", + //@"D:\excels\e2.xlsx", + //@"D:\excels\e3.xlsx", + //@"D:\excels\e4.xlsx", + //@"D:\excels\e5.xlsx", + //@"D:\excels\e6.xlsx" }; const string resultExcelPath = @"D:\excels\result.xlsx"; @@ -38,7 +44,7 @@ namespace ConsoleApp1 workbookPart.Workbook.AppendChild(new Sheets()); resultExcelFile.WorkbookPart.AddNewPart(); - + resultExcelFile.WorkbookPart.SharedStringTablePart.SharedStringTable = new SharedStringTable(); resultExcelFile.WorkbookPart.AddNewPart(); workbookPart.Workbook.Save(); @@ -85,10 +91,11 @@ namespace ConsoleApp1 // Разные всяческие Part'ы можно найти тут: // https://docs.microsoft.com/ru-ru/dotnet/api/documentformat.openxml.packaging.spreadsheetdocument.workbookpart?view=openxml-2.8.1 - var firstExcelStringTable = sourceExcelFile.WorkbookPart.GetPartsOfType() + var sourceExcelStringTable = sourceExcelFile.WorkbookPart.GetPartsOfType() .FirstOrDefault().SharedStringTable; - resultExcelFile.WorkbookPart.SharedStringTablePart.SharedStringTable = - firstExcelStringTable.CloneNode(true) as SharedStringTable; + + //resultExcelFile.WorkbookPart.SharedStringTablePart.SharedStringTable.Load(sourceExcelFile.WorkbookPart.SharedStringTablePart); + //sourceExcelStringTable.CloneNode(true) as SharedStringTable; // Аналогично переносим таблицу стилей ячеек (жирный шрифт и прочее). // Иначе ячейки переносятся абсолютно обычными, без всякой стилизации. @@ -101,7 +108,32 @@ namespace ConsoleApp1 // Клонируем ряды в новый файл (без клонирования они не вытаскиваются из общего дерева) foreach (var row in sourceFileCurrentListRows) - resultExcelSheetData.AppendChild(row.CloneNode(true)); + { + var newCells = new List(); + + foreach(var c in row.ChildElements) + { + var cell = (Cell)c; + if (cell.DataType is not null && cell.DataType.Value == CellValues.SharedString) + { + var cellValue = sourceExcelStringTable.ElementAt(int.Parse(cell.InnerText)).InnerText; + + newCells.Add(new Cell { + CellReference = cell.CellReference, + DataType = CellValues.String, + CellValue = new CellValue(cellValue) + }); + } + else + newCells.Add(cell.CloneNode(true) as Cell); + } + + var newRow = row.CloneNode(true); + + newRow.RemoveAllChildren(); + newRow.Append(newCells); + resultExcelSheetData.AppendChild(newRow); + } } } From 6c2e58a14d5925f06e990009564bfa99541349b5 Mon Sep 17 00:00:00 2001 From: KharchenkoVV Date: Fri, 17 Sep 2021 15:41:55 +0500 Subject: [PATCH 7/8] First version of Excel files union using ClosedXML library --- ConsoleApp1/ConsoleApp1.csproj | 2 +- ConsoleApp1/Program.cs | 149 ++++++++++++--------------------- 2 files changed, 55 insertions(+), 96 deletions(-) diff --git a/ConsoleApp1/ConsoleApp1.csproj b/ConsoleApp1/ConsoleApp1.csproj index 392da02e..42fc49a5 100644 --- a/ConsoleApp1/ConsoleApp1.csproj +++ b/ConsoleApp1/ConsoleApp1.csproj @@ -7,7 +7,7 @@ - + diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index d395ee23..04877954 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -1,11 +1,10 @@ -using DocumentFormat.OpenXml; -using DocumentFormat.OpenXml.Packaging; -using DocumentFormat.OpenXml.Spreadsheet; -//using AsbSaubReport; +//using AsbSaubReport; //using AutoMapper; using System; +using System.IO; using System.Collections.Generic; using System.Linq; +using ClosedXML.Excel; namespace ConsoleApp1 { @@ -17,127 +16,87 @@ namespace ConsoleApp1 class Program { + private static void RemovePicturesFromSheet(IXLWorksheet sheet) + { + var picsNames = new List(); + + foreach (var p in sheet.Pictures) + picsNames.Add(p.Name); + + var filteredPics = picsNames.Distinct(); + + foreach (var n in filteredPics) + sheet.Pictures.Delete(n); + } static void Main(/*string[] args*/) { var sourceExcelPaths = new List { - @"D:\excels\excel1.xlsx", - @"D:\excels\excel2.xlsx" + //@"D:\excels\excel1.xlsx", + //@"D:\excels\excel2.xlsx" //@"D:\excels\e1.xlsx", //@"D:\excels\e2.xlsx", //@"D:\excels\e3.xlsx", - //@"D:\excels\e4.xlsx", - //@"D:\excels\e5.xlsx", - //@"D:\excels\e6.xlsx" + @"D:\excels\e4.XLSX", + @"D:\excels\e5.XLSX", + @"D:\excels\e6.XLSX" }; const string resultExcelPath = @"D:\excels\result.xlsx"; - // result file create and init - using (var resultExcelFile = SpreadsheetDocument.Create(resultExcelPath, - SpreadsheetDocumentType.Workbook, true)) + using var resultExcelFile = new XLWorkbook(); + + const int maxAllowedColumns = 256; + + foreach(var sourceExcelPath in sourceExcelPaths) { - var workbookPart = resultExcelFile.AddWorkbookPart(); - workbookPart.Workbook = new Workbook(); + using var sourceExcelFile = new XLWorkbook(sourceExcelPath); - workbookPart.Workbook.AppendChild(new Sheets()); - - resultExcelFile.WorkbookPart.AddNewPart(); - resultExcelFile.WorkbookPart.SharedStringTablePart.SharedStringTable = new SharedStringTable(); - resultExcelFile.WorkbookPart.AddNewPart(); - - workbookPart.Workbook.Save(); - } - - foreach (var sourceExcelPath in sourceExcelPaths) - { - using var sourceExcelFile = SpreadsheetDocument.Open(sourceExcelPath, false); - - using var resultExcelFile = SpreadsheetDocument.Open(resultExcelPath, true); - - foreach (var sheet in sourceExcelFile.WorkbookPart.Workbook.Descendants()) + foreach (var sheet in sourceExcelFile.Worksheets) { - // Чтобы писать на новый лист, нужен новый WorkSheetPart, WorkSheet, SheetData и Sheet. - // https://stackoverflow.com/questions/9120544/openxml-multiple-sheets + //var columnsToDelete = sheet.Columns().Skip(sheet.ColumnsUsed().Count()); - var resultExcelWorkSheetPart = resultExcelFile.WorkbookPart.AddNewPart(); - resultExcelWorkSheetPart.Worksheet = new Worksheet(); + //foreach (var d in columnsToDelete) + // d.Delete(); - var resultExcelSheetData = resultExcelWorkSheetPart. - Worksheet.AppendChild(new SheetData()); - - var newSheet = new Sheet() + if (sheet.Columns().Count() > maxAllowedColumns && + sheet.Columns().Count() / sheet.ColumnsUsed().Count() > 5) { - Id = resultExcelFile.WorkbookPart.GetIdOfPart(resultExcelWorkSheetPart), - SheetId = sheet.SheetId.Value, - Name = sheet.Name - }; + Console.WriteLine($"В файле {Path.GetFileName(sourceExcelPath)} " + + $"{sheet.Columns().Count() - sheet.ColumnsUsed().Count()} пустых колонок " + + $"без записей. \n Если продолжить выполнение, форматирование выходного" + + $"листа может быть нарушено. \n Для сохранения структуры документа " + + $"рекомендуется прервать выполнение и удалить неиспользуемые колонки. \n" + + $" Продолжить выполнение? (y/n)"); - resultExcelFile.WorkbookPart.Workbook.Sheets.Append(newSheet); + var res = Console.ReadLine(); - var sourceWorkSheetPart = sourceExcelFile.WorkbookPart. - GetPartById(sheet.Id) as WorksheetPart; + if (res != "y") + return; - // Не самая лучшая выборка рядов текущей страницы исходного файла. - // Наверняка для этого есть метод. - var sourceFileCurrentListRows = sourceWorkSheetPart.Worksheet.ChildElements - .FirstOrDefault(w => w.LocalName == "sheetData").ChildElements; + var firstTableCell = sheet.FirstCellUsed(); + var lastTableCell = sheet.LastCellUsed(); + var rngData = sheet.Range(firstTableCell.Address, lastTableCell.Address); - // Числовые ячейки хранятся как есть и переносятся без проблем, а вот значения строковых ячеек хранятся - // в отдельной таблице. При этом в самой строковой ячейке хранится индекс этой строки в таблице - // (чтоб не хранить кучу одинаковых строк в документе. Что-то вроде интернирования строк). - // Тут как раз переносится клон этой таблицы из исходного файла в результирующий. - // Разные всяческие Part'ы можно найти тут: - // https://docs.microsoft.com/ru-ru/dotnet/api/documentformat.openxml.packaging.spreadsheetdocument.workbookpart?view=openxml-2.8.1 + RemovePicturesFromSheet(sheet); - var sourceExcelStringTable = sourceExcelFile.WorkbookPart.GetPartsOfType() - .FirstOrDefault().SharedStringTable; + var wsCopy = resultExcelFile.Worksheets.Add(sheet.Name); + wsCopy.Cell(1, 1).Value = rngData; - //resultExcelFile.WorkbookPart.SharedStringTablePart.SharedStringTable.Load(sourceExcelFile.WorkbookPart.SharedStringTablePart); - //sourceExcelStringTable.CloneNode(true) as SharedStringTable; - - // Аналогично переносим таблицу стилей ячеек (жирный шрифт и прочее). - // Иначе ячейки переносятся абсолютно обычными, без всякой стилизации. - - var stylesSheet = sourceExcelFile.WorkbookPart.GetPartsOfType() - .FirstOrDefault().Stylesheet; - resultExcelFile.WorkbookPart.WorkbookStylesPart.Stylesheet = - stylesSheet.CloneNode(true) as Stylesheet; - - // Клонируем ряды в новый файл (без клонирования они не вытаскиваются из общего дерева) - - foreach (var row in sourceFileCurrentListRows) + GC.Collect(); + } + else { - var newCells = new List(); + RemovePicturesFromSheet(sheet); - foreach(var c in row.ChildElements) - { - var cell = (Cell)c; - if (cell.DataType is not null && cell.DataType.Value == CellValues.SharedString) - { - var cellValue = sourceExcelStringTable.ElementAt(int.Parse(cell.InnerText)).InnerText; - - newCells.Add(new Cell { - CellReference = cell.CellReference, - DataType = CellValues.String, - CellValue = new CellValue(cellValue) - }); - } - else - newCells.Add(cell.CloneNode(true) as Cell); - } - - var newRow = row.CloneNode(true); - - newRow.RemoveAllChildren(); - newRow.Append(newCells); - resultExcelSheetData.AppendChild(newRow); + sheet.CopyTo(resultExcelFile, sheet.Name); + GC.Collect(); } } } - + resultExcelFile.SaveAs(resultExcelPath); Console.WriteLine("Done. Press any key to quit."); Console.ReadKey(); From 99c805c7d4182c8af715889fd59919cfb21f4c37 Mon Sep 17 00:00:00 2001 From: KharchenkoVV Date: Fri, 17 Sep 2021 15:58:01 +0500 Subject: [PATCH 8/8] Added advanced union logic for files with small amount of empty columns --- ConsoleApp1/Program.cs | 43 +++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index 04877954..03daec69 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -29,6 +29,15 @@ namespace ConsoleApp1 sheet.Pictures.Delete(n); } + private static IXLRange GetCellsRange(IXLWorksheet sheet) + { + var firstTableCell = sheet.FirstCellUsed(); + var lastTableCell = sheet.LastCellUsed(); + var rngData = sheet.Range(firstTableCell.Address, lastTableCell.Address); + + return rngData; + } + static void Main(/*string[] args*/) { var sourceExcelPaths = new List @@ -36,8 +45,8 @@ namespace ConsoleApp1 //@"D:\excels\excel1.xlsx", //@"D:\excels\excel2.xlsx" //@"D:\excels\e1.xlsx", - //@"D:\excels\e2.xlsx", - //@"D:\excels\e3.xlsx", + @"D:\excels\e2.xlsx", + @"D:\excels\e3.xlsx", @"D:\excels\e4.XLSX", @"D:\excels\e5.XLSX", @"D:\excels\e6.XLSX" @@ -55,12 +64,10 @@ namespace ConsoleApp1 foreach (var sheet in sourceExcelFile.Worksheets) { - //var columnsToDelete = sheet.Columns().Skip(sheet.ColumnsUsed().Count()); - - //foreach (var d in columnsToDelete) - // d.Delete(); - - if (sheet.Columns().Count() > maxAllowedColumns && + // Если в файле очень много колонок и многие из них пустые - + // предлагаем удалить их вручную или пытаемся копировать + // ячейки попорядку (не красиво). Красиво не получится. + if (sheet.Columns().Count() > maxAllowedColumns && sheet.Columns().Count() / sheet.ColumnsUsed().Count() > 5) { Console.WriteLine($"В файле {Path.GetFileName(sourceExcelPath)} " + @@ -75,9 +82,7 @@ namespace ConsoleApp1 if (res != "y") return; - var firstTableCell = sheet.FirstCellUsed(); - var lastTableCell = sheet.LastCellUsed(); - var rngData = sheet.Range(firstTableCell.Address, lastTableCell.Address); + var rngData = GetCellsRange(sheet); RemovePicturesFromSheet(sheet); @@ -86,6 +91,22 @@ namespace ConsoleApp1 GC.Collect(); } + // Если колонок в файле немного, но среди них также есть пустые - + // проще удалить ненужные колонки и скопировать лист красиво. + else if (sheet.Columns().Count() < maxAllowedColumns && + sheet.Columns().Count() / sheet.ColumnsUsed().Count() > 2) + { + RemovePicturesFromSheet(sheet); + + var columnsToDelete = sheet.Columns().Skip(sheet.ColumnsUsed().Count()); + + foreach (var d in columnsToDelete) + d.Delete(); + + sheet.CopyTo(resultExcelFile, sheet.Name); + GC.Collect(); + } + // Если по колонкам все хорошо, копируем лист красиво. else { RemovePicturesFromSheet(sheet);